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