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