View Javadoc
1   /**
2    * This file Copyright (c) 2003-2017 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.cms.core;
35  
36  import info.magnolia.init.MagnoliaConfigurationProperties;
37  
38  import java.io.File;
39  
40  import javax.jcr.Node;
41  import javax.jcr.RepositoryException;
42  import javax.jcr.Session;
43  
44  import org.apache.commons.io.FilenameUtils;
45  import org.apache.commons.lang3.StringUtils;
46  import org.safehaus.uuid.UUIDGenerator;
47  
48  /**
49   * Utility class to retrieve files or directory used by Magnolia. Examples: cache directory, tmp files, ..
50   */
51  public final class Path {
52      /**
53       * New unlabeled nodes default name.
54       */
55      private static final String DEFAULT_UNTITLED_NODE_NAME = "untitled";
56  
57      private static final char SELECTOR_DELIMITER_CHAR = '~';
58  
59      public static final String SELECTOR_DELIMITER = Character.toString(SELECTOR_DELIMITER_CHAR);
60  
61      /**
62       * Utility class, don't instantiate.
63       */
64      private Path() {
65          // unused
66      }
67  
68      /**
69       * Gets the cache directory path (cms.cache.startdir) as set with Java options while startup or in web.xml.
70       *
71       * @return Cache directory path
72       */
73      public static String getCacheDirectoryPath() {
74          return getCacheDirectory().getAbsolutePath();
75      }
76  
77      public static File getCacheDirectory() {
78          String path = SystemProperty.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_CACHE_STARTDIR);
79          File dir = isAbsolute(path) ? new File(path) : new File(getAppRootDir(), path);
80          dir.mkdirs();
81          return dir;
82      }
83  
84      /**
85       * Gets the temporary directory path (cms.upload.tmpdir) as set with Java options while startup or in web.xml.
86       *
87       * @return Temporary directory path
88       */
89      public static String getTempDirectoryPath() {
90          return getTempDirectory().getAbsolutePath();
91      }
92  
93      public static File getTempDirectory() {
94          String path = SystemProperty.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_UPLOAD_TMPDIR);
95          File dir = isAbsolute(path) ? new File(path) : new File(getAppRootDir(), path);
96          dir.mkdirs();
97          return dir;
98      }
99  
100     /**
101      * Gets cms.exchange.history file location as set with Java options while startup or in web.xml.
102      *
103      * @return exchange history file location
104      */
105     public static String getHistoryFilePath() {
106         return getHistoryFile().getAbsolutePath();
107     }
108 
109     public static File getHistoryFile() {
110         String path = SystemProperty.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_EXCHANGE_HISTORY);
111         return isAbsolute(path) ? new File(path) : new File(getAppRootDir(), path);
112     }
113 
114     /**
115      * Gets repositories file location as set with Java options while startup or in web.xml.
116      *
117      * @return file location
118      */
119     public static String getRepositoriesConfigFilePath() {
120         return getRepositoriesConfigFile().getAbsolutePath();
121     }
122 
123     public static File getRepositoriesConfigFile() {
124         String path = SystemProperty.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_REPOSITORIES_CONFIG);
125         return isAbsolute(path) ? new File(path) : new File(getAppRootDir(), path);
126     }
127 
128     /**
129      * Gets the root directory for the magnolia web application.
130      *
131      * @return magnolia root dir
132      */
133     public static File getAppRootDir() {
134         return new File(SystemProperty.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_APP_ROOTDIR));
135     }
136 
137     /**
138      * Gets absolute filesystem path, adds application root if path is not absolute.
139      */
140     public static String getAbsoluteFileSystemPath(String path) {
141         if (isAbsolute(path)) {
142             return path;
143         }
144         // using the file() constructor will allow relative paths in the form ../../apps
145         return new File(getAppRootDir(), path).getAbsolutePath();
146     }
147 
148     /**
149      * @deprecated since 5.2.2, use {@link #getUniqueLabel(Session, String, String)} instead.
150      */
151     public static String getUniqueLabel(HierarchyManager hierarchyManager, String parent, String label) {
152         try {
153             label = Path.getUniqueLabel(hierarchyManager.getWorkspace().getSession(), parent, label);
154         } catch (RepositoryException e) {
155             label = UUIDGenerator.getInstance().generateRandomBasedUUID().toString();
156         }
157         return label;
158     }
159 
160     /**
161      * Checks if an item already exists with given name (label) in JCR, and returns a unique name;
162      * if necessary, an increment is appended to the name.
163      *
164      * <p>Given the following tree:</p>
165      * <pre>
166      * /
167      * ├── a
168      * ├── b
169      * └── b0
170      *
171      * Path.getUniqueLabel(session, "/", "a")  = "a0"
172      * Path.getUniqueLabel(session, "/", "b")  = "b1"
173      * Path.getUniqueLabel(session, "/", "b0") = "b1"
174      * Path.getUniqueLabel(session, "/", "c")  = "c"
175      * </pre>
176      */
177     public static String getUniqueLabel(Session session, String parentPath, String label) throws RepositoryException {
178         if (parentPath.equals("/")) {
179             parentPath = StringUtils.EMPTY;
180         }
181         while (session.itemExists(parentPath + "/" + label)) {
182             label = createUniqueName(label);
183         }
184         return label;
185     }
186 
187     /**
188      * Checks if an item already exists with given name (label) in JCR, and returns a unique name;
189      * if necessary, an increment is inserted into the name, before the given extension.
190      *
191      * <p>Given the following tree:</p>
192      * <pre>
193      * /
194      * ├── a.txt
195      * ├── b.txt
196      * └── b0.txt
197      *
198      * Path.getUniqueLabel(session, "/", "a.txt", "txt")  = "a0.txt"
199      * Path.getUniqueLabel(session, "/", "b.txt", "txt")  = "b1.txt"
200      * Path.getUniqueLabel(session, "/", "b0.txt", "txt") = "b1.txt"
201      * Path.getUniqueLabel(session, "/", "c.txt", "txt")  = "c.txt"
202      * Path.getUniqueLabel(session, "/", "a.foo", "txt")  = "a.foo"
203      * Path.getUniqueLabel(session, "/", "a.txt", "bar")  = "a.txt0"
204      * </pre>
205      */
206     public static String getUniqueLabel(Session session, String parentPath, String label, String extension) throws RepositoryException {
207         if (StringUtils.isNotEmpty(extension) && extension.equals(FilenameUtils.getExtension(label))) {
208             parentPath = "/".equals(parentPath) ? StringUtils.EMPTY : parentPath;
209             String basename = FilenameUtils.getBaseName(label);
210             String fullName = label;
211 
212             while (session.itemExists(parentPath + "/" + fullName)) {
213                 basename = createUniqueName(basename);
214                 fullName = basename + FilenameUtils.EXTENSION_SEPARATOR + extension;
215             }
216             return fullName;
217         }
218 
219         return Path.getUniqueLabel(session, parentPath, label);
220     }
221 
222     /**
223      * @deprecated since 5.2.2, use {@link #getUniqueLabel(Node, String)} instead.
224      */
225     public static String getUniqueLabel(Content parent, String label) {
226         return Path.getUniqueLabel(parent.getJCRNode(), label);
227     }
228 
229     public static String getUniqueLabel(Node parent, String label) {
230         try {
231             while (parent.hasNode(label) || parent.hasProperty(label)) {
232                 label = createUniqueName(label);
233             }
234         } catch (RepositoryException e) {
235             label = UUIDGenerator.getInstance().generateRandomBasedUUID().toString();
236         }
237         return label;
238     }
239 
240     public static boolean isAbsolute(String path) {
241 
242         if (path == null) {
243             return false;
244         }
245 
246         if (path.startsWith("/") || path.startsWith(File.separator)) {
247             return true;
248         }
249 
250         // windows c:
251         if (path.length() >= 3 && Character.isLetter(path.charAt(0)) && path.charAt(1) == ':') {
252             return true;
253         }
254 
255         return false;
256     }
257 
258     /**
259      * Replace illegal characters based on system property magnolia.ut8.enabled.
260      *
261      * @param label label to validate
262      * @return validated label
263      */
264     public static String getValidatedLabel(String label) {
265         String charset = StringUtils.EMPTY;
266         if ((SystemProperty.getBooleanProperty(MagnoliaConfigurationProperties.MAGNOLIA_UTF8_ENABLED))) {
267             charset = "UTF-8";
268         }
269         return getValidatedLabel(label, charset);
270 
271     }
272 
273     /**
274      * If charset equals <code>UTF-8</code>, replaces the following characters with a dash <code>-</code> :
275      * <p>
276      * Jackrabbit not allowed {@code 32: [ ] 91: [[] 93: []] 42: [*] 34: ["] 58 [:] 92: [\] 39 :[']}
277      * <p>
278      * URL not valid {@code 59: [;] 47: [/] 63: [?] 43: [+] 37: [%] 33: [!] 35:[#] 94: [^]}.
279      * <p>
280      * Otherwise, replaces illegal characters with a dash <code>-</code> except for {@code [_] [0-9], [A-Z], [a-z], [-], [_], [.]}.
281      * <p>
282      * Please notice that a valid label can not begin with dot or period <code>[.]</code>.
283      *
284      * @return a validated label for a node.
285      */
286     public static String getValidatedLabel(String label, String charset) {
287         if (StringUtils.isEmpty(label)) {
288             return DEFAULT_UNTITLED_NODE_NAME;
289         }
290         final StringBuilder newLabel = new StringBuilder(label.length());
291 
292         // label cannot begin with . (dot)
293         int ch = label.charAt(0);
294         if (!isCharValid(ch, charset) || ch == 46) {
295             newLabel.append("-");
296         } else {
297             newLabel.append(label.charAt(0));
298         }
299 
300         for (int i = 1; i < label.length(); i++) {
301             int charCode = label.charAt(i);
302             if (isCharValid(charCode, charset)) {
303                 newLabel.append(label.charAt(i));
304             } else {
305                 newLabel.append("-");
306             }
307         }
308         if (newLabel.length() == 0) {
309             newLabel.append(DEFAULT_UNTITLED_NODE_NAME);
310         }
311         return newLabel.toString();
312     }
313 
314     /**
315      * @param charCode char code
316      * @param charset charset (ex. UTF-8)
317      * @return true if char can be used as a content name
318      */
319     public static boolean isCharValid(int charCode, String charset) {
320         // TODO fgrilli: we now allow dots (.) in JR local names but actually in JR 2.0 other chars could be allowed as well
321         // (see http://www.day.com/specs/jcr/2.0/3_Repository_Model.html paragraph 2.2 and org.apache.jackrabbit.util.XMLChar.isValid()).
322         // Also, now that we're on java 6 and JR 2.0 should the check for the charset be dropped?
323 
324         // http://www.ietf.org/rfc/rfc1738.txt
325         // safe = "$" | "-" | "_" | "." | "+"
326         // extra = "!" | "*" | "'" | "(" | ")" | ","
327         // national = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`"
328         // punctuation = "<" | ">" | "#" | "%" | <">
329         // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "="
330 
331         if ("UTF-8".equals(charset)) {
332             // jackrabbit not allowed 32: [ ] 91: [[] 93: []] 42: [*] 34: ["] 46: [.] 58 [:] 92: [\] 39 :[']
333             // url not valid 59: [;] 47: [/] 63: [?] 43: [+] 37: [%] 33: [!] 35:[#]
334             if (charCode != 32
335                     && charCode != '['
336                     && charCode != ']'
337                     && charCode != '*'
338                     && charCode != '"'
339                     && charCode != ':'
340                     && charCode != 92
341                     && charCode != 39
342                     && charCode != ';'
343                     && charCode != '/'
344                     && charCode != '?'
345                     && charCode != '+'
346                     && charCode != '%'
347                     && charCode != '!'
348                     && charCode != '#'
349                     && charCode != '@'
350                     && charCode != '='
351                     && charCode != '&'
352                     && charCode != SELECTOR_DELIMITER_CHAR) {
353                 return true;
354             }
355         } else {
356             // charCodes: 48-57: [0-9]; 65-90: [A-Z]; 97-122: [a-z]; 45: [-]; 95:[_]
357             if (((charCode >= 48) && (charCode <= 57))
358                     || ((charCode >= 65) && (charCode <= 90))
359                     || ((charCode >= 97) && (charCode <= 122))
360                     || charCode == 45
361                     || charCode == 46
362                     || charCode == 95) {
363                 return true;
364             }
365 
366         }
367         return false;
368 
369     }
370 
371     private static String createUniqueName(String baseName) {
372         int pos;
373         for (pos = baseName.length() - 1; pos >= 0; pos--) {
374             char c = baseName.charAt(pos);
375             if (c < '0' || c > '9') {
376                 break;
377             }
378         }
379         String base;
380         int cnt;
381         if (pos == -1) {
382             if (baseName.length() > 1) {
383                 pos = baseName.length() - 2;
384             }
385         }
386         if (pos == -1) {
387             base = baseName;
388             cnt = -1;
389         } else {
390             pos++;
391             base = baseName.substring(0, pos);
392             if (pos == baseName.length()) {
393                 cnt = -1;
394             } else {
395                 cnt = Integer.parseInt(baseName.substring(pos));
396             }
397         }
398         return (base + ++cnt);
399     }
400 
401     public static String getAbsolutePath(String path, String label) {
402         if (StringUtils.isEmpty(path) || (path.equals("/"))) {
403             return "/" + label;
404         }
405 
406         return path + "/" + label;
407     }
408 
409     public static String getAbsolutePath(String path) {
410         if (!path.startsWith("/")) {
411             return "/" + path;
412         }
413         return path;
414     }
415 }