1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.dam.jcr;
35
36 import static info.magnolia.dam.api.AssetProviderCapability.*;
37
38 import info.magnolia.context.MgnlContext;
39 import info.magnolia.dam.api.AbstractAssetProvider;
40 import info.magnolia.dam.api.Asset;
41 import info.magnolia.dam.api.AssetProviderCapability;
42 import info.magnolia.dam.api.AssetQuery;
43 import info.magnolia.dam.api.Folder;
44 import info.magnolia.dam.api.Item;
45 import info.magnolia.dam.api.ItemKey;
46 import info.magnolia.dam.api.PathAwareAssetProvider;
47 import info.magnolia.dam.api.metadata.AssetMetadata;
48 import info.magnolia.dam.api.metadata.DublinCore;
49 import info.magnolia.dam.api.metadata.MagnoliaAssetMetadata;
50 import info.magnolia.dam.core.config.DamCoreConfiguration;
51 import info.magnolia.jcr.RuntimeRepositoryException;
52 import info.magnolia.jcr.util.NodeUtil;
53 import info.magnolia.jcr.wrapper.I18nNodeWrapper;
54
55 import java.net.URI;
56 import java.net.URISyntaxException;
57 import java.util.ArrayList;
58 import java.util.Collections;
59 import java.util.EnumSet;
60 import java.util.HashMap;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Map;
64
65 import javax.jcr.ItemNotFoundException;
66 import javax.jcr.Node;
67 import javax.jcr.RepositoryException;
68 import javax.jcr.query.Query;
69 import javax.jcr.query.QueryManager;
70 import javax.jcr.query.QueryResult;
71 import javax.jcr.query.Row;
72
73 import org.apache.commons.collections4.IteratorUtils;
74 import org.apache.commons.collections4.Transformer;
75 import org.apache.commons.lang3.StringEscapeUtils;
76 import org.apache.commons.lang3.StringUtils;
77 import org.apache.jackrabbit.JcrConstants;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81
82
83
84 public class JcrAssetProvider extends AbstractAssetProvider implements PathAwareAssetProvider {
85
86 private static final Logger log = LoggerFactory.getLogger(JcrAssetProvider.class);
87
88 protected static final String ASSET_SELECTOR_NAME = "asset";
89 protected static final String ASSET_SELECT_STATEMENT = "SELECT * FROM [" + JcrConstants.NT_BASE + "] AS " + ASSET_SELECTOR_NAME;
90 protected static final String ASSET_RESTRICTION = ASSET_SELECTOR_NAME + ".[jcr:primaryType] = 'mgnl:asset'";
91 protected static final String FOLDER_RESTRICTION = "[jcr:primaryType] = 'mgnl:folder'";
92 protected static final String ASSET_JOIN_STATEMENT = String.format(" INNER JOIN [mgnl:resource] AS resource ON ISCHILDNODE(resource, %s)", ASSET_SELECTOR_NAME);
93
94 protected static final String ORDER_BY = " order by ";
95 protected static final String ASCENDING_KEYWORD = " asc";
96 protected static final String DESCENDING_KEYWORD = " desc";
97
98
99
100
101 private static final Map<String, String> jcrPropertiesMapper;
102 static {
103 Map<String, String> map = new HashMap<String, String>();
104 map.put("created", "mgnl:created");
105 map.put("lastModified", "mgnl:lastModified");
106 jcrPropertiesMapper = Collections.unmodifiableMap(map);
107 }
108
109 private final DamCoreConfiguration configuration;
110
111 private String workspaceName = DamConstants.WORKSPACE;
112 private String rootPath = "/";
113
114 public JcrAssetProvider(final DamCoreConfiguration configuration) {
115 this.configuration = configuration;
116 }
117
118 public String getRootPath() {
119 return rootPath;
120 }
121
122 public void setRootPath(String rootPath) {
123 this.rootPath = rootPath;
124 }
125
126 public String getWorkspaceName() {
127 return workspaceName;
128 }
129
130 public void setWorkspaceName(String workspaceName) {
131 this.workspaceName = workspaceName;
132 }
133
134 @Override
135 protected EnumSet<AssetProviderCapability> setupProviderCapabilities() {
136 return EnumSet.of(query, queryWithProviderSpecificString, hierarchical, rendition, queryWithPagination, queryWithSorting);
137 }
138
139 @Override
140 public boolean supports(Class<? extends AssetMetadata> metaData) {
141 return (MagnoliaAssetMetadata.class.isAssignableFrom(metaData) || DublinCore.class.isAssignableFrom(metaData));
142 }
143
144 @Override
145 public Asset getAsset(ItemKey assetKey) throws AssetNotFoundException, IllegalItemKeyException {
146 return createAsset(getNodeByIdentifier(assetKey));
147 }
148
149 @Override
150 public Asset getAsset(String assetPath) throws PathNotFoundException {
151 return createAsset(getNodeByPath(assetPath));
152 }
153
154
155
156
157 Asset createAsset(final Node assetNode) {
158 if (AssetNodeTypes.isAsset(assetNode)) {
159 log.debug("Created asset linked to the following node '{}'", NodeUtil.getPathIfPossible(assetNode));
160 return new JcrAsset(this, new I18nNodeWrapper(assetNode));
161 }
162 throw new IllegalArgumentException("Node '" + NodeUtil.getPathIfPossible(assetNode) + "' is not defining an asset but another item type");
163 }
164
165 @Override
166 public Folder getFolder(final ItemKey folderKey) throws AssetNotFoundException, IllegalItemKeyException {
167 return createFolder(getNodeByIdentifier(folderKey));
168 }
169
170 @Override
171 public Folder getFolder(final String folderPath) throws PathNotFoundException {
172 return createFolder(getNodeByPath(folderPath));
173 }
174
175
176
177
178 Folder createFolder(final Node folderNode) {
179 if (AssetNodeTypes.isFolder(folderNode) || isRootFolder(folderNode)) {
180 log.debug("Created folder linked to the following node '{}'", NodeUtil.getPathIfPossible(folderNode));
181 return new JcrFolder(this, folderNode);
182 }
183 throw new IllegalArgumentException("Node '" + NodeUtil.getPathIfPossible(folderNode) + "' is not defining a folder but another item type");
184 }
185
186 @Override
187 public Item getItem(final ItemKey itemKey) throws AssetNotFoundException, IllegalItemKeyException {
188 return createItem(getNodeByIdentifier(itemKey));
189 }
190
191 @Override
192 public Item getItem(final String path) throws PathNotFoundException {
193 return createItem(getNodeByPath(path));
194 }
195
196 Iterator<Item> getChildren(final Node parentNode) {
197 try {
198 Iterator<Node> filteredChildNodes = NodeUtil.getNodes(parentNode, new JcrItemNodeTypePredicate()).iterator();
199 return IteratorUtils.transformedIterator(filteredChildNodes, new NodeToItemTransformer());
200 } catch (RepositoryException e) {
201 throw new RuntimeRepositoryException(e);
202 }
203 }
204
205 Item getChild(final String name, final Node parentNode) {
206 Node child;
207 try {
208 child = parentNode.getNode(name);
209 } catch (RepositoryException e) {
210 throw new RuntimeRepositoryException(e);
211 }
212 return createItem(child);
213 }
214
215 Folder getParent(final Node itemNode) {
216 try {
217 return createFolder(itemNode.getParent());
218 } catch (RepositoryException e) {
219 throw new RuntimeRepositoryException(e);
220 }
221 }
222
223
224
225
226 boolean isRootFolder(final Node folderNode) {
227 return getRootPath().equals(NodeUtil.getPathIfPossible(folderNode));
228 }
229
230
231
232
233 String getPath(final Node itemNode) {
234 try {
235 return getRootPath().equals("/") ? itemNode.getPath() : StringUtils.removeStart(itemNode.getPath(), getRootPath());
236 } catch (RepositoryException e) {
237 throw new RuntimeRepositoryException(e);
238 }
239 }
240
241 String getName(final Node itemNode) {
242 try {
243 return itemNode.getName();
244 } catch (RepositoryException e) {
245 throw new RuntimeRepositoryException(e);
246 }
247 }
248
249
250
251
252 Item createItem(final Node node) {
253 if (AssetNodeTypes.isAsset(node)) {
254 return createAsset(node);
255 } else if (AssetNodeTypes.isFolder(node) || isRootFolder(node)) {
256 return createFolder(node);
257 }
258 throw new IllegalArgumentException("Node '" + NodeUtil.getPathIfPossible(node) + "' is not referencing an Folder or an Asset but another item type");
259 }
260
261 @Override
262 public Folder getRootFolder() {
263 return createFolder(getRootNode());
264 }
265
266 @Override
267 public Iterator<Item> list(final AssetQuery assetQuery) {
268 final String queryStr = buildQueryString(assetQuery);
269
270 log.debug("Running SQL2 query '{}' against workspace {}.", queryStr, getWorkspaceName());
271
272 try {
273
274 final QueryManager jcrQueryManager = MgnlContext.getJCRSession(getWorkspaceName()).getWorkspace().getQueryManager();
275 final Query query = jcrQueryManager.createQuery(queryStr, Query.JCR_SQL2);
276
277 if (assetQuery.getMaxResults() > 0) {
278 query.setLimit(assetQuery.getMaxResults());
279 }
280 if (assetQuery.getOffset() > 0) {
281 query.setOffset(assetQuery.getOffset());
282 }
283
284 final QueryResult queryResult = query.execute();
285 return IteratorUtils.transformedIterator(queryResult.getRows(), new RowToItemTransformer());
286
287 } catch (RepositoryException re) {
288 throw new RuntimeRepositoryException(re);
289 }
290 }
291
292
293
294
295 protected String buildQueryString(final AssetQuery assetQuery) {
296
297 final StringBuilder selectStatement = new StringBuilder().append(ASSET_SELECT_STATEMENT);
298
299 final boolean considerExtension = StringUtils.isNotBlank(assetQuery.getExtension());
300
301 if (considerExtension) {
302 selectStatement.append(ASSET_JOIN_STATEMENT);
303 }
304
305 selectStatement.append(" WHERE(");
306 if (assetQuery.includesAssets()) {
307 selectStatement.append(ASSET_RESTRICTION);
308 } else {
309 if (!assetQuery.includesFolders()) {
310
311 throw new IllegalArgumentException("Querying without including assets or folders doesn't make sense.");
312 }
313 }
314
315 if (assetQuery.includesFolders()) {
316 if (assetQuery.includesAssets()) {
317 selectStatement.append(" OR ");
318 }
319 selectStatement.append(FOLDER_RESTRICTION);
320 }
321
322 selectStatement.append(")");
323
324
325 List<String> whereStatements = new ArrayList<String>();
326
327
328 if (considerExtension) {
329 whereStatements.add(String.format("resource.extension='%s'", assetQuery.getExtension()));
330 }
331
332
333 if (StringUtils.isNotBlank(assetQuery.getAdditionalQueryStatement())) {
334 whereStatements.add(StringEscapeUtils.unescapeHtml4(assetQuery.getAdditionalQueryStatement()));
335 }
336
337
338 String condition;
339 if (assetQuery.includesDescendants()) {
340 condition = "ISDESCENDANTNODE(" + ASSET_SELECTOR_NAME + ", '%s')";
341 } else {
342
343 condition = "ISCHILDNODE(" + ASSET_SELECTOR_NAME + ", '%s')";
344 }
345 if (StringUtils.isNotBlank(assetQuery.getRootPath())) {
346 whereStatements.add(String.format(condition, assetQuery.getRootPath()));
347 } else if (assetQuery.getRootFolder() != null) {
348 try {
349 Node assetFolder = MgnlContext.getJCRSession(getWorkspaceName()).getNodeByIdentifier(assetQuery.getRootFolder().getItemKey().getAssetId());
350 whereStatements.add(String.format(condition, assetFolder.getPath()));
351 } catch (RepositoryException e) {
352 log.error("Failed to retrieve assets for the following folder node id '{}':", assetQuery.getRootFolder().getItemKey().getAssetId(), e);
353 }
354 }
355
356 if (!whereStatements.isEmpty()) {
357 selectStatement.append(" AND ");
358 selectStatement.append(StringUtils.join(whereStatements, " AND "));
359 }
360
361
362 StringBuilder orderByStatement = new StringBuilder();
363 if (!assetQuery.getSorters().isEmpty()) {
364 String sortOrder;
365 for (AssetQuery.OrderBy orderBy : assetQuery.getSorters()) {
366 String jcrPropertyName = orderBy.getPropertyId();
367 if (JcrAssetProvider.jcrPropertiesMapper.containsKey(jcrPropertyName)) {
368 jcrPropertyName = JcrAssetProvider.jcrPropertiesMapper.get(orderBy.getPropertyId());
369 }
370 sortOrder = orderBy.getOrder() == AssetQuery.Order.ASCENDING ? ASCENDING_KEYWORD : DESCENDING_KEYWORD;
371 orderByStatement.append("asset.[").append(jcrPropertyName).append("]").append(sortOrder).append(",");
372 }
373 if (orderByStatement.length() > 0) {
374 selectStatement.append(ORDER_BY).append(orderByStatement).deleteCharAt(selectStatement.length() - 1);
375 }
376 }
377
378 return selectStatement.toString();
379 }
380
381 public String getLink(final Asset asset) {
382 final String contextPath = MgnlContext.getContextPath();
383 String fileName = asset.getFileName();
384 try {
385 fileName = new URI(null, null, fileName, null).toASCIIString();
386 } catch (URISyntaxException urise) {
387 log.warn("Could not encode the following file name {}", fileName);
388 }
389 return contextPath + configuration.getDownloadPath() + asset.getItemKey().asString() + "/" + fileName;
390 }
391
392
393
394
395
396
397
398 private Node getNodeByIdentifier(final ItemKey itemKey) {
399 try {
400 Node node = MgnlContext.getJCRSession(getWorkspaceName()).getNodeByIdentifier(itemKey.getAssetId());
401 if (!isChildOfRoot(node)) {
402 throw new IllegalItemKeyException(itemKey, this, "ItemKey points to an Asset outside the realm of this provider");
403 }
404 return node;
405 } catch (ItemNotFoundException infe) {
406 throw new AssetNotFoundException(itemKey);
407 } catch (RepositoryException e) {
408 throw new IllegalItemKeyException(itemKey, this, e.getMessage());
409 }
410 }
411
412
413
414
415
416
417
418 private Node getNodeByPath(String relPath) {
419 try {
420 if (StringUtils.isBlank(relPath)) {
421 return getRootNode();
422 }
423 return getRootNode().getNode(StringUtils.removeStart(relPath, "/"));
424 } catch (RepositoryException e) {
425 throw new PathNotFoundException(relPath);
426 }
427 }
428
429 private Node getRootNode() {
430 try {
431 return MgnlContext.getJCRSession(getWorkspaceName()).getNode(getRootPath());
432 } catch (RepositoryException e) {
433 throw new PathNotFoundException(rootPath);
434 }
435 }
436
437 private boolean isChildOfRoot(Node itemNode) {
438 try {
439 return itemNode.getPath().startsWith(getRootPath());
440 } catch (RepositoryException e) {
441 throw new RuntimeRepositoryException(e);
442 }
443 }
444
445
446
447
448 class NodeToItemTransformer implements Transformer {
449
450 @Override
451 public Object transform(Object input) {
452 return createItem( (Node)input);
453 }
454 }
455
456
457
458
459 class RowToItemTransformer implements Transformer {
460
461 @Override
462 public Object transform(Object input) {
463 try {
464 Node node = ((Row) input).getNode(ASSET_SELECTOR_NAME);
465 return createItem(node);
466 } catch (RepositoryException e) {
467 throw new RuntimeRepositoryException(e);
468 }
469 }
470 }
471 }