View Javadoc
1   /**
2    * This file Copyright (c) 2009-2015 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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.regex.Matcher;
48  import java.util.regex.Pattern;
49  
50  import javax.jcr.Node;
51  import javax.jcr.PathNotFoundException;
52  import javax.jcr.Property;
53  import javax.jcr.PropertyType;
54  import javax.jcr.RepositoryException;
55  import javax.jcr.Session;
56  
57  import org.apache.commons.lang3.StringUtils;
58  import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
59  import org.apache.jackrabbit.spi.commons.conversion.PathParser;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  /**
64   * Utility methods for various operations necessary for link transformations and handling.
65   * This is actually a Business Facade providing an entry point to the link transformations.
66   * Hence it will be renamed to LinkManager (MAGNOLIA-4765) soon.
67   */
68  public class LinkUtil {
69  
70      /**
71       * Pattern that matches external and mailto: links.
72       */
73      public static final Pattern EXTERNAL_LINK_PATTERN = Pattern.compile("^(\\w*://|mailto:|javascript:|tel:).*");
74  
75      public static final String DEFAULT_EXTENSION = "html";
76  
77      public static final String DEFAULT_REPOSITORY = RepositoryConstants.WEBSITE;
78  
79      /**
80       * Pattern to find a link.
81       */
82      public static final Pattern LINK_OR_IMAGE_PATTERN = Pattern.compile(
83              "(<(a|img|embed) " + // start <a or <img
84                      "[^>]*" +  // some attributes
85                      "(href|src)[ ]*=[ ]*\")" + // start href or src
86                      "([^\"]*)" + // the link
87                      "(\"" + // ending "
88                      "[^>]*" + // any attributes
89                      ">)"); // end the tag
90  
91      /**
92       * Pattern to find a magnolia formatted uuid link.
93       */
94      public static Pattern UUID_PATTERN = Pattern.compile(
95              "\\$\\{link:\\{uuid:\\{([^\\}]*)\\}," // the uuid of the node
96                      + "repository:\\{([^\\}]*)\\},"
97                      + "(workspace:\\{[^\\}]*\\},)?" // is not supported anymore
98                      + "(path|handle):\\{([^\\}]*)\\}"        // fallback handle should not be used unless the uuid is invalid
99                      + "(,nodeData:\\{([^\\}]*)\\}," // in case we point to a binary (node data has no uuid!)
100                     + "extension:\\{([^\\}]*)\\})?" // the extension to be used in rendering
101                     + "\\}\\}"  // the handle
102                     + "(#([^\\?\"]*))?" // anchor
103                     + "(\\?([^\"]*))?"); // parameters
104 
105     /**
106      * Pattern to find a link.
107      */
108     public static final Pattern LINK_PATTERN = Pattern.compile(
109             "(/[^\\.\"#\\?]*)" + // the handle
110                     "(\\.([\\w[^#\\?]]+))?" + // extension (if any)
111                     "(#([^\\?\"]*))?" + // anchor
112                     "(\\?([^\"]*))?" // parameters
113     );
114 
115     private static final Logger log = LoggerFactory.getLogger(LinkUtil.class);
116 
117 
118     //-- conversions from UUID - singles
119 
120     /**
121      * Transforms a uuid to a handle beginning with a /. This path is used to get the page from the repository.
122      * The editor needs this kind of links.
123      */
124     public static String convertUUIDtoHandle(String uuid, String workspaceName) throws LinkException {
125         return createLinkInstance(workspaceName, uuid).getPath();
126     }
127 
128     /**
129      * Transforms a uuid to an uri. It does not add the context path. In difference from {@link Link#getHandle()},
130      * this method will apply all uri to repository mappings as well as i18n.
131      */
132     public static String convertUUIDtoURI(String uuid, String workspaceName) throws LinkException {
133         return LinkTransformerManager.getInstance().getAbsolute(false).transform(createLinkInstance(workspaceName, uuid));
134     }
135 
136     //-- conversions to UUID - bulk
137 
138     /**
139      * Parses provided html and transforms all the links to the magnolia format. Used during storing.
140      *
141      * @param html html code with links to be converted
142      * @return html with changed hrefs
143      */
144     public static String convertAbsoluteLinksToUUIDs(String html) {
145         // get all link tags
146         Matcher matcher = LINK_OR_IMAGE_PATTERN.matcher(html);
147         StringBuffer res = new StringBuffer();
148         while (matcher.find()) {
149             final String href = matcher.group(4);
150             if (!isExternalLinkOrAnchor(href)) {
151                 try {
152                     Link link = parseLink(href);
153                     String linkStr = toPattern(link);
154                     linkStr = StringUtils.replace(linkStr, "\\", "\\\\");
155                     linkStr = StringUtils.replace(linkStr, "$", "\\$");
156                     matcher.appendReplacement(res, "$1" + linkStr + "$5");
157                 } catch (LinkException e) {
158                     // this is expected if the link is an absolute path to something else
159                     // than content stored in the repository
160                     matcher.appendReplacement(res, "$0");
161                     log.debug("can't parse link", e);
162                 }
163             } else {
164                 matcher.appendReplacement(res, "$0");
165             }
166         }
167         matcher.appendTail(res);
168         return res.toString();
169     }
170 
171     //-- conversions from UUID - bulk
172 
173     /**
174      * Converts provided html with links in UUID pattern format to any other kind of links based on provided link transformer.
175      *
176      * @param str Html with UUID links
177      * @param transformer Link transformer
178      * @return converted html with links as created by provided transformer.
179      * @see LinkTransformerManager
180      */
181     public static String convertLinksFromUUIDPattern(String str, LinkTransformer transformer) throws LinkException {
182         Matcher matcher = UUID_PATTERN.matcher(str);
183         StringBuffer res = new StringBuffer();
184         while (matcher.find()) {
185             Link link = createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
186             String replacement = transformer.transform(link);
187             // Replace "\" with "\\" and "$" with "\$" since Matcher.appendReplacement treats these characters specially
188             replacement = StringUtils.replace(replacement, "\\", "\\\\");
189             replacement = StringUtils.replace(replacement, "$", "\\$");
190             matcher.appendReplacement(res, replacement);
191         }
192         matcher.appendTail(res);
193         return res.toString();
194     }
195 
196     public static String convertLinksFromUUIDPattern(String str) throws LinkException {
197         LinkTransformer transformer = LinkTransformerManager.getInstance().getBrowserLink(null);
198         return convertLinksFromUUIDPattern(str, transformer);
199     }
200 
201     /**
202      * Determines if the given link is internal and relative.
203      */
204     public static boolean isInternalRelativeLink(String href) {
205         // TODO : this could definitely be improved
206         return !isExternalLinkOrAnchor(href) && !href.startsWith("/");
207     }
208 
209     /**
210      * Determines whether the given link is external link or anchor (i.e. returns true for all non translatable links).
211      */
212     public static boolean isExternalLinkOrAnchor(String href) {
213         return LinkUtil.EXTERNAL_LINK_PATTERN.matcher(href).matches() || href.startsWith("#");
214     }
215 
216     /**
217      * Make a absolute path relative. It adds ../ until the root is reached
218      *
219      * @param absolutePath absolute path
220      * @param url page to be relative to
221      * @return relative path
222      */
223     public static String makePathRelative(String url, String absolutePath) {
224         String fromPath = StringUtils.substringBeforeLast(url, "/");
225         String toPath = StringUtils.substringBeforeLast(absolutePath, "/");
226 
227         // reference to parent folder
228         if (StringUtils.equals(fromPath, toPath) && StringUtils.endsWith(absolutePath, "/")) {
229             return ".";
230         }
231 
232         String[] fromDirectories = StringUtils.split(fromPath, "/");
233         String[] toDirectories = StringUtils.split(toPath, "/");
234 
235         int pos = 0;
236         while (pos < fromDirectories.length && pos < toDirectories.length && fromDirectories[pos].equals(toDirectories[pos])) {
237             pos++;
238         }
239 
240         StringBuilder rel = new StringBuilder();
241         for (int i = pos; i < fromDirectories.length; i++) {
242             rel.append("../");
243         }
244 
245         for (int i = pos; i < toDirectories.length; i++) {
246             rel.append(toDirectories[i]).append("/");
247         }
248 
249         rel.append(StringUtils.substringAfterLast(absolutePath, "/"));
250 
251         return rel.toString();
252     }
253 
254     /**
255      * Maps a path to a repository.
256      *
257      * @param path URI
258      * @return repository denoted by the provided URI.
259      */
260     public static String mapPathToRepository(String path) {
261         String workspaceName = getURI2RepositoryManager().getRepository(path);
262         if (StringUtils.isEmpty(workspaceName)) {
263             workspaceName = DEFAULT_REPOSITORY;
264         }
265         return workspaceName;
266     }
267 
268     /**
269      * Appends a parameter to the given url, using ?, or & if there are already
270      * parameters in the given url. <strong>Warning:</strong> It does not
271      * <strong>replace</strong> an existing parameter with the same name.
272      */
273     public static void addParameter(StringBuffer uri, String name, String value) {
274         if (uri.indexOf("?") < 0) {
275             uri.append('?');
276         } else {
277             uri.append('&');
278         }
279         uri.append(name).append('=');
280         try {
281             uri.append(URLEncoder.encode(value, "UTF-8"));
282         } catch (UnsupportedEncodingException e) {
283             throw new RuntimeException("It seems your system does not support UTF-8 !?", e);
284         }
285     }
286 
287     /**
288      * Creates absolute link including context path for provided node data.
289      *
290      * @param nodedata Node data to create link for.
291      * @return Absolute link to the provided node data.
292      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
293      * @deprecated since 5.0 use {@link #createAbsoluteLink(Property)} instead.
294      */
295     @Deprecated
296     public static String createAbsoluteLink(NodeData nodedata) throws LinkException {
297         if (nodedata == null || !nodedata.isExist()) {
298             return null;
299         }
300         try {
301             if (nodedata.getType() != PropertyType.BINARY) {
302                 return createAbsoluteLink(nodedata.getJCRProperty());
303             }
304             return createAbsoluteLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
305         } catch (RepositoryException e) {
306             throw new LinkException(e);
307         }
308     }
309 
310     /**
311      * Creates absolute link including context path for provided Property.
312      *
313      * @return Absolute link to the provided Property.
314      */
315     public static String createAbsoluteLink(Property property) throws LinkException {
316         if (property == null) {
317             return null;
318         }
319         return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(property));
320     }
321 
322     /**
323      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
324      *
325      * @param uuid UUID of content to create link to.
326      * @param workspaceName Name of the repository where content is located.
327      * @return Absolute link to the provided content.
328      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
329      */
330     public static String createAbsoluteLink(String workspaceName, String uuid) throws RepositoryException {
331         Node jcrNode = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
332         return createAbsoluteLink(jcrNode);
333     }
334 
335     /**
336      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
337      *
338      * @param content content to create link to.
339      * @return Absolute link to the provided content.
340      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
341      * @deprecated since 5.0 use {@link #createAbsoluteLink(Node)} instead.
342      */
343     @Deprecated
344     public static String createAbsoluteLink(Content content) {
345         if (content == null) {
346             return null;
347         }
348         return createAbsoluteLink(content.getJCRNode());
349     }
350 
351     /**
352      * Creates absolute link including context path to the provided node and performing all URI2Repository mappings and applying locales.
353      *
354      * @return Absolute link to the provided content.
355      */
356     public static String createAbsoluteLink(Node node) {
357         if (node == null) {
358             return null;
359         }
360         return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(node));
361     }
362 
363     /**
364      * Creates a complete url to access given content from external systems applying all the URI2Repository mappings and locales.
365      *
366      * @deprecated since 5.0 use {@link #createExternalLink(Node)} instead.
367      */
368     @Deprecated
369     public static String createExternalLink(Content content) {
370         if (content == null) {
371             return null;
372         }
373         return createExternalLink(content.getJCRNode());
374     }
375 
376     /**
377      * Creates a complete url to access given node from external systems applying all the URI2Repository mappings and locales.
378      */
379     public static String createExternalLink(Node node) {
380         if (node == null) {
381             return null;
382         }
383         return LinkTransformerManager.getInstance().getCompleteUrl().transform(createLinkInstance(node));
384     }
385 
386     /**
387      * Creates link guessing best possible link format from current site and provided node.
388      *
389      * @param node Node to create link for.
390      * @return Absolute link to the provided node data.
391      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
392      * @deprecated since 5.0 use {@link #createLink(Node)} instead.
393      */
394     @Deprecated
395     public static String createLink(Content node) {
396         if (node == null) {
397             return null;
398         }
399         return createLink(node.getJCRNode());
400     }
401 
402     /**
403      * Creates link guessing best possible link format from current site and provided node.
404      *
405      * @param node Node to create link for.
406      * @return Absolute link to the provided Node.
407      */
408     public static String createLink(Node node) {
409         if (node == null) {
410             return null;
411         }
412         try {
413             return LinkTransformerManager.getInstance().getBrowserLink(node.getPath()).transform(createLinkInstance(node));
414         } catch (RepositoryException e) {
415             log.debug(e.getMessage(), e);
416         }
417         return null;
418     }
419 
420     /**
421      * Creates link guessing best possible link format from current site and provided node data.
422      *
423      * @param nodedata Node data to create link for.
424      * @return Absolute link to the provided node data.
425      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
426      * @deprecated Since 5.0 use {@link #createAbsoluteLink(Property)} instead.
427      */
428     @Deprecated
429     public static String createLink(NodeData nodedata) throws LinkException {
430         if (nodedata == null || !nodedata.isExist()) {
431             return null;
432         }
433         try {
434             if (nodedata.getType() != PropertyType.BINARY) {
435                 return createLink(nodedata.getJCRProperty());
436             }
437             return createLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
438         } catch (RepositoryException e) {
439             throw new LinkException(e.getMessage(), e);
440         }
441     }
442 
443     /**
444      * Creates link guessing best possible link format from current site and provided Property.
445      *
446      * @param property Property to create link for.
447      * @return Absolute link to the provided Property.
448      */
449     public static String createLink(Property property) throws LinkException {
450         if (property == null) {
451             return null;
452         }
453         try {
454             return LinkTransformerManager.getInstance().getBrowserLink(property.getParent().getPath()).transform(createLinkInstance(property));
455         } catch (RepositoryException e) {
456             throw new LinkException(e.getMessage(), e);
457         }
458     }
459 
460     /**
461      * Creates link guessing best possible link format from current site and provided content.
462      *
463      * @param uuid UUID of content to create link to.
464      * @param workspaceName Name of the repository where content is located.
465      * @return Absolute link to the provided content.
466      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
467      */
468     public static String createLink(String workspaceName, String uuid) throws RepositoryException {
469         Node node = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
470         return createLink(node);
471     }
472 
473     /**
474      * Creates Link to provided Content.
475      *
476      * @return Link to provided Content.
477      * @deprecated since 5.0 use {@link #createLinkInstance(Node)} instead.
478      */
479     @Deprecated
480     public static Link createLinkInstance(Content node) {
481         return createLinkInstance(node.getJCRNode());
482     }
483 
484     /**
485      * Creates Link to provided Node.
486      *
487      * @return Link to provided Node.
488      */
489     public static Link createLinkInstance(Node node) {
490         return new Link(node);
491     }
492 
493     /**
494      * Creates Link to provided NodeData.
495      *
496      * @return Link to provided NodeData.
497      * @deprecated since 5.0 use {@link #createLinkInstance(Property)} instead.
498      */
499     @Deprecated
500     public static Link createLinkInstance(NodeData nodeData) throws LinkException {
501         try {
502             if (nodeData.getType() != PropertyType.BINARY) {
503                 return createLinkInstance(nodeData.getJCRProperty());
504             }
505             return createLinkInstance(MgnlContext.getJCRSession(nodeData.getHierarchyManager().getWorkspace().getName()).getNode(nodeData.getHandle()));
506         } catch (RepositoryException e) {
507             throw new LinkException("can't find node " + nodeData, e);
508         }
509     }
510 
511     public static Link createLinkInstance(Property property) throws LinkException {
512         return new Link(property);
513     }
514 
515     /**
516      * Creates link to the content denoted by repository and uuid.
517      *
518      * @param workspaceName Parent repository of the content of interest.
519      * @param uuid UUID of the content to create link to.
520      * @return link to the content with provided UUID.
521      */
522     public static Link createLinkInstance(String workspaceName, String uuid) throws LinkException {
523         try {
524             return new Link(MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid));
525         } catch (RepositoryException e) {
526             throw new LinkException("can't get node with uuid " + uuid + " and repository " + workspaceName);
527         }
528     }
529 
530     /**
531      * Creates link to the content identified by the repository and path. Link will use specified extension and will also contain the anchor and parameters if specified.
532      *
533      * @param workspaceName Source repository for the content.
534      * @param path Path to the content of interest.
535      * @param extension Optional extension to be used in the link
536      * @param anchor Optional link anchor.
537      * @param parameters Optional link parameters.
538      * @return Link pointing to the content denoted by repository and path including extension, anchor and parameters if such were provided.
539      */
540     public static Link createLinkInstance(String workspaceName, String path, String extension, String anchor, String parameters) throws LinkException {
541         Node node = null;
542         String fileName = null;
543         String nodeDataName = null;
544         Property property = null;
545         try {
546             Session session = MgnlContext.getJCRSession(workspaceName);
547 
548             boolean exists = false;
549             try {
550                 PathParser.checkFormat(path);
551             } catch (MalformedPathException e) {
552                 // we first check for path incl. the file name. While file name might not be necessarily part of the path, it might contain also non ascii chars. If that is the case, parsing exception will occur so we know that path with filename can't exist.
553             }
554             exists = session.itemExists(path) && !session.propertyExists(path);
555             if (exists) {
556                 node = session.getNode(path);
557             }
558             if (node == null) {
559                 if (session.nodeExists(path)) {
560                     node = session.getNode(path);
561                 }
562                 if (node != null && node.isNodeType(NodeTypes.Resource.NAME) && node.hasProperty("fileName")) {
563                     fileName = node.getProperty("fileName").getString();
564                 }
565                 if (session.propertyExists(path)) {
566                     nodeDataName = StringUtils.substringAfterLast(path, "/");
567                     path = StringUtils.substringBeforeLast(path, "/");
568                     property = node.getProperty(nodeDataName);
569                 }
570             }
571             if (node == null) {
572                 throw new LinkException("can't find node " + path + " in repository " + workspaceName);
573             }
574         } catch (RepositoryException e) {
575             throw new LinkException("can't get node with path " + path + " from repository " + workspaceName);
576         }
577 
578         Link link = new Link(node);
579         link.setAnchor(anchor);
580         link.setExtension(extension);
581         link.setParameters(parameters);
582         link.setFileName(fileName);
583         link.setPropertyName(nodeDataName);
584         link.setProperty(property);
585         link.setPath(path);
586         return link;
587     }
588 
589     /**
590      * Creates link based on provided parameters. Should the uuid be non existent or the fallback handle invalid, creates nonetheless an <em>"undefined"</em> {@link Link} object,
591      * pointing to the non existing uuid so that broken link detection tools can find it.
592      *
593      * @param uuid UUID of the content
594      * @param workspaceName Content repository name.
595      * @param fallbackHandle Optional fallback content handle.
596      * @param nodeDataName Content node data name for binary data.
597      * @param extension Optional link extension.
598      * @param anchor Optional link anchor.
599      * @param parameters Optional link parameters.
600      * @return Link pointing to the content denoted by uuid and repository. Link is created using all provided optional values if present.
601      */
602     public static Link createLinkInstance(String uuid, String workspaceName, String fallbackHandle, String nodeDataName, String extension, String anchor, String parameters) throws LinkException {
603         final String defaultRepository = StringUtils.defaultIfEmpty(workspaceName, RepositoryConstants.WEBSITE);
604         Link link;
605         try {
606             link = createLinkInstance(defaultRepository, uuid);
607         } catch (LinkException e) {
608             try {
609                 final Node node = MgnlContext.getJCRSession(defaultRepository).getNode(fallbackHandle != null ? fallbackHandle : "");
610                 link = createLinkInstance(node);
611             } catch (PathNotFoundException pnfe) {
612                 log.warn("Can't find node with uuid {} or handle {} in repository {}", uuid, fallbackHandle, defaultRepository);
613                 link = new Link();
614                 link.setUUID(uuid);
615             } catch (RepositoryException re) {
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             }
620         }
621         link.setFallbackPath(fallbackHandle);
622         link.setPropertyName(nodeDataName);
623         link.setExtension(extension);
624         link.setAnchor(anchor);
625         link.setParameters(parameters);
626 
627         return link;
628     }
629 
630     /**
631      * Parses UUID link pattern string and converts it into a Link object.
632      *
633      * @param uuidLink String containing reference to content as a UUID link pattern.
634      * @return Link to content referenced in the provided text.
635      */
636     public static Link parseUUIDLink(String uuidLink) throws LinkException {
637         Matcher matcher = UUID_PATTERN.matcher(uuidLink);
638         if (matcher.matches()) {
639             return createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
640         }
641         throw new LinkException("can't parse [ " + uuidLink + "]");
642     }
643 
644     /**
645      * Parses provided URI to the link.
646      *
647      * @param link URI representing path to piece of content
648      * @return Link pointing to the content represented by provided URI
649      */
650     public static Link parseLink(String link) throws LinkException {
651         // ignore context handle if existing
652         link = StringUtils.removeStart(link, MgnlContext.getContextPath());
653 
654         Matcher matcher = LINK_PATTERN.matcher(link);
655         if (matcher.matches()) {
656             String orgHandle = matcher.group(1);
657             orgHandle = Components.getComponent(I18nContentSupport.class).toRawURI(orgHandle);
658             String workspaceName = getURI2RepositoryManager().getRepository(orgHandle);
659             String handle = getURI2RepositoryManager().getHandle(orgHandle);
660             return createLinkInstance(workspaceName, handle, matcher.group(3), matcher.group(5), matcher.group(7));
661         }
662         throw new LinkException("can't parse [ " + link + "]");
663     }
664 
665     /**
666      * Converts provided Link to an UUID link pattern.
667      *
668      * @param link Link to convert.
669      * @return UUID link pattern representation of provided link.
670      */
671     public static String toPattern(Link link) {
672         return "${link:{"
673                 + "uuid:{" + link.getUUID() + "},"
674                 + "repository:{" + link.getWorkspace() + "},"
675                 + "path:{" + link.getPath() + "}," // original handle represented by the uuid
676                 + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "}," // in case of binaries
677                 + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}" // the extension to use if no extension can be resolved otherwise
678                 + "}}"
679                 + (StringUtils.isNotEmpty(link.getAnchor()) ? "#" + link.getAnchor() : "")
680                 + (StringUtils.isNotEmpty(link.getParameters()) ? "?" + link.getParameters() : "");
681     }
682 
683     private static URI2RepositoryManager getURI2RepositoryManager() {
684         return Components.getComponent(URI2RepositoryManager.class);
685     }
686 }