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