View Javadoc

1   /**
2    * This file Copyright (c) 2009-2012 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.lang.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   */
66  public class LinkUtil {
67  
68      /**
69       * Pattern that matches external and mailto: links.
70       */
71      public static final Pattern EXTERNAL_LINK_PATTERN = Pattern.compile("^(\\w*://|mailto:|javascript:|tel:).*");
72  
73      public static final String DEFAULT_EXTENSION = "html";
74  
75      public static final String DEFAULT_REPOSITORY = RepositoryConstants.WEBSITE;
76  
77      /**
78       * Pattern to find a link.
79       */
80      public static final Pattern LINK_OR_IMAGE_PATTERN = Pattern.compile(
81              "(<(a|img|embed) " + // start <a or <img
82                      "[^>]*" +  // some attributes
83                      "(href|src)[ ]*=[ ]*\")" + // start href or src
84                      "([^\"]*)" + // the link
85                      "(\"" + // ending "
86                      "[^>]*" + // any attributes
87              ">)"); // end the tag
88  
89      /**
90       * Pattern to find a magnolia formatted uuid link.
91       */
92      public static Pattern UUID_PATTERN = Pattern.compile(
93          "\\$\\{link:\\{uuid:\\{([^\\}]*)\\}," // the uuid of the node
94          + "repository:\\{([^\\}]*)\\},"
95          + "(workspace:\\{[^\\}]*\\},)?" // is not supported anymore
96          + "(path|handle):\\{([^\\}]*)\\}"        // fallback handle should not be used unless the uuid is invalid
97          + "(,nodeData:\\{([^\\}]*)\\}," // in case we point to a binary (node data has no uuid!)
98          + "extension:\\{([^\\}]*)\\})?" // the extension to be used in rendering
99          + "\\}\\}"  // the handle
100         + "(#([^\\?\"]*))?" // anchor
101         + "(\\?([^\"]*))?"); // parameters
102 
103     /**
104      * Pattern to find a link.
105      */
106     public static final Pattern LINK_PATTERN = Pattern.compile(
107         "(/[^\\.\"#\\?]*)" + // the handle
108         "(\\.([\\w[^#\\?]]+))?" + // extension (if any)
109         "(#([^\\?\"]*))?" + // anchor
110         "(\\?([^\"]*))?" // parameters
111     );
112 
113     /**
114      * Logger.
115      */
116     private static final Logger log = LoggerFactory.getLogger(LinkUtil.class);
117 
118 
119     //-- conversions from UUID - singles
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      * Parses provided html and transforms all the links to the magnolia format. Used during storing.
139      * @param html html code with links to be converted
140      * @return html with changed hrefs
141      */
142     public static String convertAbsoluteLinksToUUIDs(String html) {
143         // get all link tags
144         Matcher matcher = LINK_OR_IMAGE_PATTERN.matcher(html);
145         StringBuffer res = new StringBuffer();
146         while (matcher.find()) {
147             final String href = matcher.group(4);
148             if (!isExternalLinkOrAnchor(href)) {
149                 try {
150                     Link link = parseLink(href);
151                     String linkStr = toPattern(link);
152                     linkStr = StringUtils.replace(linkStr, "\\", "\\\\");
153                     linkStr = StringUtils.replace(linkStr, "$", "\\$");
154                     matcher.appendReplacement(res, "$1" + linkStr + "$5");
155                 }
156                 catch (LinkException e) {
157                     // this is expected if the link is an absolute path to something else
158                     // than content stored in the repository
159                     matcher.appendReplacement(res, "$0");
160                     log.debug("can't parse link", e);
161                 }
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      * @param str Html with UUID links
176      * @param transformer Link transformer
177      * @return converted html with links as created by provided transformer.
178      * @see LinkTransformerManager
179      */
180     public static String convertLinksFromUUIDPattern(String str, LinkTransformer transformer) throws LinkException {
181         Matcher matcher = UUID_PATTERN.matcher(str);
182         StringBuffer res = new StringBuffer();
183         while (matcher.find()) {
184             Link link = createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
185             String replacement = transformer.transform(link);
186             // Replace "\" with "\\" and "$" with "\$" since Matcher.appendReplacement treats these characters specially
187             replacement = StringUtils.replace(replacement, "\\", "\\\\");
188             replacement = StringUtils.replace(replacement,"$", "\\$");
189             matcher.appendReplacement(res, replacement);
190         }
191         matcher.appendTail(res);
192         return res.toString();
193     }
194 
195     public static String convertLinksFromUUIDPattern(String str) throws LinkException {
196         LinkTransformer transformer = LinkTransformerManager.getInstance().getBrowserLink(null);
197         return convertLinksFromUUIDPattern(str, transformer);
198     }
199 
200     /**
201      * Determines if the given link is internal and relative.
202      */
203     public static boolean isInternalRelativeLink(String href) {
204         // TODO : this could definitely be improved
205         return !isExternalLinkOrAnchor(href) && !href.startsWith("/");
206     }
207 
208     /**
209      * Determines whether the given link is external link or anchor (i.e. returns true for all non translatable links).
210      */
211     public static boolean isExternalLinkOrAnchor(String href) {
212         return LinkUtil.EXTERNAL_LINK_PATTERN.matcher(href).matches() || href.startsWith("#");
213     }
214 
215     /**
216      * Make a absolute path relative. It adds ../ until the root is reached
217      * @param absolutePath absolute path
218      * @param url page to be relative to
219      * @return relative path
220      */
221     public static String makePathRelative(String url, String absolutePath){
222         String fromPath = StringUtils.substringBeforeLast(url, "/");
223         String toPath = StringUtils.substringBeforeLast(absolutePath, "/");
224 
225         // reference to parent folder
226         if (StringUtils.equals(fromPath, toPath) && StringUtils.endsWith(absolutePath, "/")) {
227             return ".";
228         }
229 
230         String[] fromDirectories = StringUtils.split(fromPath, "/");
231         String[] toDirectories = StringUtils.split(toPath, "/");
232 
233         int pos=0;
234         while(pos < fromDirectories.length && pos < toDirectories.length && fromDirectories[pos].equals(toDirectories[pos])){
235             pos++;
236         }
237 
238         StringBuilder rel = new StringBuilder();
239         for(int i=pos; i < fromDirectories.length; i++ ){
240             rel.append("../");
241         }
242 
243         for(int i=pos; i < toDirectories.length; i++ ){
244             rel.append(toDirectories[i] + "/");
245         }
246 
247         rel.append(StringUtils.substringAfterLast(absolutePath, "/"));
248 
249         return rel.toString();
250     }
251 
252     /**
253      * Maps a path to a repository.
254      * @param path URI
255      * @return repository denoted by the provided URI.
256      */
257     public static String mapPathToRepository(String path) {
258         String workspaceName = getURI2RepositoryManager().getRepository(path);
259         if(StringUtils.isEmpty(workspaceName)){
260             workspaceName = DEFAULT_REPOSITORY;
261         }
262         return workspaceName;
263     }
264 
265     /**
266      * Appends a parameter to the given url, using ?, or & if there are already
267      * parameters in the given url. <strong>Warning:</strong> It does not
268      * <strong>replace</strong> an existing parameter with the same name.
269      */
270     public static void addParameter(StringBuffer uri, String name, String value) {
271         if (uri.indexOf("?") < 0) {
272             uri.append('?');
273         } else {
274             uri.append('&');
275         }
276         uri.append(name).append('=');
277         try {
278             uri.append(URLEncoder.encode(value, "UTF-8"));
279         } catch (UnsupportedEncodingException e) {
280             throw new RuntimeException("It seems your system does not support UTF-8 !?", e);
281         }
282     }
283 
284     /**
285      * Creates absolute link including context path for provided node data.
286      *
287      * @param nodedata
288      *            Node data to create link for.
289      * @return Absolute link to the provided node data.
290      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
291      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Property) instead.
292      */
293     public static String createAbsoluteLink(NodeData nodedata) throws LinkException {
294         if(nodedata == null || !nodedata.isExist()){
295             return null;
296         }
297         try {
298             if(nodedata.getType() != PropertyType.BINARY){
299                 return createAbsoluteLink(nodedata.getJCRProperty());
300             }
301             return createAbsoluteLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
302         } catch (RepositoryException e) {
303             throw new LinkException(e);
304         }
305     }
306 
307     /**
308      * Creates absolute link including context path for provided Property.
309      * @param property
310      * @return Absolute link to the provided Property.
311      * @throws LinkException
312      */
313     public static String createAbsoluteLink(Property property) throws LinkException {
314         if(property == null){
315             return null;
316         }
317         return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(property));
318     }
319 
320     /**
321      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
322      *
323      * @param uuid
324      *            UUID of content to create link to.
325      * @param workspaceName
326      *            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
339      *            content to create link to.
340      * @return Absolute link to the provided content.
341      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
342      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Node) instead.
343      */
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      * @param node
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      * @deprecated Since 5.0 use LinkUtil.createExternalLink(Node) instead.
366      */
367     public static String createExternalLink(Content content) {
368         if(content == null){
369             return null;
370         }
371         return createExternalLink(content.getJCRNode());
372     }
373 
374     /**
375      * Creates a complete url to access given node from external systems applying all the URI2Repository mappings and locales.
376      * @param node
377      */
378     public static String createExternalLink(Node node) {
379         if(node == null){
380             return null;
381         }
382         return LinkTransformerManager.getInstance().getCompleteUrl().transform(createLinkInstance(node));
383     }
384 
385     /**
386      * Creates link guessing best possible link format from current site and provided node.
387      *
388      * @param nodedata
389      *            Node data 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 LinkUtil.createLink(Node) instead.
393      */
394     public static String createLink(Content node) {
395         if(node == null){
396             return null;
397         }
398         return createLink(node.getJCRNode());
399     }
400 
401     /**
402      * Creates link guessing best possible link format from current site and provided node.
403      *
404      * @param node Node to create link for.
405      * @return Absolute link to the provided Node.
406      */
407     public static String createLink(Node node) {
408         if(node == null){
409             return null;
410         }
411             try {
412                 return LinkTransformerManager.getInstance().getBrowserLink(node.getPath()).transform(createLinkInstance(node));
413             } catch (RepositoryException e) {
414                 log.debug(e.getMessage(), e);
415             }
416             return null;
417     }
418 
419     /**
420      * Creates link guessing best possible link format from current site and provided node data.
421      *
422      * @param nodedata
423      *            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 LinkUtil.createAbsoluteLink(Property) instead.
427      */
428     public static String createLink(NodeData nodedata) throws LinkException {
429         if(nodedata == null || !nodedata.isExist()){
430             return null;
431         }
432         try {
433             if(nodedata.getType() != PropertyType.BINARY){
434                 return createLink(nodedata.getJCRProperty());
435             }
436             return createLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
437         } catch (RepositoryException e) {
438             throw new LinkException(e.getMessage(), e);
439         }
440     }
441 
442     /**
443      * Creates link guessing best possible link format from current site and provided Property.
444      *
445      * @param property Property to create link for.
446      * @return Absolute link to the provided Property.
447      * @throws LinkException
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
464      *            UUID of content to create link to.
465      * @param workspaceName
466      *            Name of the repository where content is located.
467      * @return Absolute link to the provided content.
468      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
469      */
470     public static String createLink(String workspaceName, String uuid) throws RepositoryException {
471         Node node = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
472         return createLink(node);
473     }
474 
475     /**
476      * Creates Link to provided Content.
477      * @param node
478      * @return Link to provided Content.
479      * @deprecated Since 5.0 use LinkUtil.createLinkInstance(Node) instead.
480      */
481     public static Link createLinkInstance(Content node) {
482         return createLinkInstance(node.getJCRNode());
483     }
484 
485     /**
486      * Creates Link to provided Node.
487      * @param node
488      * @return Link to provided Node.
489      */
490     protected static Link createLinkInstance(Node node) {
491         return new Link(node);
492     }
493 
494     /**
495      * Creates Link to provided NodeData.
496      * @param nodeData
497      * @return Link to provided NodeData.
498      * @throws LinkException
499      * @deprecated Since 5.0 use LinkUtil.createLinkInstance(Property) instead.
500      */
501     public static Link createLinkInstance(NodeData nodeData) throws LinkException{
502         try {
503             if(nodeData.getType() != PropertyType.BINARY){
504                 return createLinkInstance(nodeData.getJCRProperty());
505             }
506             return createLinkInstance(MgnlContext.getJCRSession(nodeData.getHierarchyManager().getWorkspace().getName()).getNode(nodeData.getHandle()));
507         } catch (RepositoryException e) {
508             throw new LinkException("can't find node " + nodeData , e);
509         }
510     }
511 
512     public static Link createLinkInstance(Property property) throws LinkException{
513         return new Link(property);
514     }
515 
516     /**
517      * Creates link to the content denoted by repository and uuid.
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      * @param workspaceName Source repository for the content.
533      * @param path Path to the content of interest.
534      * @param extension Optional extension to be used in the link
535      * @param anchor Optional link anchor.
536      * @param parameters Optional link parameters.
537      * @return Link pointing to the content denoted by repository and path including extension, anchor and parameters if such were provided.
538      * @throws LinkException
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      * @param uuid UUID of the content
593      * @param workspaceName Content repository name.
594      * @param fallbackHandle Optional fallback content handle.
595      * @param nodeDataName Content node data name for binary data.
596      * @param extension Optional link extension.
597      * @param anchor Optional link anchor.
598      * @param parameters Optional link parameters.
599      * @return Link pointing to the content denoted by uuid and repository. Link is created using all provided optional values if present.
600      * @throws LinkException
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 {}", new Object[]{ 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 {}", new Object[]{ 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      * @param uuidLink String containing reference to content as a UUID link pattern.
633      * @return Link to content referenced in the provided text.
634      */
635     public static Link parseUUIDLink(String uuidLink) throws LinkException{
636         Matcher matcher = UUID_PATTERN.matcher(uuidLink);
637         if(matcher.matches()){
638             return createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
639         }
640         throw new LinkException("can't parse [ " + uuidLink + "]");
641     }
642 
643     /**
644      * Parses provided URI to the link.
645      * @param link URI representing path to piece of content
646      * @return Link pointing to the content represented by provided URI
647      */
648     public static Link parseLink(String link) throws LinkException{
649         // ignore context handle if existing
650         link = StringUtils.removeStart(link, MgnlContext.getContextPath());
651 
652         Matcher matcher = LINK_PATTERN.matcher(link);
653         if(matcher.matches()){
654             String orgHandle = matcher.group(1);
655             orgHandle = Components.getComponent(I18nContentSupport.class).toRawURI(orgHandle);
656             String workspaceName = getURI2RepositoryManager().getRepository(orgHandle);
657             String handle = getURI2RepositoryManager().getHandle(orgHandle);
658             return createLinkInstance(workspaceName, handle, matcher.group(3),matcher.group(5),matcher.group(7));
659         }
660         throw new LinkException("can't parse [ " + link + "]");
661     }
662 
663     /**
664      * Converts provided Link to an UUID link pattern.
665      * @param link Link to convert.
666      * @return UUID link pattern representation of provided link.
667      */
668     public static String toPattern(Link link) {
669         return "${link:{"
670             + "uuid:{" + link.getUUID() + "},"
671             + "repository:{" + link.getWorkspace() + "},"
672             + "path:{" + link.getPath() + "}," // original handle represented by the uuid
673             + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "}," // in case of binaries
674             + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}" // the extension to use if no extension can be resolved otherwise
675             + "}}"
676             + (StringUtils.isNotEmpty(link.getAnchor())? "#" + link.getAnchor():"")
677             + (StringUtils.isNotEmpty(link.getParameters())? "?" + link.getParameters() : "");
678     }
679 
680     private static URI2RepositoryManager getURI2RepositoryManager(){
681         return Components.getComponent(URI2RepositoryManager.class);
682     }
683 }