View Javadoc
1   /**
2    * This file Copyright (c) 2009-2016 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      * Transforms a uuid to a handle beginning with a /. This path is used to get the page from the repository.
121      * The editor needs this kind of links.
122      */
123     public static String convertUUIDtoHandle(String uuid, String workspaceName) throws LinkException {
124         return createLinkInstance(workspaceName, uuid).getPath();
125     }
126 
127     /**
128      * Transforms a uuid to an uri. It does not add the context path. In difference from {@link Link#getHandle()},
129      * this method will apply all uri to repository mappings as well as i18n.
130      */
131     public static String convertUUIDtoURI(String uuid, String workspaceName) throws LinkException {
132         return LinkTransformerManager.getInstance().getAbsolute(false).transform(createLinkInstance(workspaceName, uuid));
133     }
134 
135     //-- conversions to UUID - bulk
136     /**
137      * Parses provided html and transforms all the links to the magnolia format. Used during storing.
138      * @param html html code with links to be converted
139      * @return html with changed hrefs
140      */
141     public static String convertAbsoluteLinksToUUIDs(String html) {
142         if (html == null) {
143             return null;
144         }
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                 }
158                 catch (LinkException e) {
159                     // this is expected if the link is an absolute path to something else
160                     // than content stored in the repository
161                     matcher.appendReplacement(res, "$0");
162                     log.debug("can't parse link", e);
163                 }
164             }
165             else{
166                 matcher.appendReplacement(res, "$0");
167             }
168         }
169         matcher.appendTail(res);
170         return res.toString();
171     }
172 
173     //-- conversions from UUID - bulk
174 
175     /**
176      * Converts provided html with links in UUID pattern format to any other kind of links based on provided link transformer.
177      * @param str Html with UUID links
178      * @param transformer Link transformer
179      * @return converted html with links as created by provided transformer.
180      * @see LinkTransformerManager
181      */
182     public static String convertLinksFromUUIDPattern(String str, LinkTransformer transformer) throws LinkException {
183         if (str == null) {
184             return null;
185         }
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             // Replace "\" with "\\" and "$" with "\$" since Matcher.appendReplacement treats these characters specially
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      * Determines if the given link is internal and relative.
207      */
208     public static boolean isInternalRelativeLink(String href) {
209         // TODO : this could definitely be improved
210         return !isExternalLinkOrAnchor(href) && !href.startsWith("/");
211     }
212 
213     /**
214      * Determines whether the given link is external link or anchor (i.e. returns true for all non translatable links).
215      */
216     public static boolean isExternalLinkOrAnchor(String href) {
217         return LinkUtil.EXTERNAL_LINK_PATTERN.matcher(href).matches() || href.startsWith("#");
218     }
219 
220     /**
221      * Make a absolute path relative. It adds ../ until the root is reached
222      * @param absolutePath absolute path
223      * @param url page to be relative to
224      * @return relative path
225      */
226     public static String makePathRelative(String url, String absolutePath){
227         String fromPath = StringUtils.substringBeforeLast(url, "/");
228         String toPath = StringUtils.substringBeforeLast(absolutePath, "/");
229 
230         // reference to parent folder
231         if (StringUtils.equals(fromPath, toPath) && StringUtils.endsWith(absolutePath, "/")) {
232             return ".";
233         }
234 
235         String[] fromDirectories = StringUtils.split(fromPath, "/");
236         String[] toDirectories = StringUtils.split(toPath, "/");
237 
238         int pos=0;
239         while(pos < fromDirectories.length && pos < toDirectories.length && fromDirectories[pos].equals(toDirectories[pos])){
240             pos++;
241         }
242 
243         StringBuilder rel = new StringBuilder();
244         for(int i=pos; i < fromDirectories.length; i++ ){
245             rel.append("../");
246         }
247 
248         for(int i=pos; i < toDirectories.length; i++ ){
249             rel.append(toDirectories[i] + "/");
250         }
251 
252         rel.append(StringUtils.substringAfterLast(absolutePath, "/"));
253 
254         return rel.toString();
255     }
256 
257     /**
258      * Maps a path to a repository.
259      * @param path URI
260      * @return repository denoted by the provided URI.
261      */
262     public static String mapPathToRepository(String path) {
263         String workspaceName = getURI2RepositoryManager().getRepository(path);
264         if(StringUtils.isEmpty(workspaceName)){
265             workspaceName = DEFAULT_REPOSITORY;
266         }
267         return workspaceName;
268     }
269 
270     /**
271      * Appends a parameter to the given url, using ?, or & if there are already
272      * parameters in the given url. <strong>Warning:</strong> It does not
273      * <strong>replace</strong> an existing parameter with the same name.
274      */
275     public static void addParameter(StringBuffer uri, String name, String value) {
276         if (uri.indexOf("?") < 0) {
277             uri.append('?');
278         } else {
279             uri.append('&');
280         }
281         uri.append(name).append('=');
282         try {
283             uri.append(URLEncoder.encode(value, "UTF-8"));
284         } catch (UnsupportedEncodingException e) {
285             throw new RuntimeException("It seems your system does not support UTF-8 !?", e);
286         }
287     }
288 
289     /**
290      * Creates absolute link including context path for provided node data.
291      *
292      * @param nodedata Node data to create link for.
293      *
294      * @return Absolute link to the provided node data.
295      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
296      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Property) instead.
297      */
298     public static String createAbsoluteLink(NodeData nodedata) throws LinkException {
299         if(nodedata == null || !nodedata.isExist()){
300             return null;
301         }
302         try {
303             if(nodedata.getType() != PropertyType.BINARY){
304                 return createAbsoluteLink(nodedata.getJCRProperty());
305             }
306             return createAbsoluteLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
307         } catch (RepositoryException e) {
308             throw new LinkException(e);
309         }
310     }
311 
312     /**
313      * Creates absolute link including context path for provided Property.
314      *
315      * @return Absolute link to the provided Property.
316      */
317     public static String createAbsoluteLink(Property property) throws LinkException {
318         if(property == null){
319             return null;
320         }
321         return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(property));
322     }
323 
324     /**
325      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
326      *
327      * @param uuid UUID of content to create link to.
328      * @param workspaceName Name of the repository where content is located.
329      *
330      * @return Absolute link to the provided content.
331      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
332      */
333     public static String createAbsoluteLink(String workspaceName, String uuid) throws RepositoryException {
334         Node jcrNode = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
335         return createAbsoluteLink(jcrNode);
336     }
337 
338     /**
339      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
340      *
341      * @param content content to create link to.
342      *
343      * @return Absolute link to the provided content.
344      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
345      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Node) instead.
346      */
347     public static String createAbsoluteLink(Content content) {
348         if(content == null){
349             return null;
350         }
351         return createAbsoluteLink(content.getJCRNode());
352     }
353 
354     /**
355      * Creates absolute link including context path to the provided node and performing all URI2Repository mappings and applying locales.
356      *
357      * @return Absolute link to the provided content.
358      */
359     public static String createAbsoluteLink(Node node) {
360         if(node == null){
361             return null;
362         }
363         return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(node));
364     }
365 
366     /**
367      * Creates a complete url to access given content from external systems applying all the URI2Repository mappings and locales.
368      * @deprecated Since 5.0 use LinkUtil.createExternalLink(Node) instead.
369      */
370     public static String createExternalLink(Content content) {
371         if(content == null){
372             return null;
373         }
374         return createExternalLink(content.getJCRNode());
375     }
376 
377     /**
378      * Creates a complete url to access given node from external systems applying all the URI2Repository mappings and locales.
379      */
380     public static String createExternalLink(Node node) {
381         if(node == null){
382             return null;
383         }
384         return LinkTransformerManager.getInstance().getCompleteUrl().transform(createLinkInstance(node));
385     }
386 
387     /**
388      * Creates link guessing best possible link format from current site and provided node.
389      *
390      * @param nodedata Node data to create link for.
391      *
392      * @return Absolute link to the provided node data.
393      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
394      * @deprecated Since 5.0 use LinkUtil.createLink(Node) instead.
395      */
396     public static String createLink(Content node) {
397         if(node == null){
398             return null;
399         }
400         return createLink(node.getJCRNode());
401     }
402 
403     /**
404      * Creates link guessing best possible link format from current site and provided node.
405      *
406      * @param node Node to create link for.
407      * @return Absolute link to the provided Node.
408      */
409     public static String createLink(Node node) {
410         if(node == null){
411             return null;
412         }
413             try {
414                 return LinkTransformerManager.getInstance().getBrowserLink(node.getPath()).transform(createLinkInstance(node));
415             } catch (RepositoryException e) {
416                 log.debug(e.getMessage(), e);
417             }
418             return null;
419     }
420 
421     /**
422      * Creates link guessing best possible link format from current site and provided node data.
423      *
424      * @param nodedata Node data to create link for.
425      * @return Absolute link to the provided node data.
426      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
427      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Property) instead.
428      */
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      *
466      * @return Absolute link to the provided content.
467      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
468      */
469     public static String createLink(String workspaceName, String uuid) throws RepositoryException {
470         Node node = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
471         return createLink(node);
472     }
473 
474     /**
475      * Creates Link to provided Content.
476      *
477      * @return Link to provided Content.
478      * @deprecated Since 5.0 use LinkUtil.createLinkInstance(Node) instead.
479      */
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      *
498      * @deprecated Since 5.0 use LinkUtil.createLinkInstance(Property) instead.
499      */
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      * @param workspaceName Parent repository of the content of interest.
518      * @param uuid UUID of the content to create link to.
519      * @return link to the content with provided UUID.
520      */
521     public static Link createLinkInstance(String workspaceName, String uuid) throws LinkException {
522         try {
523             return new Link(MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid));
524         } catch (RepositoryException e) {
525             throw new LinkException("can't get node with uuid " + uuid + " and repository " + workspaceName);
526         }
527     }
528 
529     /**
530      * 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.
531      * @param workspaceName Source repository for the content.
532      * @param path Path to the content of interest.
533      * @param extension Optional extension to be used in the link
534      * @param anchor Optional link anchor.
535      * @param parameters Optional link parameters.
536      * @return Link pointing to the content denoted by repository and path including extension, anchor and parameters if such were provided.
537      */
538     public static Link createLinkInstance(String workspaceName, String path, String extension, String anchor, String parameters) throws LinkException {
539         Node node = null;
540         String fileName = null;
541         String nodeDataName = null;
542         Property property = null;
543         try {
544             Session session = MgnlContext.getJCRSession(workspaceName);
545 
546             boolean exists = false;
547             try {
548                 PathParser.checkFormat(path);
549             } catch (MalformedPathException e) {
550                 // 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.
551             }
552             exists = session.itemExists(path) && !session.propertyExists(path);
553             if (exists) {
554                 node = session.getNode(path);
555             }
556             if (node == null) {
557                 if(session.nodeExists(path)){
558                     node = session.getNode(path);
559                 }
560                 if (node != null && node.isNodeType(NodeTypes.Resource.NAME) && node.hasProperty("fileName")) {
561                     fileName = node.getProperty("fileName").getString();
562                 }
563                 if (session.propertyExists(path)) {
564                     nodeDataName = StringUtils.substringAfterLast(path, "/");
565                     path = StringUtils.substringBeforeLast(path, "/");
566                     property = node.getProperty(nodeDataName);
567                 }
568             }
569             if (node == null) {
570                 throw new LinkException("can't find node " + path + " in repository " + workspaceName);
571             }
572         } catch (RepositoryException e) {
573             throw new LinkException("can't get node with path " + path + " from repository " + workspaceName);
574         }
575 
576         Link link = new Link(node);
577         link.setAnchor(anchor);
578         link.setExtension(extension);
579         link.setParameters(parameters);
580         link.setFileName(fileName);
581         link.setPropertyName(nodeDataName);
582         link.setProperty(property);
583         link.setPath(path);
584         return link;
585     }
586 
587     /**
588      * 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,
589      * pointing to the non existing uuid so that broken link detection tools can find it.
590      * @param uuid UUID of the content
591      * @param workspaceName Content repository name.
592      * @param fallbackHandle Optional fallback content handle.
593      * @param nodeDataName Content node data name for binary data.
594      * @param extension Optional link extension.
595      * @param anchor Optional link anchor.
596      * @param parameters Optional link parameters.
597      * @return Link pointing to the content denoted by uuid and repository. Link is created using all provided optional values if present.
598      */
599     public static Link createLinkInstance(String uuid, String workspaceName, String fallbackHandle, String nodeDataName, String extension, String anchor, String parameters) throws LinkException {
600         final String defaultRepository = StringUtils.defaultIfEmpty(workspaceName, RepositoryConstants.WEBSITE);
601         Link link;
602         try {
603             link = createLinkInstance(defaultRepository, uuid);
604         } catch (LinkException e) {
605             try {
606                 final Node node = MgnlContext.getJCRSession(defaultRepository).getNode(fallbackHandle != null? fallbackHandle:"");
607                 link = createLinkInstance(node);
608             } catch (PathNotFoundException pnfe) {
609                 log.warn("Can't find node with uuid {} or handle {} in repository {}", uuid, fallbackHandle, defaultRepository);
610                 link = new Link();
611                 link.setUUID(uuid);
612             } catch (RepositoryException re) {
613                 log.warn("Can't find node with uuid {} or handle {} in repository {}", uuid, fallbackHandle, defaultRepository);
614                 link = new Link();
615                 link.setUUID(uuid);
616             }
617         }
618         link.setFallbackPath(fallbackHandle);
619         link.setPropertyName(nodeDataName);
620         link.setExtension(extension);
621         link.setAnchor(anchor);
622         link.setParameters(parameters);
623 
624         return link;
625     }
626 
627     /**
628      * Parses UUID link pattern string and converts it into a Link object.
629      * @param uuidLink String containing reference to content as a UUID link pattern.
630      * @return Link to content referenced in the provided text.
631      */
632     public static Link parseUUIDLink(String uuidLink) throws LinkException{
633         Matcher matcher = UUID_PATTERN.matcher(uuidLink);
634         if(matcher.matches()){
635             return createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
636         }
637         throw new LinkException("can't parse [ " + uuidLink + "]");
638     }
639 
640     /**
641      * Parses provided URI to the link.
642      * @param link URI representing path to piece of content
643      * @return Link pointing to the content represented by provided URI
644      */
645     public static Link parseLink(String link) throws LinkException{
646         // ignore context handle if existing
647         link = StringUtils.removeStart(link, MgnlContext.getContextPath());
648 
649         Matcher matcher = LINK_PATTERN.matcher(link);
650         if(matcher.matches()){
651             String orgHandle = matcher.group(1);
652             orgHandle = Components.getComponent(I18nContentSupport.class).toRawURI(orgHandle);
653             String workspaceName = getURI2RepositoryManager().getRepository(orgHandle);
654             String handle = getURI2RepositoryManager().getHandle(orgHandle);
655             return createLinkInstance(workspaceName, handle, matcher.group(3),matcher.group(5),matcher.group(7));
656         }
657         throw new LinkException("can't parse [ " + link + "]");
658     }
659 
660     /**
661      * Converts provided Link to an UUID link pattern.
662      * @param link Link to convert.
663      * @return UUID link pattern representation of provided link.
664      */
665     public static String toPattern(Link link) {
666         return "${link:{"
667             + "uuid:{" + link.getUUID() + "},"
668             + "repository:{" + link.getWorkspace() + "},"
669             + "path:{" + link.getPath() + "}," // original handle represented by the uuid
670             + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "}," // in case of binaries
671             + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}" // the extension to use if no extension can be resolved otherwise
672             + "}}"
673             + (StringUtils.isNotEmpty(link.getAnchor())? "#" + link.getAnchor():"")
674             + (StringUtils.isNotEmpty(link.getParameters())? "?" + link.getParameters() : "");
675     }
676 
677     private static URI2RepositoryManager getURI2RepositoryManager(){
678         return Components.getComponent(URI2RepositoryManager.class);
679     }
680 }