1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.onehippo.forge.content.exim.core.util;
17
18 import java.rmi.RemoteException;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.Set;
23
24 import javax.jcr.ItemNotFoundException;
25 import javax.jcr.Node;
26 import javax.jcr.NodeIterator;
27 import javax.jcr.RepositoryException;
28 import javax.jcr.Session;
29 import javax.jcr.Workspace;
30
31 import org.apache.commons.lang3.ArrayUtils;
32 import org.apache.commons.lang3.StringUtils;
33 import org.hippoecm.repository.HippoStdNodeType;
34 import org.hippoecm.repository.api.HippoNode;
35 import org.hippoecm.repository.api.HippoNodeType;
36 import org.hippoecm.repository.api.HippoWorkspace;
37 import org.hippoecm.repository.api.StringCodec;
38 import org.hippoecm.repository.api.StringCodecFactory;
39 import org.hippoecm.repository.api.Workflow;
40 import org.hippoecm.repository.api.WorkflowException;
41 import org.hippoecm.repository.api.WorkflowManager;
42 import org.hippoecm.repository.standardworkflow.DefaultWorkflow;
43 import org.hippoecm.repository.standardworkflow.FolderWorkflow;
44 import org.hippoecm.repository.util.JcrUtils;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48
49
50
51 public class HippoNodeUtils {
52
53 private static Logger log = LoggerFactory.getLogger(HippoNodeUtils.class);
54
55
56
57
58 private static final String DOCUMENT_PATH_PREFIX = "/content/documents/";
59
60
61
62
63 private static final String GALLERY_PATH_PREFIX = "/content/gallery/";
64
65
66
67
68 private static final String ASSET_PATH_PREFIX = "/content/assets/";
69
70
71
72
73 private static final String DEFAULT_HIPPO_FOLDER_NODE_TYPE = "hippostd:folder";
74
75
76
77
78 private static final String DEFAULT_HIPPO_FOLDER_WORKFLOW_CATEGORY = "threepane";
79
80
81
82
83 private static final String DEFAULT_NEW_DOCUMENT_WORKFLOW_CATEGORY = "new-document";
84
85
86
87
88 private static final String DEFAULT_NEW_FOLDER_WORKFLOW_CATEGORY = "new-folder";
89
90
91
92
93 private static final String DEFAULT_WORKFLOW_CATEGORY = "core";
94
95
96
97
98 private static final StringCodec DEFAULT_URI_ENCODING = new StringCodecFactory.UriEncoding();
99
100 private HippoNodeUtils() {
101 }
102
103
104
105
106
107 public static StringCodec getDefaultUriEncoding() {
108 return DEFAULT_URI_ENCODING;
109 }
110
111
112
113
114
115
116
117
118
119 public static Node getChildNodeOfType(final Node baseNode, final String childNodeName,
120 final String... childNodeTypes) throws RepositoryException {
121 if (!baseNode.hasNode(childNodeName)) {
122 return null;
123 }
124
125 Node childNode;
126
127 for (NodeIterator nodeIt = baseNode.getNodes(childNodeName); nodeIt.hasNext();) {
128 childNode = nodeIt.nextNode();
129
130 if (StringUtils.equals(childNodeName, childNode.getName())) {
131 if (childNodeTypes == null || childNodeTypes.length == 0) {
132 return childNode;
133 } else {
134 for (String childNodeType : childNodeTypes) {
135 if (childNode.isNodeType(childNodeType)) {
136 return childNode;
137 }
138 }
139 }
140 }
141 }
142
143 return null;
144 }
145
146
147
148
149
150
151
152
153
154 public static Workflow getHippoWorkflow(final Session session, final String category, final Node node)
155 throws RepositoryException {
156 Workspace workspace = session.getWorkspace();
157
158 ClassLoader workspaceClassloader = workspace.getClass().getClassLoader();
159 ClassLoader currentClassloader = Thread.currentThread().getContextClassLoader();
160
161 try {
162 if (workspaceClassloader != currentClassloader) {
163 Thread.currentThread().setContextClassLoader(workspaceClassloader);
164 }
165
166 WorkflowManager wfm = ((HippoWorkspace) workspace).getWorkflowManager();
167
168 return wfm.getWorkflow(category, node);
169 } finally {
170 if (workspaceClassloader != currentClassloader) {
171 Thread.currentThread().setContextClassLoader(currentClassloader);
172 }
173 }
174 }
175
176
177
178
179
180
181
182 public static Node getFirstVariantNode(final Node handle) throws RepositoryException {
183 if (handle == null) {
184 return null;
185 }
186
187 for (NodeIterator nodeIt = handle.getNodes(handle.getName()); nodeIt.hasNext();) {
188 Node variantNode = nodeIt.nextNode();
189
190 if (variantNode != null) {
191 return variantNode;
192 }
193 }
194
195 return null;
196 }
197
198
199
200
201
202
203
204 public static Map<String, Node> getDocumentVariantsMap(final Node handle) throws RepositoryException {
205 Map<String, Node> variantsMap = new HashMap<>();
206 Node variantNode = null;
207 String hippoState;
208
209 for (NodeIterator nodeIt = handle.getNodes(handle.getName()); nodeIt.hasNext();) {
210 variantNode = nodeIt.nextNode();
211
212 if (variantNode.hasProperty(HippoStdNodeType.HIPPOSTD_STATE)) {
213 hippoState = variantNode.getProperty(HippoStdNodeType.HIPPOSTD_STATE).getString();
214 variantsMap.put(hippoState, variantNode);
215 }
216 }
217
218 return variantsMap;
219 }
220
221
222
223
224
225
226
227
228
229 public static Node getDocumentVariantByHippoStdState(final Node handle, final String hippoStdState)
230 throws RepositoryException {
231 Node variantNode = null;
232 String state;
233
234 for (NodeIterator nodeIt = handle.getNodes(handle.getName()); nodeIt.hasNext();) {
235 variantNode = nodeIt.nextNode();
236
237 if (variantNode.hasProperty(HippoStdNodeType.HIPPOSTD_STATE)) {
238 state = variantNode.getProperty(HippoStdNodeType.HIPPOSTD_STATE).getString();
239 if (StringUtils.equals(hippoStdState, state)) {
240 return variantNode;
241 }
242 }
243 }
244
245 return null;
246 }
247
248
249
250
251
252
253
254 public static boolean isDocumentHandleLive(final Node handle) throws RepositoryException {
255 Node liveVariant = getDocumentVariantByHippoStdState(handle, HippoStdNodeType.PUBLISHED);
256
257 if (liveVariant != null) {
258 String[] availabilities = JcrUtils.getMultipleStringProperty(liveVariant, HippoNodeType.HIPPO_AVAILABILITY,
259 ArrayUtils.EMPTY_STRING_ARRAY);
260 for (String availability : availabilities) {
261 if (StringUtils.equals("live", availability)) {
262 return true;
263 }
264 }
265 }
266
267 return false;
268 }
269
270
271
272
273
274
275
276
277
278 public static Node createMissingHippoFolders(final Session session, String absPath)
279 throws RepositoryException, WorkflowException {
280
281 String[] folderNames = StringUtils
282 .split(ContentPathUtils.encodeNodePath(ContentPathUtils.removeIndexNotationInNodePath(absPath)), "/");
283
284 Node rootNode = session.getRootNode();
285 Node curNode = rootNode;
286 String folderNodePath;
287
288 for (String folderName : folderNames) {
289 if (curNode == rootNode) {
290 folderNodePath = "/" + folderName;
291 } else {
292 folderNodePath = curNode.getPath() + "/" + folderName;
293 }
294
295 Node existingFolderNode = getExistingHippoFolderNode(session, folderNodePath);
296
297 if (existingFolderNode == null) {
298 curNode = session.getNode(
299 createHippoFolderNodeByWorkflow(session, curNode, DEFAULT_HIPPO_FOLDER_NODE_TYPE, folderName));
300 } else {
301 curNode = existingFolderNode;
302 }
303
304 curNode = getHippoCanonicalNode(curNode);
305
306 if (isHippoMirrorNode(curNode)) {
307 curNode = getRereferencedNodeByHippoMirror(curNode);
308 }
309 }
310
311 return curNode;
312 }
313
314
315
316
317
318
319
320
321 public static Node getHippoDocumentHandle(Node node) throws RepositoryException {
322 if (node.isNodeType("hippo:handle")) {
323 return node;
324 } else if (node.isNodeType("hippo:document")) {
325 if (!node.getSession().getRootNode().isSame(node)) {
326 Node parentNode = node.getParent();
327
328 if (parentNode.isNodeType("hippo:handle")) {
329 return parentNode;
330 }
331 }
332 }
333
334 return null;
335 }
336
337
338
339
340
341
342 public static boolean isDocumentPath(final String path) {
343 return StringUtils.startsWith(path, DOCUMENT_PATH_PREFIX);
344 }
345
346
347
348
349
350
351 public static boolean isGalleryPath(final String path) {
352 return StringUtils.startsWith(path, GALLERY_PATH_PREFIX);
353 }
354
355
356
357
358
359
360 public static boolean isAssetPath(final String path) {
361 return StringUtils.startsWith(path, ASSET_PATH_PREFIX);
362 }
363
364
365
366
367
368
369 public static boolean isBinaryPath(final String path) {
370 return isGalleryPath(path) || isAssetPath(path);
371 }
372
373
374
375
376
377
378 static Node getHippoCanonicalNode(Node node) {
379 if (node instanceof HippoNode) {
380 HippoNode hnode = (HippoNode) node;
381
382 try {
383 Node canonical = hnode.getCanonicalNode();
384
385 if (canonical == null) {
386 log.debug("Cannot get canonical node for '{}'. This means there is no phyiscal equivalence of the "
387 + "virtual node. Return null", node.getPath());
388 }
389
390 return canonical;
391 } catch (RepositoryException e) {
392 log.error("Repository exception while fetching canonical node. Return null", e);
393 throw new RuntimeException(e);
394 }
395 }
396
397 return node;
398 }
399
400
401
402
403
404
405
406 static boolean isHippoMirrorNode(Node node) throws RepositoryException {
407 if (node.isNodeType(HippoNodeType.NT_FACETSELECT) || node.isNodeType(HippoNodeType.NT_MIRROR)) {
408 return true;
409 }
410
411 return false;
412 }
413
414
415
416
417
418
419 static Node getRereferencedNodeByHippoMirror(Node mirrorNode) {
420 String docBaseUUID = null;
421
422 try {
423 if (!isHippoMirrorNode(mirrorNode)) {
424 log.info("Cannot deref a node that is not of (sub)type '{}' or '{}'. Return null",
425 HippoNodeType.NT_FACETSELECT, HippoNodeType.NT_MIRROR);
426 return null;
427 }
428
429
430 docBaseUUID = mirrorNode.getProperty(HippoNodeType.HIPPO_DOCBASE).getString();
431
432 try {
433 return mirrorNode.getSession().getNodeByIdentifier(docBaseUUID);
434 } catch (IllegalArgumentException e) {
435 log.warn("Docbase cannot be parsed to a valid uuid. Return null");
436 return null;
437 }
438 } catch (ItemNotFoundException e) {
439 String path = null;
440
441 try {
442 path = mirrorNode.getPath();
443 } catch (RepositoryException e1) {
444 log.error("RepositoryException, cannot return deferenced node: {}", e1);
445 }
446
447 log.info(
448 "ItemNotFoundException, cannot return deferenced node because docbase uuid '{}' cannot be found. The docbase property is at '{}/hippo:docbase'. Return null",
449 docBaseUUID, path);
450 } catch (RepositoryException e) {
451 log.error("RepositoryException, cannot return deferenced node: {}", e);
452 }
453
454 return null;
455 }
456
457
458
459
460
461
462
463
464 static Node getExistingHippoFolderNode(final Session session, final String absPath) throws RepositoryException {
465
466 if (StringUtils.isEmpty(absPath)) {
467 return null;
468 }
469
470 String[] pathSegments = StringUtils
471 .split(ContentPathUtils.encodeNodePath(ContentPathUtils.removeIndexNotationInNodePath(absPath)), "/");
472
473 Node curFolder = session.getRootNode();
474
475 for (String pathSegment : pathSegments) {
476 if (!curFolder.hasNode(pathSegment)) {
477 return null;
478 }
479
480 boolean found = false;
481
482 for (NodeIterator nodeIt = curFolder.getNodes(pathSegment); nodeIt.hasNext();) {
483 Node childNode = nodeIt.nextNode();
484
485 if (childNode != null && !isHippoDocumentHandleOrVariant(childNode)) {
486 found = true;
487 curFolder = childNode;
488 break;
489 }
490 }
491
492 if (!found) {
493 return null;
494 }
495 }
496
497 if (curFolder == null) {
498 return null;
499 }
500
501 Node canonicalFolderNode = getHippoCanonicalNode(curFolder);
502
503 if (isHippoMirrorNode(canonicalFolderNode)) {
504 canonicalFolderNode = getRereferencedNodeByHippoMirror(canonicalFolderNode);
505 }
506
507 if (canonicalFolderNode == null) {
508 return null;
509 }
510
511 if (isHippoDocumentHandleOrVariant(canonicalFolderNode)) {
512 return null;
513 }
514
515 return canonicalFolderNode;
516 }
517
518
519
520
521
522
523
524 static boolean isHippoDocumentHandleOrVariant(Node node) throws RepositoryException {
525 if (node.isNodeType("hippo:handle")) {
526 return true;
527 } else if (node.isNodeType("hippo:document")) {
528 if (!node.getSession().getRootNode().isSame(node)) {
529 Node parentNode = node.getParent();
530
531 if (parentNode.isNodeType("hippo:handle")) {
532 return true;
533 }
534 }
535 }
536
537 return false;
538 }
539
540
541
542
543
544
545
546
547
548
549
550 static String createHippoFolderNodeByWorkflow(final Session session, Node folderNode, String nodeTypeName,
551 String name) throws RepositoryException, WorkflowException {
552 try {
553 folderNode = getHippoCanonicalNode(folderNode);
554 Workflow wf = getHippoWorkflow(session, DEFAULT_HIPPO_FOLDER_WORKFLOW_CATEGORY, folderNode);
555
556 if (wf instanceof FolderWorkflow) {
557 FolderWorkflow fwf = (FolderWorkflow) wf;
558
559 String category = DEFAULT_NEW_DOCUMENT_WORKFLOW_CATEGORY;
560
561 if (nodeTypeName.equals(DEFAULT_HIPPO_FOLDER_NODE_TYPE)) {
562 category = DEFAULT_NEW_FOLDER_WORKFLOW_CATEGORY;
563
564
565 if (fwf.hints() != null && fwf.hints().get("prototypes") != null) {
566 Object protypesMap = fwf.hints().get("prototypes");
567 if (protypesMap instanceof Map) {
568 for (Object o : ((Map) protypesMap).entrySet()) {
569 Entry entry = (Entry) o;
570 if (entry.getKey() instanceof String && entry.getValue() instanceof Set) {
571 if (((Set) entry.getValue()).contains(DEFAULT_HIPPO_FOLDER_NODE_TYPE)) {
572
573 category = (String) entry.getKey();
574 break;
575 }
576 }
577 }
578 }
579 }
580 }
581
582 String nodeName = DEFAULT_URI_ENCODING.encode(name);
583 String added = fwf.add(category, nodeTypeName, nodeName);
584 if (added == null) {
585 throw new WorkflowException("Failed to add document/folder for type '" + nodeTypeName
586 + "'. Make sure there is a prototype.");
587 }
588 Node addedNode = folderNode.getSession().getNode(added);
589 if (!nodeName.equals(name)) {
590 DefaultWorkflow defaultWorkflow = (DefaultWorkflow) getHippoWorkflow(session,
591 DEFAULT_WORKFLOW_CATEGORY, addedNode);
592 defaultWorkflow.setDisplayName(name);
593 }
594
595 if (DEFAULT_NEW_DOCUMENT_WORKFLOW_CATEGORY.equals(category)) {
596
597
598 if (addedNode.isNodeType("hippostd:publishable")) {
599 log.info("Added document '{}' is pusblishable so set status to preview.", addedNode.getPath());
600 addedNode.setProperty("hippostd:state", "unpublished");
601 addedNode.setProperty(HippoNodeType.HIPPO_AVAILABILITY, new String[] { "preview" });
602 } else {
603 log.info("Added document '{}' is not publishable so set status to live & preview directly.",
604 addedNode.getPath());
605 addedNode.setProperty(HippoNodeType.HIPPO_AVAILABILITY, new String[] { "live", "preview" });
606 }
607
608 if (addedNode.isNodeType("hippostd:publishableSummary")) {
609 addedNode.setProperty("hippostd:stateSummary", "new");
610 }
611 addedNode.getSession().save();
612 }
613 return added;
614 } else {
615 throw new WorkflowException(
616 "Can't create folder " + name + " [" + nodeTypeName + "] in the folder " + folderNode.getPath()
617 + ", because there is no FolderWorkflow possible on the folder node: " + wf);
618 }
619 } catch (RemoteException e) {
620 throw new WorkflowException(e.toString(), e);
621 }
622 }
623 }