View Javadoc

1   /**
2    * This file Copyright (c) 2009-2010 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.ContentRepository;
37  import info.magnolia.cms.beans.config.URI2RepositoryManager;
38  import info.magnolia.cms.core.Content;
39  import info.magnolia.cms.core.HierarchyManager;
40  import info.magnolia.cms.core.NodeData;
41  import info.magnolia.context.MgnlContext;
42  
43  import java.io.UnsupportedEncodingException;
44  import java.net.URLEncoder;
45  import java.util.regex.Matcher;
46  import java.util.regex.Pattern;
47  
48  import javax.jcr.RepositoryException;
49  
50  import org.apache.commons.lang.StringUtils;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  /**
55   * Utility methods for various operations necessary for link transformations and handling.
56   * @author had
57   *
58   */
59  public class LinkUtil {
60  
61      /**
62       * Pattern that matches external and mailto: links.
63       */
64      public static final Pattern EXTERNAL_LINK_PATTERN = Pattern.compile("^(\\w*://|mailto:|javascript:).*");
65  
66      public static final String DEFAULT_EXTENSION = "html";
67  
68      public static final String DEFAULT_REPOSITORY = ContentRepository.WEBSITE;
69  
70      // is this proxied or not? Tests says no.
71      //private static final LinkTransformerManager linkManager = LinkTransformerManager.getInstance();
72  
73      /**
74       * Pattern to find a link.
75       */
76      public static final Pattern LINK_OR_IMAGE_PATTERN = Pattern.compile(
77          "(<(a|img|embed) " + // start <a or <img
78          "[^>]*" +  // some attributes
79          "(href|src)[ ]*=[ ]*\")" + // start href or src
80          "([^\"]*)" + // the link
81          "(\"" + // ending "
82          "[^>]*" + // any attributes
83          ">)"); // end the tag
84  
85      /**
86       * Logger.
87       */
88      private static final Logger log = LoggerFactory.getLogger(LinkUtil.class);
89  
90  
91      //-- conversions from UUID - singles
92      /**
93       * Transforms a uuid to a handle beginning with a /. This path is used to get the page from the repository.
94       * The editor needs this kind of links.
95       */
96      public static String convertUUIDtoHandle(String uuid, String repository) throws LinkException {
97          return LinkFactory.createLink(repository, uuid).getHandle();
98      }
99  
100     /**
101      * Transforms a uuid to an uri. It does not add the context path. In difference from {@link Link#getHandle()},
102      * this method will apply all uri to repository mappings as well as i18n.
103      */
104     public static String convertUUIDtoURI(String uuid, String repository) throws LinkException {
105         return LinkTransformerManager.getInstance().getAbsolute(false).transform(LinkFactory.createLink(repository, uuid));
106     }
107 
108     //-- conversions to UUID - bulk
109     /**
110      * Parses provided html and transforms all the links to the magnolia format. Used during storing.
111      * @param html html code with links to be converted
112      * @return html with changed hrefs
113      */
114     public static String convertAbsoluteLinksToUUIDs(String html) {
115         // get all link tags
116         Matcher matcher = LINK_OR_IMAGE_PATTERN.matcher(html);
117         StringBuffer res = new StringBuffer();
118         while (matcher.find()) {
119             final String href = matcher.group(4);
120             if (!isExternalLinkOrAnchor(href)) {
121                 try {
122                     Link link = LinkFactory.parseLink(href);
123                     String linkStr = LinkFactory.toPattern(link);
124                     linkStr = StringUtils.replace(linkStr, "\\", "\\\\");
125                     linkStr = StringUtils.replace(linkStr, "$", "\\$");
126                     matcher.appendReplacement(res, "$1" + linkStr + "$5");
127                 }
128                 catch (LinkException e) {
129                     // this is expected if the link is an absolute path to something else
130                     // than content stored in the repository
131                     matcher.appendReplacement(res, "$0");
132                     log.debug("can't parse link", e);
133                 }
134             }
135             else{
136                 matcher.appendReplacement(res, "$0");
137             }
138         }
139         matcher.appendTail(res);
140         return res.toString();
141     }
142 
143     //-- conversions from UUID - bulk
144 
145     /**
146      * Converts provided html with links in UUID pattern format to any other kind of links based on provided link transformer.
147      * @param str Html with UUID links
148      * @param transformer Link transformer
149      * @return converted html with links as created by provided transformer.
150      * @see LinkTransformerManager
151      */
152     public static String convertLinksFromUUIDPattern(String str, LinkTransformer transformer) throws LinkException {
153         Matcher matcher = LinkFactory.UUID_PATTERN.matcher(str);
154         StringBuffer res = new StringBuffer();
155         while (matcher.find()) {
156             Link link = LinkFactory.createLink(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
157             String replacement = transformer.transform(link);
158             // Replace "\" with "\\" and "$" with "\$" since Matcher.appendReplacement treats these characters specially
159             replacement = StringUtils.replace(replacement, "\\", "\\\\");
160             replacement = StringUtils.replace(replacement,"$", "\\$");
161             matcher.appendReplacement(res, replacement);
162         }
163         matcher.appendTail(res);
164         return res.toString();
165     }
166 
167     public static String convertLinksFromUUIDPattern(String str) throws LinkException {
168         LinkTransformer transformer = LinkTransformerManager.getInstance().getBrowserLink(null);
169         return convertLinksFromUUIDPattern(str, transformer);
170     }
171     /**
172      * Determines if the given link is internal and relative.
173      */
174     public static boolean isInternalRelativeLink(String href) {
175         // TODO : this could definitely be improved
176         return !isExternalLinkOrAnchor(href) && !href.startsWith("/");
177     }
178 
179     /**
180      * Determines whether the given link is external link or anchor (i.e. returns true for all non translatable links).
181      */
182     public static boolean isExternalLinkOrAnchor(String href) {
183        return LinkUtil.EXTERNAL_LINK_PATTERN.matcher(href).matches() || href.startsWith("#");
184     }
185 
186     /**
187      * Make a absolute path relative. It adds ../ until the root is reached
188      * @param absolutePath absolute path
189      * @param url page to be relative to
190      * @return relative path
191      */
192     public static String makePathRelative(String url, String absolutePath){
193         String fromPath = StringUtils.substringBeforeLast(url, "/");
194         String toPath = StringUtils.substringBeforeLast(absolutePath, "/");
195 
196         // reference to parent folder
197         if (StringUtils.equals(fromPath, toPath) && StringUtils.endsWith(absolutePath, "/")) {
198             return ".";
199         }
200 
201         String[] fromDirectories = StringUtils.split(fromPath, "/");
202         String[] toDirectories = StringUtils.split(toPath, "/");
203 
204         int pos=0;
205         while(pos < fromDirectories.length && pos < toDirectories.length && fromDirectories[pos].equals(toDirectories[pos])){
206             pos++;
207         }
208 
209         String rel = "";
210         for(int i=pos; i < fromDirectories.length; i++ ){
211             rel += "../";
212         }
213 
214         for(int i=pos; i < toDirectories.length; i++ ){
215             rel = rel + toDirectories[i] + "/";
216         }
217 
218         rel += StringUtils.substringAfterLast(absolutePath, "/");
219 
220         return rel;
221     }
222 
223     /**
224      * Maps a path to a repository.
225      * @param path URI
226      * @return repository denoted by the provided URI.
227      */
228     public static String mapPathToRepository(String path) {
229         String repository = URI2RepositoryManager.getInstance().getRepository(path);
230         if(StringUtils.isEmpty(repository)){
231             repository = DEFAULT_REPOSITORY;
232         }
233         return repository;
234     }
235 
236     /**
237      * Appends a parameter to the given url, using ?, or & if there are already
238      * parameters in the given url. <strong>Warning:</strong> It does not
239      * <strong>replace</strong> an existing parameter with the same name.
240      */
241     public static void addParameter(StringBuffer uri, String name, String value) {
242         if (uri.indexOf("?") < 0) {
243             uri.append('?');
244         } else {
245             uri.append('&');
246         }
247         uri.append(name).append('=');
248         try {
249             uri.append(URLEncoder.encode(value, "UTF-8"));
250         } catch (UnsupportedEncodingException e) {
251             throw new RuntimeException("It seems your system does not support UTF-8 !?", e);
252         }
253     }
254 
255     /**
256      * Creates absolute link including context path for provided node data.
257      * @param nodedata Node data to create link for.
258      * @return Absolute link to the provided node data.
259      * @see AbstractI18nContentSupport
260      */
261     public static String createAbsoluteLink(NodeData nodedata) throws LinkException {
262         if(nodedata == null || !nodedata.isExist()){
263             return null;
264         }
265         return LinkTransformerManager.getInstance().getAbsolute().transform(LinkFactory.createLink(nodedata));
266     }
267 
268     /**
269      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
270      * @param uuid UUID of content to create link to.
271      * @param repository Name of the repository where content is located.
272      * @return Absolute link to the provided content.
273      * @see AbstractI18nContentSupport
274      */
275     public static String createAbsoluteLink(String repository, String uuid) throws RepositoryException {
276         HierarchyManager hm = MgnlContext.getHierarchyManager(repository);
277         Content node = hm.getContentByUUID(uuid);
278         return createAbsoluteLink(node);
279     }
280 
281     /**
282      * Creates absolute link including context path to the provided content and performing all URI2Repository mappings and applying locales.
283      * @param content content to create link to.
284      * @return Absolute link to the provided content.
285      * @see AbstractI18nContentSupport
286      */
287     public static String createAbsoluteLink(Content content) {
288         if(content == null){
289             return null;
290         }
291         return LinkTransformerManager.getInstance().getAbsolute().transform(LinkFactory.createLink(content));
292     }
293 
294     /**
295      * Creates a complete url to access given content from external systems applying all the URI2Repository mappings and locales.
296      * @param content
297      * @return
298      */
299     public static String createExternalLink(Content content) {
300         if(content == null){
301             return null;
302         }
303         return LinkTransformerManager.getInstance().getCompleteUrl().transform(LinkFactory.createLink(content));
304     }
305 
306     /**
307      * Creates link guessing best possible link format from current site and provided node.
308      * @param nodedata Node data to create link for.
309      * @return Absolute link to the provided node data.
310      * @see AbstractI18nContentSupport
311      */
312     public static String createLink(Content node) {
313         if(node == null){
314             return null;
315         }
316         return LinkTransformerManager.getInstance().getBrowserLink(node.getHandle()).transform(LinkFactory.createLink(node));
317     }
318 
319     /**
320      * Creates link guessing best possible link format from current site and provided node data.
321      * @param nodedata Node data to create link for.
322      * @return Absolute link to the provided node data.
323      * @see AbstractI18nContentSupport
324      */
325     public static String createLink(NodeData nodedata) throws LinkException {
326         if(nodedata == null || !nodedata.isExist()){
327             return null;
328         }
329         try {
330             return LinkTransformerManager.getInstance().getBrowserLink(nodedata.getParent().getHandle()).transform(LinkFactory.createLink(nodedata));
331         } catch (RepositoryException e) {
332             throw new LinkException(e.getMessage(), e);
333         }
334     }
335 
336     /**
337      * Creates link guessing best possible link format from current site and provided content.
338      * @param uuid UUID of content to create link to.
339      * @param repository Name of the repository where content is located.
340      * @return Absolute link to the provided content.
341      * @see AbstractI18nContentSupport
342      */
343     public static String createLink(String repository, String uuid) throws RepositoryException {
344         HierarchyManager hm = MgnlContext.getHierarchyManager(repository);
345         Content node = hm.getContentByUUID(uuid);
346         return createLink(node);
347     }
348 }