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