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