View Javadoc

1   /**
2    * This file Copyright (c) 2009-2011 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 java.util.regex.Matcher;
37  import java.util.regex.Pattern;
38  
39  import javax.jcr.RepositoryException;
40  import javax.jcr.PathNotFoundException;
41  
42  import org.apache.commons.lang.StringUtils;
43  
44  import info.magnolia.cms.beans.config.ContentRepository;
45  import info.magnolia.cms.beans.config.URI2RepositoryManager;
46  import info.magnolia.cms.core.Content;
47  import info.magnolia.cms.core.HierarchyManager;
48  import info.magnolia.cms.core.NodeData;
49  import info.magnolia.cms.i18n.I18nContentSupportFactory;
50  import info.magnolia.context.MgnlContext;
51  
52  /**
53   * Factory processing various input into the Link objects and back.
54   * For parsing html and converting multiple link instances on the fly use {@link LinkUtil}.
55   *
56   * @author had
57   * @version $Id:$
58   */
59  public class LinkFactory {
60      private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LinkFactory.class);
61  
62      /**
63       * Creates new link from the content node.
64       * @param node Target content for the link.
65       * @return Link pointing to the provided content.
66       */
67      public static Link createLink(Content node) {
68          return new Link(node);
69      }
70  
71      /**
72       * Creates new link from the node data.
73       * @param nodeData Target node data for the link.
74       * @return Link pointing to the provided node data.
75       */
76      public static Link createLink(NodeData nodeData) throws LinkException {
77          try {
78              return new Link(nodeData.getHierarchyManager().getName(), nodeData.getParent(), nodeData);
79          } catch (RepositoryException e) {
80              throw new LinkException("can't find node " + nodeData , e);
81          }
82      }
83  
84      /**
85       * Creates link to the content denoted by repository and uuid.
86       * @param repository Parent repository of the content of interest.
87       * @param uuid UUID of the content to create link to.
88       * @return link to the content with provided UUID.
89       */
90      public static Link createLink(String repository, String uuid) throws LinkException {
91          try {
92              return new Link(MgnlContext.getHierarchyManager(repository).getContentByUUID(uuid));
93          } catch (RepositoryException e) {
94              throw new LinkException("can't get node with uuid " + uuid + " and repository " + repository);
95          }
96      }
97  
98      /**
99       * 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.
100      * @param repository Source repository for the content.
101      * @param path Path to the content of interest.
102      * @param extension Optional extension to be used in the link
103      * @param anchor Optional link anchor.
104      * @param parameters Optional link parameters.
105      * @return Link pointing to the content denoted by repository and path including extension, anchor and parameters if such were provided.
106      * @throws LinkException
107      */
108     public static Link createLink(String repository, String path, String extension, String anchor, String parameters) throws LinkException {
109         Content node = null;
110         String fileName = null;
111         String nodeDataName = null;
112         NodeData nodeData = null;
113         try {
114             HierarchyManager hm = MgnlContext.getHierarchyManager(repository);
115             boolean exists = false;
116             try {
117                 // jackrabbit own path parser
118                 // TODO: rewrite this as Magnolia method or allow configuration of parser per JCR impl
119                 Class parser = Class.forName("org.apache.jackrabbit.spi.commons.conversion.PathParser");
120                 parser.getMethod("checkFormat", new Class[] {String.class}).invoke(null, new Object[] {path});
121             } catch (Exception e) {
122                 if ("org.apache.jackrabbit.spi.commons.conversion.MalformedPathException".equals(e.getClass().getName())) {
123                     // 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.
124                     exists = false;
125                 } else {
126                     // ignore - parser doesn't exists
127                 }
128             }
129             exists = hm.isExist(path) && !hm.isNodeData(path);
130             if (exists) {
131                 node = hm.getContent(path);
132             }
133             if (node == null) {
134                 // this is a binary containing the name at the end
135                 // this name is stored as an attribute but is not part of the handle
136                 if (hm.isNodeData(StringUtils.substringBeforeLast(path, "/"))) {
137                     fileName = StringUtils.substringAfterLast(path, "/");
138                     path = StringUtils.substringBeforeLast(path, "/");
139                 }
140 
141                 // link to the binary node data
142                 if (hm.isNodeData(path)) {
143                     nodeDataName = StringUtils.substringAfterLast(path, "/");
144                     path = StringUtils.substringBeforeLast(path, "/");
145                     node = hm.getContent(path);
146                     nodeData = node.getNodeData(nodeDataName);
147                 }
148             }
149             if (node == null) {
150                 throw new LinkException("can't find node " + path + " in repository " + repository);
151             }
152         } catch (RepositoryException e) {
153             throw new LinkException("can't get node with path " + path + " from repository " + repository);
154         }
155 
156         Link link = new Link(node);
157         link.setAnchor(anchor);
158         link.setExtension(extension);
159         link.setParameters(parameters);
160         link.setFileName(fileName);
161         link.setNodeDataName(nodeDataName);
162         link.setNodeData(nodeData);
163         link.setHandle(path);
164         return link;
165     }
166 
167     /**
168      * 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,
169      * pointing to the non existing uuid so that broken link detection tools can find it.
170      * @param uuid UUID of the content
171      * @param repository Content repository name.
172      * @param fallbackHandle Optional fallback content handle.
173      * @param nodeDataName Content node data name for binary data.
174      * @param extension Optional link extension.
175      * @param anchor Optional link anchor.
176      * @param parameters Optional link parameters.
177      * @return Link pointing to the content denoted by uuid and repository. Link is created using all provided optional values if present.
178      * @throws LinkException
179      */
180     public static Link createLink(String uuid, String repository, String fallbackHandle, String nodeDataName, String extension, String anchor, String parameters) throws LinkException {
181         final String defaultRepository = StringUtils.defaultIfEmpty(repository, ContentRepository.WEBSITE);
182         Link link;
183         try {
184             link = createLink(defaultRepository, uuid);
185         } catch (LinkException e) {
186             try {
187                 final Content node = MgnlContext.getHierarchyManager(defaultRepository).getContent(fallbackHandle != null? fallbackHandle:"");
188                 link = createLink(node);
189             } catch (PathNotFoundException pnfe) {
190                 log.warn("Can't find node with uuid {} or handle {} in repository {}", new Object[]{ uuid, fallbackHandle, defaultRepository});
191                 link = new Link();
192                 link.setUUID(uuid);
193             } catch (RepositoryException re) {
194                 log.warn("Can't find node with uuid {} or handle {} in repository {}", new Object[]{ uuid, fallbackHandle, defaultRepository});
195                 link = new Link();
196                 link.setUUID(uuid);
197             }
198         }
199         link.setFallbackHandle(fallbackHandle);
200         link.setNodeDataName(nodeDataName);
201         link.setExtension(extension);
202         link.setAnchor(anchor);
203         link.setParameters(parameters);
204 
205         return link;
206     }
207 
208     /**
209      * Parses UUID link pattern string and converts it into a Link object.
210      * @param uuidLink String containing reference to content as a UUID link pattern.
211      * @return Link to content referenced in the provided text.
212      */
213     public static Link parseUUIDLink(String uuidLink) throws LinkException{
214         Matcher matcher = LinkFactory.UUID_PATTERN.matcher(uuidLink);
215         if(matcher.matches()){
216             return createLink(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
217         }
218         else{
219             throw new LinkException("can't parse [ " + uuidLink + "]");
220         }
221     }
222 
223     /**
224      * Parses provided URI to the link.
225      * @param link URI representing path to piece of content
226      * @return Link pointing to the content represented by provided URI
227      */
228     public static Link parseLink(String link) throws LinkException{
229         // ignore context handle if existing
230         link = StringUtils.removeStart(link, MgnlContext.getContextPath());
231 
232         Matcher matcher = LinkFactory.LINK_PATTERN.matcher(link);
233         if(matcher.matches()){
234             String orgHandle = matcher.group(1);
235             orgHandle = I18nContentSupportFactory.getI18nSupport().toRawURI(orgHandle);
236             String repository = URI2RepositoryManager.getInstance().getRepository(orgHandle);
237             String handle = URI2RepositoryManager.getInstance().getHandle(orgHandle);
238             return createLink(repository, handle, matcher.group(3),matcher.group(5),matcher.group(7));
239         }
240         else{
241             throw new LinkException("can't parse [ " + link + "]");
242         }
243     }
244 
245     /**
246      * Converts provided Link to an UUID link pattern.
247      * @param link Link to convert.
248      * @return UUID link pattern representation of provided link.
249      */
250     public static String toPattern(Link link) {
251         return "${link:{"
252             + "uuid:{" + link.getUUID() + "},"
253             + "repository:{" + link.getRepository() + "},"
254             + "handle:{" + link.getHandle() + "}," // original handle represented by the uuid
255             + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "}," // in case of binaries
256             + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}" // the extension to use if no extension can be resolved otherwise
257             + "}}"
258             + (StringUtils.isNotEmpty(link.getAnchor())? "#" + link.getAnchor():"")
259             + (StringUtils.isNotEmpty(link.getParameters())? "?" + link.getParameters() : "");
260     }
261 
262     /**
263      * Pattern to find a magnolia formatted uuid link.
264      */
265     public static Pattern UUID_PATTERN = Pattern.compile(
266         "\\$\\{link:\\{uuid:\\{([^\\}]*)\\}," // the uuid of the node
267         + "repository:\\{([^\\}]*)\\},"
268         + "(workspace:\\{[^\\}]*\\},)?" // is not supported anymore
269         + "(path|handle):\\{([^\\}]*)\\}"        // fallback handle should not be used unless the uuid is invalid
270         + "(,nodeData:\\{([^\\}]*)\\}," // in case we point to a binary (node data has no uuid!)
271         + "extension:\\{([^\\}]*)\\})?" // the extension to be used in rendering
272         + "\\}\\}"  // the handle
273         + "(#([^\\?\"]*))?" // anchor
274         + "(\\?([^\"]*))?"); // parameters
275 
276 
277     /**
278      * Pattern to find a link.
279      */
280     public static final Pattern LINK_PATTERN = Pattern.compile(
281         "(/[^\\.\"#\\?]*)" + // the handle
282         "(\\.([\\w[^#\\?]]+))?" + // extension (if any)
283         "(#([^\\?\"]*))?" + // anchor
284         "(\\?([^\"]*))?" // parameters
285     );
286 }