View Javadoc

1   /**
2    * This file Copyright (c) 2009-2013 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   * 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     /**
116      * Logger.
117      */
118     private static final Logger log = LoggerFactory.getLogger(LinkUtil.class);
119 
120 
121     //-- conversions from UUID - singles
122     /**
123      * Transforms a uuid to a handle beginning with a /. This path is used to get the page from the repository.
124      * The editor needs this kind of links.
125      */
126     public static String convertUUIDtoHandle(String uuid, String workspaceName) throws LinkException {
127         return createLinkInstance(workspaceName, uuid).getPath();
128     }
129 
130     /**
131      * Transforms a uuid to an uri. It does not add the context path. In difference from {@link Link#getHandle()},
132      * this method will apply all uri to repository mappings as well as i18n.
133      */
134     public static String convertUUIDtoURI(String uuid, String workspaceName) throws LinkException {
135         return LinkTransformerManager.getInstance().getAbsolute(false).transform(createLinkInstance(workspaceName, uuid));
136     }
137 
138     //-- conversions to UUID - bulk
139     /**
140      * Parses provided html and transforms all the links to the magnolia format. Used during storing.
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                 }
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         Matcher matcher = UUID_PATTERN.matcher(str);
184         StringBuffer res = new StringBuffer();
185         while (matcher.find()) {
186             Link link = createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
187             String replacement = transformer.transform(link);
188             // Replace "\" with "\\" and "$" with "\$" since Matcher.appendReplacement treats these characters specially
189             replacement = StringUtils.replace(replacement, "\\", "\\\\");
190             replacement = StringUtils.replace(replacement,"$", "\\$");
191             matcher.appendReplacement(res, replacement);
192         }
193         matcher.appendTail(res);
194         return res.toString();
195     }
196 
197     public static String convertLinksFromUUIDPattern(String str) throws LinkException {
198         LinkTransformer transformer = LinkTransformerManager.getInstance().getBrowserLink(null);
199         return convertLinksFromUUIDPattern(str, transformer);
200     }
201 
202     /**
203      * Determines if the given link is internal and relative.
204      */
205     public static boolean isInternalRelativeLink(String href) {
206         // TODO : this could definitely be improved
207         return !isExternalLinkOrAnchor(href) && !href.startsWith("/");
208     }
209 
210     /**
211      * Determines whether the given link is external link or anchor (i.e. returns true for all non translatable links).
212      */
213     public static boolean isExternalLinkOrAnchor(String href) {
214         return LinkUtil.EXTERNAL_LINK_PATTERN.matcher(href).matches() || href.startsWith("#");
215     }
216 
217     /**
218      * Make a absolute path relative. It adds ../ until the root is reached
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] + "/");
247         }
248 
249         rel.append(StringUtils.substringAfterLast(absolutePath, "/"));
250 
251         return rel.toString();
252     }
253 
254     /**
255      * Maps a path to a repository.
256      * @param path URI
257      * @return repository denoted by the provided URI.
258      */
259     public static String mapPathToRepository(String path) {
260         String workspaceName = getURI2RepositoryManager().getRepository(path);
261         if(StringUtils.isEmpty(workspaceName)){
262             workspaceName = DEFAULT_REPOSITORY;
263         }
264         return workspaceName;
265     }
266 
267     /**
268      * Appends a parameter to the given url, using ?, or & if there are already
269      * parameters in the given url. <strong>Warning:</strong> It does not
270      * <strong>replace</strong> an existing parameter with the same name.
271      */
272     public static void addParameter(StringBuffer uri, String name, String value) {
273         if (uri.indexOf("?") < 0) {
274             uri.append('?');
275         } else {
276             uri.append('&');
277         }
278         uri.append(name).append('=');
279         try {
280             uri.append(URLEncoder.encode(value, "UTF-8"));
281         } catch (UnsupportedEncodingException e) {
282             throw new RuntimeException("It seems your system does not support UTF-8 !?", e);
283         }
284     }
285 
286     /**
287      * Creates absolute link including context path for provided node data.
288      *
289      * @param nodedata
290      *            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 LinkUtil.createAbsoluteLink(Property) instead.
294      */
295     public static String createAbsoluteLink(NodeData nodedata) throws LinkException {
296         if(nodedata == null || !nodedata.isExist()){
297             return null;
298         }
299         try {
300             if(nodedata.getType() != PropertyType.BINARY){
301                 return createAbsoluteLink(nodedata.getJCRProperty());
302             }
303             return createAbsoluteLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
304         } catch (RepositoryException e) {
305             throw new LinkException(e);
306         }
307     }
308 
309     /**
310      * Creates absolute link including context path for provided Property.
311      * @param property
312      * @return Absolute link to the provided Property.
313      * @throws LinkException
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
326      *            UUID of content to create link to.
327      * @param workspaceName
328      *            Name of the repository where content is located.
329      * @return Absolute link to the provided content.
330      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
331      */
332     public static String createAbsoluteLink(String workspaceName, String uuid) throws RepositoryException {
333         Node jcrNode = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
334         return createAbsoluteLink(jcrNode);
335     }
336 
337     /**
338      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
339      *
340      * @param content
341      *            content to create link to.
342      * @return Absolute link to the provided content.
343      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
344      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Node) instead.
345      */
346     public static String createAbsoluteLink(Content content) {
347         if(content == null){
348             return null;
349         }
350         return createAbsoluteLink(content.getJCRNode());
351     }
352 
353     /**
354      * Creates absolute link including context path to the provided node and performing all URI2Repository mappings and applying locales.
355      * @param node
356      * @return Absolute link to the provided content.
357      */
358     public static String createAbsoluteLink(Node node) {
359         if(node == null){
360             return null;
361         }
362         return LinkTransformerManager.getInstance().getAbsolute().transform(createLinkInstance(node));
363     }
364 
365     /**
366      * Creates a complete url to access given content from external systems applying all the URI2Repository mappings and locales.
367      * @deprecated Since 5.0 use LinkUtil.createExternalLink(Node) instead.
368      */
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      * @param node
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
391      *            Node data to create link for.
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
425      *            Node data to create link for.
426      * @return Absolute link to the provided node data.
427      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
428      * @deprecated Since 5.0 use LinkUtil.createAbsoluteLink(Property) instead.
429      */
430     public static String createLink(NodeData nodedata) throws LinkException {
431         if(nodedata == null || !nodedata.isExist()){
432             return null;
433         }
434         try {
435             if(nodedata.getType() != PropertyType.BINARY){
436                 return createLink(nodedata.getJCRProperty());
437             }
438             return createLink(MgnlContext.getJCRSession(nodedata.getHierarchyManager().getWorkspace().getName()).getNode(nodedata.getHandle()));
439         } catch (RepositoryException e) {
440             throw new LinkException(e.getMessage(), e);
441         }
442     }
443 
444     /**
445      * Creates link guessing best possible link format from current site and provided Property.
446      *
447      * @param property Property to create link for.
448      * @return Absolute link to the provided Property.
449      * @throws LinkException
450      */
451     public static String createLink(Property property) throws LinkException {
452         if(property == null){
453             return null;
454         }
455         try {
456             return LinkTransformerManager.getInstance().getBrowserLink(property.getParent().getPath()).transform(createLinkInstance(property));
457         } catch (RepositoryException e) {
458             throw new LinkException(e.getMessage(), e);
459         }
460     }
461 
462     /**
463      * Creates link guessing best possible link format from current site and provided content.
464      *
465      * @param uuid
466      *            UUID of content to create link to.
467      * @param workspaceName
468      *            Name of the repository where content is located.
469      * @return Absolute link to the provided content.
470      * @see info.magnolia.cms.i18n.AbstractI18nContentSupport
471      */
472     public static String createLink(String workspaceName, String uuid) throws RepositoryException {
473         Node node = MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid);
474         return createLink(node);
475     }
476 
477     /**
478      * Creates Link to provided Content.
479      * @param node
480      * @return Link to provided Content.
481      * @deprecated Since 5.0 use LinkUtil.createLinkInstance(Node) instead.
482      */
483     public static Link createLinkInstance(Content node) {
484         return createLinkInstance(node.getJCRNode());
485     }
486 
487     /**
488      * Creates Link to provided Node.
489      * @param node
490      * @return Link to provided Node.
491      */
492     public static Link createLinkInstance(Node node) {
493         return new Link(node);
494     }
495 
496     /**
497      * Creates Link to provided NodeData.
498      * @param nodeData
499      * @return Link to provided NodeData.
500      * @throws LinkException
501      * @deprecated Since 5.0 use LinkUtil.createLinkInstance(Property) instead.
502      */
503     public static Link createLinkInstance(NodeData nodeData) throws LinkException{
504         try {
505             if(nodeData.getType() != PropertyType.BINARY){
506                 return createLinkInstance(nodeData.getJCRProperty());
507             }
508             return createLinkInstance(MgnlContext.getJCRSession(nodeData.getHierarchyManager().getWorkspace().getName()).getNode(nodeData.getHandle()));
509         } catch (RepositoryException e) {
510             throw new LinkException("can't find node " + nodeData , e);
511         }
512     }
513 
514     public static Link createLinkInstance(Property property) throws LinkException{
515         return new Link(property);
516     }
517 
518     /**
519      * Creates link to the content denoted by repository and uuid.
520      * @param workspaceName Parent repository of the content of interest.
521      * @param uuid UUID of the content to create link to.
522      * @return link to the content with provided UUID.
523      */
524     public static Link createLinkInstance(String workspaceName, String uuid) throws LinkException {
525         try {
526             return new Link(MgnlContext.getJCRSession(workspaceName).getNodeByIdentifier(uuid));
527         } catch (RepositoryException e) {
528             throw new LinkException("can't get node with uuid " + uuid + " and repository " + workspaceName);
529         }
530     }
531 
532     /**
533      * 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.
534      * @param workspaceName Source repository for the content.
535      * @param path Path to the content of interest.
536      * @param extension Optional extension to be used in the link
537      * @param anchor Optional link anchor.
538      * @param parameters Optional link parameters.
539      * @return Link pointing to the content denoted by repository and path including extension, anchor and parameters if such were provided.
540      * @throws LinkException
541      */
542     public static Link createLinkInstance(String workspaceName, String path, String extension, String anchor, String parameters) throws LinkException {
543         Node node = null;
544         String fileName = null;
545         String nodeDataName = null;
546         Property property = null;
547         try {
548             Session session = MgnlContext.getJCRSession(workspaceName);
549 
550             boolean exists = false;
551             try {
552                 PathParser.checkFormat(path);
553             } catch (MalformedPathException e) {
554                 // 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.
555             }
556             exists = session.itemExists(path) && !session.propertyExists(path);
557             if (exists) {
558                 node = session.getNode(path);
559             }
560             if (node == null) {
561                 if(session.nodeExists(path)){
562                     node = session.getNode(path);
563                 }
564                 if (node != null && node.isNodeType(NodeTypes.Resource.NAME) && node.hasProperty("fileName")) {
565                     fileName = node.getProperty("fileName").getString();
566                 }
567                 if (session.propertyExists(path)) {
568                     nodeDataName = StringUtils.substringAfterLast(path, "/");
569                     path = StringUtils.substringBeforeLast(path, "/");
570                     property = node.getProperty(nodeDataName);
571                 }
572             }
573             if (node == null) {
574                 throw new LinkException("can't find node " + path + " in repository " + workspaceName);
575             }
576         } catch (RepositoryException e) {
577             throw new LinkException("can't get node with path " + path + " from repository " + workspaceName);
578         }
579 
580         Link link = new Link(node);
581         link.setAnchor(anchor);
582         link.setExtension(extension);
583         link.setParameters(parameters);
584         link.setFileName(fileName);
585         link.setPropertyName(nodeDataName);
586         link.setProperty(property);
587         link.setPath(path);
588         return link;
589     }
590 
591     /**
592      * 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,
593      * pointing to the non existing uuid so that broken link detection tools can find it.
594      * @param uuid UUID of the content
595      * @param workspaceName Content repository name.
596      * @param fallbackHandle Optional fallback content handle.
597      * @param nodeDataName Content node data name for binary data.
598      * @param extension Optional link extension.
599      * @param anchor Optional link anchor.
600      * @param parameters Optional link parameters.
601      * @return Link pointing to the content denoted by uuid and repository. Link is created using all provided optional values if present.
602      * @throws LinkException
603      */
604     public static Link createLinkInstance(String uuid, String workspaceName, String fallbackHandle, String nodeDataName, String extension, String anchor, String parameters) throws LinkException {
605         final String defaultRepository = StringUtils.defaultIfEmpty(workspaceName, RepositoryConstants.WEBSITE);
606         Link link;
607         try {
608             link = createLinkInstance(defaultRepository, uuid);
609         } catch (LinkException e) {
610             try {
611                 final Node node = MgnlContext.getJCRSession(defaultRepository).getNode(fallbackHandle != null? fallbackHandle:"");
612                 link = createLinkInstance(node);
613             } catch (PathNotFoundException pnfe) {
614                 log.warn("Can't find node with uuid {} or handle {} in repository {}", new Object[]{ uuid, fallbackHandle, defaultRepository});
615                 link = new Link();
616                 link.setUUID(uuid);
617             } catch (RepositoryException re) {
618                 log.warn("Can't find node with uuid {} or handle {} in repository {}", new Object[]{ uuid, fallbackHandle, defaultRepository});
619                 link = new Link();
620                 link.setUUID(uuid);
621             }
622         }
623         link.setFallbackPath(fallbackHandle);
624         link.setPropertyName(nodeDataName);
625         link.setExtension(extension);
626         link.setAnchor(anchor);
627         link.setParameters(parameters);
628 
629         return link;
630     }
631 
632     /**
633      * Parses UUID link pattern string and converts it into a Link object.
634      * @param uuidLink String containing reference to content as a UUID link pattern.
635      * @return Link to content referenced in the provided text.
636      */
637     public static Link parseUUIDLink(String uuidLink) throws LinkException{
638         Matcher matcher = UUID_PATTERN.matcher(uuidLink);
639         if(matcher.matches()){
640             return createLinkInstance(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
641         }
642         throw new LinkException("can't parse [ " + uuidLink + "]");
643     }
644 
645     /**
646      * Parses provided URI to the link.
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      * @param link Link to convert.
668      * @return UUID link pattern representation of provided link.
669      */
670     public static String toPattern(Link link) {
671         return "${link:{"
672             + "uuid:{" + link.getUUID() + "},"
673             + "repository:{" + link.getWorkspace() + "},"
674             + "path:{" + link.getPath() + "}," // original handle represented by the uuid
675             + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "}," // in case of binaries
676             + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}" // the extension to use if no extension can be resolved otherwise
677             + "}}"
678             + (StringUtils.isNotEmpty(link.getAnchor())? "#" + link.getAnchor():"")
679             + (StringUtils.isNotEmpty(link.getParameters())? "?" + link.getParameters() : "");
680     }
681 
682     private static URI2RepositoryManager getURI2RepositoryManager(){
683         return Components.getComponent(URI2RepositoryManager.class);
684     }
685 }