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