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 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.
169      * @param uuid UUID of the content
170      * @param repository Content repository name.
171      * @param fallbackHandle Optional fallback content handle.
172      * @param nodeDataName Content node data name for binary data.
173      * @param extension Optional link extension.
174      * @param anchor Optional link anchor.
175      * @param parameters Optional link parameters.
176      * @return Link pointing to the content denoted by uuid and repository. Link is created using all provided optional values if present.
177      * @throws LinkException
178      */
179     public static Link createLink(String uuid, String repository, String fallbackHandle, String nodeDataName, String extension, String anchor, String parameters) throws LinkException {
180         repository = StringUtils.defaultIfEmpty(repository, ContentRepository.WEBSITE);
181         Link link;
182         try {
183             link = createLink(repository, uuid);
184         } catch (LinkException e) {
185             try {
186                 final Content node = MgnlContext.getHierarchyManager(repository).getContent(fallbackHandle);
187                 link = createLink(node);
188             } catch (PathNotFoundException e1) {
189                 log.warn("Can't find node with uuid " + uuid + " or handle " + fallbackHandle + " in repository " + repository);
190                 link = new Link();
191             } catch (RepositoryException e1) {
192                 throw new LinkException("Failed to create link with uuid " + uuid + " and repository " + repository + " and fallbackhandle " + fallbackHandle, e);
193             }
194         }
195         link.setFallbackHandle(fallbackHandle);
196         link.setNodeDataName(nodeDataName);
197         link.setExtension(extension);
198         link.setAnchor(anchor);
199         link.setParameters(parameters);
200 
201         return link;
202     }
203 
204     /**
205      * Parses UUID link pattern string and converts it into a Link object.
206      * @param uuidLink String containing reference to content as a UUID link pattern.
207      * @return Link to content referenced in the provided text.
208      */
209     public static Link parseUUIDLink(String uuidLink) throws LinkException{
210         Matcher matcher = LinkFactory.UUID_PATTERN.matcher(uuidLink);
211         if(matcher.matches()){
212             return createLink(matcher.group(1), matcher.group(2), matcher.group(5), matcher.group(7), matcher.group(8), matcher.group(10), matcher.group(12));
213         }
214         else{
215             throw new LinkException("can't parse [ " + uuidLink + "]");
216         }
217     }
218 
219     /**
220      * Parses provided URI to the link.
221      * @param link URI representing path to piece of content
222      * @return Link pointing to the content represented by provided URI
223      */
224     public static Link parseLink(String link) throws LinkException{
225         // ignore context handle if existing
226         link = StringUtils.removeStart(link, MgnlContext.getContextPath());
227 
228         Matcher matcher = LinkFactory.LINK_PATTERN.matcher(link);
229         if(matcher.matches()){
230             String orgHandle = matcher.group(1);
231             orgHandle = I18nContentSupportFactory.getI18nSupport().toRawURI(orgHandle);
232             String repository = URI2RepositoryManager.getInstance().getRepository(orgHandle);
233             String handle = URI2RepositoryManager.getInstance().getHandle(orgHandle);
234             return createLink(repository, handle, matcher.group(3),matcher.group(5),matcher.group(7));
235         }
236         else{
237             throw new LinkException("can't parse [ " + link + "]");
238         }
239     }
240 
241     /**
242      * Converts provided Link to an UUID link pattern.
243      * @param link Link to convert.
244      * @return UUID link pattern representation of provided link.
245      */
246     public static String toPattern(Link link) {
247         return "${link:{"
248             + "uuid:{" + link.getUUID() + "},"
249             + "repository:{" + link.getRepository() + "},"
250             + "handle:{" + link.getHandle() + "}," // original handle represented by the uuid
251             + "nodeData:{" + StringUtils.defaultString(link.getNodeDataName()) + "}," // in case of binaries
252             + "extension:{" + StringUtils.defaultString(link.getExtension()) + "}" // the extension to use if no extension can be resolved otherwise
253             + "}}"
254             + (StringUtils.isNotEmpty(link.getAnchor())? "#" + link.getAnchor():"")
255             + (StringUtils.isNotEmpty(link.getParameters())? "?" + link.getParameters() : "");
256     }
257 
258     /**
259      * Pattern to find a magnolia formatted uuid link.
260      */
261     public static Pattern UUID_PATTERN = Pattern.compile(
262         "\\$\\{link:\\{uuid:\\{([^\\}]*)\\}," // the uuid of the node
263         + "repository:\\{([^\\}]*)\\},"
264         + "(workspace:\\{[^\\}]*\\},)?" // is not supported anymore
265         + "(path|handle):\\{([^\\}]*)\\}"        // fallback handle should not be used unless the uuid is invalid
266         + "(,nodeData:\\{([^\\}]*)\\}," // in case we point to a binary (node data has no uuid!)
267         + "extension:\\{([^\\}]*)\\})?" // the extension to be used in rendering
268         + "\\}\\}"  // the handle
269         + "(#([^\\?\"]*))?" // anchor
270         + "(\\?([^\"]*))?"); // parameters
271 
272 
273     /**
274      * Pattern to find a link.
275      */
276     public static final Pattern LINK_PATTERN = Pattern.compile(
277         "(/[^\\.\"#\\?]*)" + // the handle
278         "(\\.([\\w[^#\\?]]+))?" + // extension (if any)
279         "(#([^\\?\"]*))?" + // anchor
280         "(\\?([^\"]*))?" // parameters
281     );
282 }