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.lang.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     {
209         String charset = StringUtils.EMPTY;
210         if ((SystemProperty.getBooleanProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED)))
211         {
212             charset = "UTF-8";
213         }
214         return getValidatedLabel(label, charset);
215 
216     }
217 
218     /**
219      * If charset equals <code>UTF-8</code>, replaces the following characters with a dash <code>-</code> :
220      * <p>
221      * Jackrabbit not allowed {@code 32: [ ] 91: [[] 93: []] 42: [*] 34: ["] 58 [:] 92: [\] 39 :[']}
222      * <p>
223      * URL not valid {@code 59: [;] 47: [/] 63: [?] 43: [+] 37: [%] 33: [!] 35:[#] 94: [^]}.
224      * <p>
225      * Otherwise, replaces illegal characters with a dash <code>-</code> except for {@code [_] [0-9], [A-Z], [a-z], [-], [_], [.]}.
226      * <p>
227      * Please notice that a valid label can not begin with dot or period <code>[.]</code>.
228      * 
229      * @return a validated label for a node.
230      */
231     public static String getValidatedLabel(String label, String charset)
232     {
233         if (StringUtils.isEmpty(label)) {
234             return DEFAULT_UNTITLED_NODE_NAME;
235         }
236         final StringBuilder newLabel = new StringBuilder(label.length());
237 
238         // label cannot begin with . (dot)
239         int ch = label.charAt(0);
240         if (!isCharValid(ch, charset) || ch == 46) {
241             newLabel.append("-");
242         } else {
243             newLabel.append(label.charAt(0));
244         }
245 
246         for (int i = 1; i < label.length(); i++)
247         {
248             int charCode = label.charAt(i);
249             if (isCharValid(charCode, charset))
250             {
251                 newLabel.append(label.charAt(i));
252             }
253             else
254             {
255                 newLabel.append("-");
256             }
257         }
258         if (newLabel.length() == 0)
259         {
260             newLabel.append(DEFAULT_UNTITLED_NODE_NAME);
261         }
262         return newLabel.toString();
263     }
264 
265     /**
266      * @param charCode char code
267      * @param charset charset (ex. UTF-8)
268      * @return true if char can be used as a content name
269      */
270     public static boolean isCharValid(int charCode, String charset)
271     {
272         // TODO fgrilli: we now allow dots (.) in JR local names but actually in JR 2.0 other chars could be allowed as well
273         // (see http://www.day.com/specs/jcr/2.0/3_Repository_Model.html paragraph 2.2 and org.apache.jackrabbit.util.XMLChar.isValid()).
274         // Also, now that we're on java 6 and JR 2.0 should the check for the charset be dropped?
275 
276         // http://www.ietf.org/rfc/rfc1738.txt
277         // safe = "$" | "-" | "_" | "." | "+"
278         // extra = "!" | "*" | "'" | "(" | ")" | ","
279         // national = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`"
280         // punctuation = "<" | ">" | "#" | "%" | <">
281         // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "="
282 
283         if ("UTF-8".equals(charset))
284         {
285             // jackrabbit not allowed 32: [ ] 91: [[] 93: []] 42: [*] 34: ["] 46: [.] 58 [:] 92: [\] 39 :[']
286             // url not valid 59: [;] 47: [/] 63: [?] 43: [+] 37: [%] 33: [!] 35:[#]
287             if (charCode != 32
288                     && charCode != '['
289                     && charCode != ']'
290                     && charCode != '*'
291                     && charCode != '"'
292                     && charCode != ':'
293                     && charCode != 92
294                     && charCode != 39
295                     && charCode != ';'
296                     && charCode != '/'
297                     && charCode != '?'
298                     && charCode != '+'
299                     && charCode != '%'
300                     && charCode != '!'
301                     && charCode != '#'
302                     && charCode != '@'
303                     && charCode != '&'
304                     && charCode != '=')
305             {
306                 return true;
307             }
308         }
309         else
310         {
311             // charCodes: 48-57: [0-9]; 65-90: [A-Z]; 97-122: [a-z]; 45: [-]; 95:[_]
312             if (((charCode >= 48) && (charCode <= 57))
313                     || ((charCode >= 65) && (charCode <= 90))
314                     || ((charCode >= 97) && (charCode <= 122))
315                     || charCode == 45
316                     || charCode == 46
317                     || charCode == 95)
318             {
319                 return true;
320             }
321 
322         }
323         return false;
324 
325     }
326 
327     private static String createUniqueName(String baseName) {
328         int pos;
329         for (pos = baseName.length() - 1; pos >= 0; pos--) {
330             char c = baseName.charAt(pos);
331             if (c < '0' || c > '9') {
332                 break;
333             }
334         }
335         String base;
336         int cnt;
337         if (pos == -1) {
338             if (baseName.length() > 1) {
339                 pos = baseName.length() - 2;
340             }
341         }
342         if (pos == -1) {
343             base = baseName;
344             cnt = -1;
345         }
346         else {
347             pos++;
348             base = baseName.substring(0, pos);
349             if (pos == baseName.length()) {
350                 cnt = -1;
351             }
352             else {
353                 cnt = new Integer(baseName.substring(pos)).intValue();
354             }
355         }
356         return (base + ++cnt);
357     }
358 
359     public static String getAbsolutePath(String path, String label) {
360         if (StringUtils.isEmpty(path) || (path.equals("/"))) {
361             return "/" + label;
362         }
363 
364         return path + "/" + label;
365     }
366 
367     public static String getAbsolutePath(String path) {
368         if (!path.startsWith("/")) {
369             return "/" + path;
370         }
371         return path;
372     }
373 }