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