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.link;
35
36 import info.magnolia.cms.beans.config.URI2RepositoryManager;
37 import info.magnolia.cms.core.Content;
38 import info.magnolia.cms.core.NodeData;
39 import info.magnolia.cms.i18n.I18nContentSupport;
40 import info.magnolia.context.MgnlContext;
41 import info.magnolia.jcr.util.NodeTypes;
42 import info.magnolia.objectfactory.Components;
43 import info.magnolia.repository.RepositoryConstants;
44
45 import java.io.UnsupportedEncodingException;
46 import java.net.URLEncoder;
47 import java.util.Calendar;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
50
51 import javax.jcr.Node;
52 import javax.jcr.PathNotFoundException;
53 import javax.jcr.Property;
54 import javax.jcr.PropertyType;
55 import javax.jcr.RepositoryException;
56 import javax.jcr.Session;
57
58 import org.apache.commons.lang3.StringUtils;
59 import org.apache.commons.lang3.time.FastDateFormat;
60 import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
61 import org.apache.jackrabbit.spi.commons.conversion.PathParser;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65
66
67
68
69
70 public class LinkUtil {
71
72 private static final FastDateFormat FINGERPRINT_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd-HH-mm-ss");
73
74
75
76
77 public static final Pattern EXTERNAL_LINK_PATTERN = Pattern.compile("^(\\w*://|mailto:|javascript:|tel:).*");
78
79 public static final String DEFAULT_EXTENSION = "html";
80
81 public static final String DEFAULT_REPOSITORY = RepositoryConstants.WEBSITE;
82
83
84
85
86 public static final Pattern LINK_OR_IMAGE_PATTERN = Pattern.compile(
87 "(<(a|img|embed) " +
88 "[^>]*" +
89 "(href|src)[ ]*=[ ]*\")" +
90 "([^\"]*)" +
91 "(\"" +
92 "[^>]*" +
93 ">)");
94
95
96
97
98 public static Pattern UUID_PATTERN = Pattern.compile(
99 "\\$\\{link:\\{uuid:\\{([^\\}]*)\\},"
100 + "repository:\\{([^\\}]*)\\},"
101 + "(workspace:\\{[^\\}]*\\},)?"
102 + "(path|handle):\\{([^\\}]*)\\}"
103 + "(,nodeData:\\{([^\\}]*)\\},"
104 + "extension:\\{([^\\}]*)\\})?"
105 + "\\}\\}"
106 + "(#([^\\?\"]*))?"
107 + "(\\?([^\"]*))?");
108
109
110
111
112 public static final Pattern LINK_PATTERN = Pattern.compile(
113 "(/[^\\.\"#\\?]*)" +
114 "(\\.([\\w[^#\\?]]+))?" +
115 "(#([^\\?\"]*))?" +
116 "(\\?([^\"]*))?"
117 );
118
119
120
121
122 public static final Pattern IMAGE_PATTERN = Pattern.compile(
123 "(<img " +
124 "[^>]*" +
125 "src[ ]*=[ ]*\")" +
126 "([^\"]*)" +
127 "(\"" +
128 "[^>]*" +
129 ">)");
130
131 private static final Logger log = LoggerFactory.getLogger(LinkUtil.class);
132
133
134
135
136
137
138
139
140 public static String convertUUIDtoHandle(String uuid, String workspaceName) throws LinkException {
141 return createLinkInstance(workspaceName, uuid).getPath();
142 }
143
144
145
146
147
148 public static String convertUUIDtoURI(String uuid, String workspaceName) throws LinkException {
149 return LinkTransformerManager.getInstance().getAbsolute(false).transform(createLinkInstance(workspaceName, uuid));
150 }
151
152
153
154
155
156
157
158
159
160 public static String convertAbsoluteLinksToUUIDs(String html) {
161 if (html == null) {
162 return null;
163 }
164
165 Matcher matcher = LINK_OR_IMAGE_PATTERN.matcher(html);
166 StringBuffer res = new StringBuffer();
167 while (matcher.find()) {
168 final String href = matcher.group(4);
169 if (!isExternalLinkOrAnchor(href)) {
170 try {
171 Link link = parseLink(href);
172 String linkStr = toPattern(link);
173 linkStr = StringUtils.replace(linkStr, "\\", "\\\\");
174 linkStr = StringUtils.replace(linkStr, "$", "\\$");
175 matcher.appendReplacement(res, "$1" + linkStr + "$5");
176 } catch (LinkException e) {
177
178
179 matcher.appendReplacement(res, "$0");
180 log.debug("can't parse link", e);
181 }
182 } else {
183 matcher.appendReplacement(res, "$0");
184 }
185 }
186 matcher.appendTail(res);
187 return res.toString();
188 }
189
190
191
192
193
194
195
196
197
198
199
200 public static String convertLinksFromUUIDPattern(String str, LinkTransformer transformer) throws LinkException {
201 if (str == null) {
202 return null;
203 }
204 StringBuffer res = new StringBuffer();
205
206 Matcher imageMatcher = IMAGE_PATTERN.matcher(str);
207 while (imageMatcher.find()) {
208 Matcher matcher = UUID_PATTERN.matcher(imageMatcher.group());
209 if (matcher.find()) {
210 Link link = createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
211 String replacement = transformer.transform(link);
212 try {
213 replacement = addFingerprintToLink(replacement, NodeTypes.LastModified.getLastModified(MgnlContext.getJCRSession(matcher.group(2)).getNodeByIdentifier(matcher.group(1))));
214 } catch (RepositoryException e) {
215 try {
216 final Node node = MgnlContext.getJCRSession(matcher.group(2)).getNode(matcher.group(5) != null ? matcher.group(5) : "");
217 replacement = addFingerprintToLink(replacement, NodeTypes.LastModified.getLastModified(node));
218 } catch (RepositoryException e2) {
219 log.warn("Can't find node with uuid {} or handle {} in repository {}. Figerprint to link was't added.", matcher.group(1), matcher.group(5), matcher.group(2), e2);
220 }
221 }
222
223 replacement = StringUtils.replace(replacement, "\\", "\\\\");
224 replacement = StringUtils.replace(replacement, "$", "\\$");
225 imageMatcher.appendReplacement(res, imageMatcher.group().replace(matcher.group(), replacement));
226 }
227 }
228 imageMatcher.appendTail(res);
229
230
231 Matcher matcher = UUID_PATTERN.matcher(res);
232 res = new StringBuffer();
233 while (matcher.find()) {
234 Link link = createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
235 String replacement = transformer.transform(link);
236
237 replacement = StringUtils.replace(replacement, "\\", "\\\\");
238 replacement = StringUtils.replace(replacement, "$", "\\$");
239 matcher.appendReplacement(res, replacement);
240 }
241 matcher.appendTail(res);
242 return res.toString();
243 }
244
245 public static String convertLinksFromUUIDPattern(String str) throws LinkException {
246 LinkTransformer transformer = LinkTransformerManager.getInstance().getBrowserLink(null);
247 return convertLinksFromUUIDPattern(str, transformer);
248 }
249
250
251
252
253 public static boolean isInternalRelativeLink(String href) {
254
255 return !isExternalLinkOrAnchor(href) && !href.startsWith("/");
256 }
257
258
259
260
261 public static boolean isExternalLinkOrAnchor(String href) {
262 return LinkUtil.EXTERNAL_LINK_PATTERN.matcher(href).matches() || href.startsWith("#");
263 }
264
265
266
267
268
269
270
271
272 public static String makePathRelative(String url, String absolutePath) {
273 String fromPath = StringUtils.substringBeforeLast(url, "/");
274 String toPath = StringUtils.substringBeforeLast(absolutePath, "/");
275
276
277 if (StringUtils.equals(fromPath, toPath) && StringUtils.endsWith(absolutePath, "/")) {
278 return ".";
279 }
280
281 String[] fromDirectories = StringUtils.split(fromPath, "/");
282 String[] toDirectories = StringUtils.split(toPath, "/");
283
284 int pos = 0;
285 while (pos < fromDirectories.length && pos < toDirectories.length && fromDirectories[pos].equals(toDirectories[pos])) {
286 pos++;
287 }
288
289 StringBuilder rel = new StringBuilder();
290 for (int i = pos; i < fromDirectories.length; i++) {
291 rel.append("../");
292 }
293
294 for (int i = pos; i < toDirectories.length; i++) {
295 rel.append(toDirectories[i]).append("/");
296 }
297
298 rel.append(StringUtils.substringAfterLast(absolutePath, "/"));
299
300 return rel.toString();
301 }
302
303
304
305
306
307
308
309 public static String mapPathToRepository(String path) {
310 String workspaceName = getURI2RepositoryManager().getRepository(path);
311 if (StringUtils.isEmpty(workspaceName)) {
312 workspaceName = DEFAULT_REPOSITORY;
313 }
314 return workspaceName;
315 }
316
317
318
319
320
321
322 public static void addParameter(StringBuffer uri, String name, String value) {
323 if (uri.indexOf("?") < 0) {
324 uri.append('?');
325 } else {
326 uri.append('&');
327 }
328 uri.append(name).append('=');
329 try {
330 uri.append(URLEncoder.encode(value, "UTF-8"));
331 } catch (UnsupportedEncodingException e) {
332 throw new RuntimeException("It seems your system does not support UTF-8 !?", e);
333 }
334 }
335
336
337
338
339
340
341
342
343
344 @Deprecated
345 public static String createAbsoluteLink(NodeData nodedata) throws LinkException {
346 if (nodedata == null || !nodedata.isExist()) {
347 return null;
348 }
349 try {
350 if (nodedata.getType() != PropertyType.BINARY) {
351 return createAbsoluteLink(nodedata.getJCRProperty());
352 }
353 return createAbsoluteLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
354 } catch (RepositoryException e) {
355 throw new LinkException(e);
356 }
357 }
358
359
360
361
362
363
364 public static String createAbsoluteLink(Property property) throws LinkException {
365 if (property == null) {
366 return null;
367 }
368 return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(property));
369 }
370
371
372
373
374
375
376
377
378
379 public static String createAbsoluteLink(String workspaceName, String uuid) throws RepositoryException {
380 Node jcrNode = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
381 return createAbsoluteLink(jcrNode);
382 }
383
384
385
386
387
388
389
390
391
392 @Deprecated
393 public static String createAbsoluteLink(Content content) {
394 if (content == null) {
395 return null;
396 }
397 return createAbsoluteLink(content.getJCRNode());
398 }
399
400
401
402
403
404
405 public static String createAbsoluteLink(Node node) {
406 if (node == null) {
407 return null;
408 }
409 return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(node));
410 }
411
412
413
414
415
416
417 @Deprecated
418 public static String createExternalLink(Content content) {
419 if (content == null) {
420 return null;
421 }
422 return createExternalLink(content.getJCRNode());
423 }
424
425
426
427
428 public static String createExternalLink(Node node) {
429 if (node == null) {
430 return null;
431 }
432 return LinkTransformerManager.getInstance().getCompleteUrl().transform(createLinkInstance(node));
433 }
434
435
436
437
438
439
440
441
442
443 @Deprecated
444 public static String createLink(Content node) {
445 if (node == null) {
446 return null;
447 }
448 return createLink(node.getJCRNode());
449 }
450
451
452
453
454
455
456
457 public static String createLink(Node node) {
458 if (node == null) {
459 return null;
460 }
461 try {
462 return LinkTransformerManager.getInstance().getBrowserLink(node.getPath()).transform(createLinkInstance(node));
463 } catch (RepositoryException e) {
464 log.debug(e.getMessage(), e);
465 }
466 return null;
467 }
468
469
470
471
472
473
474
475
476
477 @Deprecated
478 public static String createLink(NodeData nodedata) throws LinkException {
479 if (nodedata == null || !nodedata.isExist()) {
480 return null;
481 }
482 try {
483 if (nodedata.getType() != PropertyType.BINARY) {
484 return createLink(nodedata.getJCRProperty());
485 }
486 return createLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
487 } catch (RepositoryException e) {
488 throw new LinkException(e.getMessage(), e);
489 }
490 }
491
492
493
494
495
496
497
498 public static String createLink(Property property) throws LinkException {
499 if (property == null) {
500 return null;
501 }
502 try {
503 return LinkTransformerManager.getInstance().getBrowserLink(property.getParent().getPath()).transform(createLinkInstance(property));
504 } catch (RepositoryException e) {
505 throw new LinkException(e.getMessage(), e);
506 }
507 }
508
509
510
511
512
513
514
515
516
517 public static String createLink(String workspaceName, String uuid) throws RepositoryException {
518 Node node = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
519 return createLink(node);
520 }
521
522
523
524
525
526
527
528 @Deprecated
529 public static Link createLinkInstance(Content node) {
530 return createLinkInstance(node.getJCRNode());
531 }
532
533
534
535
536
537
538 public static Link createLinkInstance(Node node) {
539 return new Link(node);
540 }
541
542
543
544
545
546
547
548 @Deprecated
549 public static Link createLinkInstance(NodeData nodeData) throws LinkException {
550 try {
551 if (nodeData.getType() != PropertyType.BINARY) {
552 return createLinkInstance(nodeData.getJCRProperty());
553 }
554 return createLinkInstance(MgnlContext.getJCRSession(nodeData.getHierarchyManager().getWorkspace().getName()).getNode(nodeData.getHandle()));
555 } catch (RepositoryException e) {
556 throw new LinkException("can't find node " + nodeData, e);
557 }
558 }
559
560 public static Link createLinkInstance(Property property) throws LinkException {
561 return new Link(property);
562 }
563
564
565
566
567
568
569
570
571 public static Link createLinkInstance(String workspaceName, String uuid) throws LinkException {
572 try {
573 return new Link(MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid));
574 } catch (RepositoryException e) {
575 throw new LinkException("can't get node with uuid " + uuid + " and repository " + workspaceName);
576 }
577 }
578
579
580
581
582
583
584
585
586
587
588
589 public static Link createLinkInstance(String workspaceName, String path, String extension, String anchor, String parameters) throws LinkException {
590 Node node = null;
591 String fileName = null;
592 String nodeDataName = null;
593 Property property = null;
594 try {
595 Session session = MgnlContext.getJCRSession(workspaceName);
596
597 boolean exists = false;
598 try {
599 PathParser.checkFormat(path);
600 } catch (MalformedPathException e) {
601
602 }
603 exists = session.itemExists(path) && !session.propertyExists(path);
604 if (exists) {
605 node = session.getNode(path);
606 }
607 if (node == null) {
608 if (session.nodeExists(path)) {
609 node = session.getNode(path);
610 }
611 if (node != null && node.isNodeType(NodeTypes.Resource.NAME) && node.hasProperty("fileName")) {
612 fileName = node.getProperty("fileName").getString();
613 }
614 if (session.propertyExists(path)) {
615 nodeDataName = StringUtils.substringAfterLast(path, "/");
616 path = StringUtils.substringBeforeLast(path, "/");
617 property = node.getProperty(nodeDataName);
618 }
619 }
620 if (node == null) {
621 throw new LinkException("can't find node " + path + " in repository " + workspaceName);
622 }
623 } catch (RepositoryException e) {
624 throw new LinkException("can't get node with path " + path + " from repository " + workspaceName);
625 }
626
627 Link link = new Link(node);
628 link.setAnchor(anchor);
629 link.setExtension(extension);
630 link.setParameters(parameters);
631 link.setFileName(fileName);
632 link.setPropertyName(nodeDataName);
633 link.setProperty(property);
634 link.setPath(path);
635 return link;
636 }
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651 public static Link createLinkInstance(String uuid, String workspaceName, String fallbackHandle, String nodeDataName, String extension, String anchor, String parameters) throws LinkException {
652 final String defaultRepository = StringUtils.defaultIfEmpty(workspaceName, RepositoryConstants.WEBSITE);
653 Link link;
654 try {
655 link = createLinkInstance(defaultRepository, uuid);
656 } catch (LinkException e) {
657 try {
658 final Node node = MgnlContext.getJCRSession(defaultRepository).getNode(fallbackHandle != null ? fallbackHandle : "");
659 link = createLinkInstance(node);
660 } catch (PathNotFoundException pnfe) {
661 log.warn("Can't find node with uuid {} or handle {} in repository {}", uuid, fallbackHandle, defaultRepository);
662 link = new Link();
663 link.setUUID(uuid);
664 } catch (RepositoryException re) {
665 log.warn("Can't find node with uuid {} or handle {} in repository {}", uuid, fallbackHandle, defaultRepository);
666 link = new Link();
667 link.setUUID(uuid);
668 }
669 }
670 link.setFallbackPath(fallbackHandle);
671 link.setPropertyName(nodeDataName);
672 link.setExtension(extension);
673 link.setAnchor(anchor);
674 link.setParameters(parameters);
675
676 return link;
677 }
678
679
680
681
682
683
684
685 public static Link parseUUIDLink(String uuidLink) throws LinkException {
686 Matcher matcher = UUID_PATTERN.matcher(uuidLink);
687 if (matcher.matches()) {
688 return createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
689 }
690 throw new LinkException("can't parse [ " + uuidLink + "]");
691 }
692
693
694
695
696
697
698
699 public static Link parseLink(String link) throws LinkException {
700
701 link = StringUtils.removeStart(link, MgnlContext.getContextPath());
702
703 Matcher matcher = LINK_PATTERN.matcher(link);
704 if (matcher.matches()) {
705 String orgHandle = matcher.group(1);
706 orgHandle = Components.getComponent(I18nContentSupport.class).toRawURI(orgHandle);
707 String workspaceName = getURI2RepositoryManager().getRepository(orgHandle);
708 String handle = getURI2RepositoryManager().getHandle(orgHandle);
709 return createLinkInstance(workspaceName, handle, matcher.group(3), matcher.group(5), matcher.group(7));
710 }
711 throw new LinkException("can't parse [ " + link + "]");
712 }
713
714
715
716
717
718
719
720 public static String toPattern(Link link) {
721 return "${link:{"
722 + "uuid:{" + link.getUUID() + "},"
723 + "repository:{" + link.getWorkspace() + "},"
724 + "path:{" + link.getPath() + "},"
725 + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "},"
726 + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}"
727 + "}}"
728 + (StringUtils.isNotEmpty(link.getAnchor()) ? "#" + link.getAnchor() : "")
729 + (StringUtils.isNotEmpty(link.getParameters()) ? "?" + link.getParameters() : "");
730 }
731
732 private static URI2RepositoryManager getURI2RepositoryManager() {
733 return Components.getComponent(URI2RepositoryManager.class);
734 }
735
736
737
738
739
740
741
742
743
744 public static String addFingerprintToLink(String link, Calendar lastModified) {
745 if (StringUtils.isBlank(link)) {
746 return null;
747 }
748 String fingerprintedLink = "";
749 if (lastModified == null) {
750 return link;
751 }
752
753 String fingerprint = FINGERPRINT_FORMAT.format(lastModified.getTime());
754
755
756 int lastDot = link.lastIndexOf('.');
757 int lastSlash = link.lastIndexOf('/');
758
759 if (lastDot > lastSlash && lastDot != -1) {
760 fingerprintedLink = link.substring(0, lastDot) + "." + fingerprint + link.substring(lastDot);
761 } else {
762
763 fingerprintedLink = link + "." + fingerprint;
764 }
765
766 return fingerprintedLink;
767 }
768
769
770
771
772
773 public static String removeFingerprintAndExtensionFromLink(String originalPath) {
774
775 String subPath = StringUtils.substringBeforeLast(originalPath, ".");
776
777 String fingerPrint = StringUtils.substringAfterLast(subPath, ".");
778 if (fingerPrint != null && fingerPrint.matches("\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2}")) {
779 return StringUtils.substringBeforeLast(subPath, ".");
780 } else {
781 return subPath;
782 }
783 }
784 }