View Javadoc
1   /*
2    * Copyright (C) 2003-2007 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.services.cms.impl;
18  
19  import com.ibm.icu.text.Transliterator;
20  import org.apache.commons.lang.StringEscapeUtils;
21  import org.apache.commons.lang.StringUtils;
22  import org.exoplatform.container.component.ComponentPlugin;
23  import org.exoplatform.services.cms.BasePath;
24  import org.exoplatform.services.cms.documents.TrashService;
25  import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
26  import org.exoplatform.services.cms.link.LinkManager;
27  import org.exoplatform.services.cms.templates.TemplateService;
28  import org.exoplatform.services.cms.thumbnail.ThumbnailPlugin;
29  import org.exoplatform.services.cms.thumbnail.ThumbnailService;
30  import org.exoplatform.services.context.DocumentContext;
31  import org.exoplatform.services.jcr.core.ExtendedNode;
32  import org.exoplatform.services.jcr.core.ManageableRepository;
33  import org.exoplatform.services.jcr.ext.common.SessionProvider;
34  import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
35  import org.exoplatform.services.jcr.impl.core.NodeImpl;
36  import org.exoplatform.services.jcr.util.Text;
37  import org.exoplatform.services.jcr.util.VersionHistoryImporter;
38  import org.exoplatform.services.listener.ListenerService;
39  import org.exoplatform.services.log.ExoLogger;
40  import org.exoplatform.services.log.Log;
41  import org.exoplatform.services.security.*;
42  import org.exoplatform.services.wcm.core.NodetypeConstant;
43  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
44  
45  import javax.jcr.*;
46  import javax.jcr.nodetype.NodeType;
47  import javax.jcr.nodetype.PropertyDefinition;
48  import javax.ws.rs.core.MediaType;
49  
50  import java.io.BufferedInputStream;
51  import java.io.BufferedReader;
52  import java.io.ByteArrayInputStream;
53  import java.io.ByteArrayOutputStream;
54  import java.io.IOException;
55  import java.io.InputStream;
56  import java.io.InputStreamReader;
57  import java.io.UnsupportedEncodingException;
58  import java.net.URLEncoder;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Calendar;
62  import java.util.Collection;
63  import java.util.Collections;
64  import java.util.Comparator;
65  import java.util.Date;
66  import java.util.HashMap;
67  import java.util.HashSet;
68  import java.util.LinkedList;
69  import java.util.List;
70  import java.util.Map;
71  import java.util.Queue;
72  import java.util.Set;
73  import java.util.zip.ZipEntry;
74  import java.util.zip.ZipInputStream;
75  
76  /**
77   * @author benjaminmestrallet
78   */
79  public class Utils {
80    private final static Log   LOG          = ExoLogger.getLogger(Utils.class.getName());
81  
82    private static final String ILLEGAL_SEARCH_CHARACTERS= "\\!^()+{}[]:\"-";
83  
84    public static final String MAPPING_FILE = "mapping.properties";
85  
86    public static final String EXO_SYMLINK = "exo:symlink";
87  
88    public static final String PRIVATE = "Private";
89  
90    public static final String PUBLIC = "Public";
91  
92  
93    public static final long KB = 1024L;
94    public static final long MB = 1024L*KB;
95    public static final long GB = 1024L*MB;
96  
97    public static Node makePath(Node rootNode, String path, String nodetype)
98        throws PathNotFoundException, RepositoryException {
99      return makePath(rootNode, path, nodetype, null);
100   }
101 
102   @SuppressWarnings("unchecked")
103   public static Node makePath(Node rootNode, String path, String nodetype, Map permissions)
104       throws PathNotFoundException, RepositoryException {
105     String[] tokens = path.split("/") ;
106     Node node = rootNode;
107     for (int i = 0; i < tokens.length; i++) {
108       String token = tokens[i];
109       if(token.length() > 0) {
110         if(node.hasNode(token)) {
111           node = node.getNode(token) ;
112         } else {
113           node = node.addNode(token, nodetype);
114           node.getSession().save();
115           node = (Node)node.getSession().getItem(node.getPath());
116           if (node.canAddMixin("exo:privilegeable")){
117             node.addMixin("exo:privilegeable");
118           }
119           if(permissions != null){
120             ((ExtendedNode)node).setPermissions(permissions);
121           }
122         }
123       }
124     }
125     rootNode.save();
126     return node;
127   }
128 
129   /**
130    * this function used to process import version history for a node
131    *
132    * @param currentNode
133    * @param versionHistorySourceStream
134    * @param mapHistoryValue
135    * @throws Exception
136    */
137   public static void processImportHistory(Node currentNode,
138                                           InputStream versionHistorySourceStream,
139                                           Map<String, String> mapHistoryValue) throws Exception {
140     //read stream, get the version history data & keep it inside a map
141     Map<String, byte[]> mapVersionHistoryData = getVersionHistoryData (versionHistorySourceStream);
142 
143     //import one by one
144     for (String uuid : mapHistoryValue.keySet()) {
145       for (String name : mapVersionHistoryData.keySet()) {
146         if (name.equals(uuid + ".xml")) {
147           try {
148             byte[] versionHistoryData = mapVersionHistoryData.get(name);
149             ByteArrayInputStream inputStream = new ByteArrayInputStream(versionHistoryData);
150             String value = mapHistoryValue.get(uuid);
151             Node versionableNode = currentNode.getSession().getNodeByUUID(uuid);
152             importHistory((NodeImpl) versionableNode,
153                           inputStream,
154                           getBaseVersionUUID(value),
155                           getPredecessors(value),
156                           getVersionHistory(value));
157             currentNode.getSession().save();
158             break;
159           } catch (ItemNotFoundException item) {
160             currentNode.getSession().refresh(false);
161             if (LOG.isErrorEnabled()) {
162               LOG.error("Can not found versionable node" + item, item);
163             }
164           } catch (Exception e) {
165             currentNode.getSession().refresh(false);
166             if (LOG.isErrorEnabled()) {
167               LOG.error("Import version history failed " + e, e);
168             }
169           }
170         }
171       }
172     }
173   }
174 
175   /**
176    * This function is used to get the version history data which is kept inside the xml files
177    * @param versionHistorySourceStream
178    * @return a map saving version history data with format: [file name, version history data]
179    * @throws IOException
180    */
181   private static Map<String, byte[]> getVersionHistoryData (InputStream versionHistorySourceStream) throws IOException {
182     Map<String, byte[]> mapVersionHistoryData = new HashMap<String, byte[]>();
183     ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(versionHistorySourceStream));
184     byte[] data = new byte[1024];
185     ZipEntry entry = zipInputStream.getNextEntry();
186     while (entry != null) {
187       //get binary data inside the zip entry
188       ByteArrayOutputStream out = new ByteArrayOutputStream();
189       int available = -1;
190       while ((available = zipInputStream.read(data, 0, 1024)) > -1) {
191         out.write(data, 0, available);
192       }
193 
194       //save data into map
195       mapVersionHistoryData.put(entry.getName(), out.toByteArray());
196 
197       //go to next entry
198       out.close();
199       zipInputStream.closeEntry();
200       entry = zipInputStream.getNextEntry();
201     }
202 
203     zipInputStream.close();
204     return mapVersionHistoryData;
205   }
206 
207   /**
208    * do import a version into a node
209    *
210    * @param versionableNode
211    * @param versionHistoryStream
212    * @param baseVersionUuid
213    * @param predecessors
214    * @param versionHistory
215    * @throws RepositoryException
216    * @throws IOException
217    */
218   private static void importHistory(NodeImpl versionableNode,
219                                     InputStream versionHistoryStream,
220                                     String baseVersionUuid,
221                                     String[] predecessors,
222                                     String versionHistory) throws RepositoryException, IOException {
223     VersionHistoryImporter versionHistoryImporter = new VersionHistoryImporter(versionableNode,
224                                                                                versionHistoryStream,
225                                                                                baseVersionUuid,
226                                                                                predecessors,
227                                                                                versionHistory);
228     versionHistoryImporter.doImport();
229   }
230 
231   /**
232    * get data from the version history file
233    *
234    * @param importHistorySourceStream
235    * @return
236    * @throws Exception
237    */
238   public static Map<String, String> getMapImportHistory(InputStream importHistorySourceStream) throws Exception {
239     ZipInputStream zipInputStream = new ZipInputStream(importHistorySourceStream);
240     ByteArrayOutputStream out = new ByteArrayOutputStream();
241     byte[] data = new byte[1024];
242     ZipEntry entry = zipInputStream.getNextEntry();
243     Map<String, String> mapHistoryValue = new HashMap<String, String>();
244     while (entry != null) {
245       int available = -1;
246       if (entry.getName().equals(MAPPING_FILE)) {
247         while ((available = zipInputStream.read(data, 0, 1024)) > -1) {
248           out.write(data, 0, available);
249         }
250         InputStream inputStream = new ByteArrayInputStream(out.toByteArray());
251         BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
252         String strLine;
253         // Read File Line By Line
254         while ((strLine = br.readLine()) != null) {
255           // Put the history information into list
256           if (strLine.indexOf("=") > -1) {
257             mapHistoryValue.put(strLine.split("=")[0], strLine.split("=")[1]);
258           }
259         }
260         // Close the input stream
261         inputStream.close();
262         zipInputStream.closeEntry();
263         break;
264       }
265       entry = zipInputStream.getNextEntry();
266     }
267     out.close();
268     zipInputStream.close();
269     return mapHistoryValue;
270   }
271 
272   private static String getBaseVersionUUID(String valueHistory) {
273     String[] arrHistoryValue = valueHistory.split(";");
274     return arrHistoryValue[1];
275   }
276 
277   private static String[] getPredecessors(String valueHistory) {
278     String[] arrHistoryValue = valueHistory.split(";");
279     String strPredecessors = arrHistoryValue[1];
280     if (strPredecessors.indexOf(",") > -1) {
281       return strPredecessors.split(",");
282     }
283     return new String[] { strPredecessors };
284   }
285 
286   private static String getVersionHistory(String valueHistory) {
287     String[] arrHistoryValue = valueHistory.split(";");
288     return arrHistoryValue[0];
289   }
290 
291   public static String getPersonalDrivePath(String parameterizedDrivePath, String userId) throws Exception {
292     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
293     NodeHierarchyCreator nodeHierarchyCreator = WCMCoreUtils.getService(NodeHierarchyCreator.class);
294     Node userNode = nodeHierarchyCreator.getUserNode(sessionProvider, userId);
295     return StringUtils.replaceOnce(parameterizedDrivePath,
296                                    nodeHierarchyCreator.getJcrPath(BasePath.CMS_USERS_PATH) + "/${userId}",
297                                    userNode.getPath());
298   }
299 
300   public static List<PropertyDefinition> getProperties(Node node) throws Exception {
301     List<PropertyDefinition> properties = new ArrayList<PropertyDefinition>();
302     NodeType nodetype = node.getPrimaryNodeType() ;
303     Collection<NodeType> types = new ArrayList<NodeType>() ;
304     types.add(nodetype) ;
305     NodeType[] mixins = node.getMixinNodeTypes() ;
306     if (mixins != null) types.addAll(Arrays.asList(mixins)) ;
307     for(NodeType nodeType : types) {
308       for(PropertyDefinition property : nodeType.getPropertyDefinitions()) {
309         String name = property.getName();
310         if(!name.equals("exo:internalUse")&& !property.isProtected()&& !node.hasProperty(name)) {
311           properties.add(property);
312         }
313       }
314     }
315     return properties;
316   }
317 
318   public static boolean isInTrash(Node node) throws RepositoryException {
319     TrashService trashService = WCMCoreUtils.getService(TrashService.class);
320     return trashService.isInTrash(node);
321   }
322 
323   /**
324    * Gets the title.
325    *
326    * @param node the node
327    * @return the title
328    * @throws Exception the exception
329    */
330   public static String getTitle(Node node) throws Exception {
331     String title = null;
332     if (node.hasProperty("exo:title")) {
333       title = node.getProperty("exo:title").getValue().getString();
334     } else if (node.hasNode("jcr:content")) {
335       Node content = node.getNode("jcr:content");
336       if (content.hasProperty("dc:title")) {
337         try {
338           title = content.getProperty("dc:title").getValues()[0].getString();
339         } catch (PathNotFoundException ex) {
340           title = null;
341         } catch(ValueFormatException ex) {
342           title = null;
343         } catch(RepositoryException ex) {
344           title = null;
345         } catch(ArrayIndexOutOfBoundsException ex) {
346           title = null;
347         }
348       }
349     }
350     if (StringUtils.isBlank(title)) {
351       if (node.isNodeType("nt:frozenNode")) {
352         String uuid = node.getProperty("jcr:frozenUuid").getString();
353         Node originalNode = node.getSession().getNodeByUUID(uuid);
354         title = originalNode.getName();
355       } else {
356         title = node.getName();
357       }
358 
359     }
360     return StringEscapeUtils.escapeHtml(Text.unescapeIllegalJcrChars(title));
361   }
362 
363   public static String escapeIllegalCharacterInQuery(String query) {
364     String ret = query;
365     if(ret != null) {
366       for (char c : ILLEGAL_SEARCH_CHARACTERS.toCharArray()) {
367         ret = ret.replace(c + "", "\\" + c);
368       }
369     }
370     ret = ret.replace("'", "''");
371     return ret;
372   }
373 
374 	/**
375 	 * Remove the symlink of a deleted node
376 	 * @param node The deleted node
377 	 * @throws Exception
378 	 */
379 	public static void removeSymlinks(Node node) throws Exception {
380 		LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
381 		List<Node> symlinks = linkManager.getAllLinks(node, EXO_SYMLINK);
382 		for (Node symlink : symlinks) {
383 			symlink.remove();
384 		}
385 	}
386 
387   /**
388    * Remove deleted Symlink from Trash
389    * @param node
390    * @throws Exception
391    */
392   private static void removeDeadSymlinksFromTrash(Node node) throws Exception {
393     LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
394     List<Node> symlinks = linkManager.getAllLinks(node, EXO_SYMLINK);
395     ListenerService listenerService =  WCMCoreUtils.getService(ListenerService.class);
396     for (Node symlink : symlinks) {
397       symlink.remove();
398       listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, null, symlink);
399     }
400   }
401   
402   /**
403    * Remove all the link of a deleted node
404    * @param node
405    * @param keepInTrash true if the link will be move to trash, otherwise set by false
406    * @throws Exception
407    */
408   public static void removeDeadSymlinks(Node node, boolean keepInTrash) throws Exception {
409     if (isInTrash(node)) {
410       removeDeadSymlinksFromTrash(node);
411       return;
412     }
413     LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
414     TrashService trashService = WCMCoreUtils.getService(TrashService.class);
415     SessionProvider sessionProvider = SessionProvider.createSystemProvider();
416     Queue<Node> queue = new LinkedList<Node>();
417     queue.add(node);
418 
419     try {
420       while (!queue.isEmpty()) {
421         node = queue.poll();
422         if (!node.isNodeType(EXO_SYMLINK)) {
423           try {
424             List<Node> symlinks = linkManager.getAllLinks(node, EXO_SYMLINK, sessionProvider);
425             // Before removing symlinks, We order symlinks by name descending, index descending.
426             // Example: symlink[3],symlink[2], symlink[1] to avoid the case that
427             // the index of same name symlink automatically changed to increasing one by one
428             Collections.sort(symlinks, new Comparator<Node>()
429                              {
430               @Override
431               public int compare(Node node1, Node node2) {
432                 try {
433                   String name1 = node1.getName();
434                   String name2 = node2.getName();
435                   if (name1.equals(name2)) {
436                     int index1 = node1.getIndex();
437                     int index2 = node2.getIndex();
438                     return -1 * ((Integer)index1).compareTo(index2);
439                   }
440                   return -1 * name1.compareTo(name2);
441                 } catch (RepositoryException e) {
442                   return 0;
443                 }
444               }
445                              });
446 
447             for (Node symlink : symlinks) {
448               synchronized (symlink) {
449                 if (keepInTrash) {
450                   trashService.moveToTrash(symlink, sessionProvider, 1);
451                 }else {
452                   symlink.remove();
453                 }
454                 ListenerService listenerService =  WCMCoreUtils.getService(ListenerService.class);
455                 listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, null, symlink);
456               }
457             }
458           } catch (Exception e) {
459             if (LOG.isWarnEnabled()) {
460               LOG.warn(e.getMessage());
461             }
462           }
463           for (NodeIterator iter = node.getNodes(); iter.hasNext(); ) {
464             queue.add(iter.nextNode());
465           }
466         }
467       }
468     } catch (Exception e) {
469       if (LOG.isWarnEnabled()) {
470         LOG.warn(e.getMessage());
471       }
472     } finally {
473       sessionProvider.close();
474     }
475   }
476 
477   public static void removeDeadSymlinks(Node node) throws Exception {
478     removeDeadSymlinks(node, true);
479   }
480 
481   public static Node getChildOfType(Node node, String childType) throws Exception {
482     if (node == null) {
483       return null;
484     }
485     NodeIterator iter = node.getNodes();
486     while (iter.hasNext()) {
487       Node child = iter.nextNode();
488       if (child.isNodeType(childType)) {
489         return child;
490       }
491     }
492     return null;
493   }
494 
495   public static boolean hasChild(Node node, String childType) throws Exception {
496     return (getChildOfType(node, childType) != null);
497   }
498 
499   /**
500    * Get Service Log Content Node of specific service.
501    *
502    * @param serviceName
503    * @return
504    * @throws Exception
505    */
506   private static Node getServiceLogContentNode(SessionProvider systemProvider, String serviceName, String logType) throws Exception {
507     // Get workspace and session where store service log
508     ManageableRepository repository = WCMCoreUtils.getRepository();
509     Session session =
510         systemProvider.getSession(repository.getConfiguration().getDefaultWorkspaceName(), repository);
511     Node serviceLogContentNode = null;
512 
513     try {
514       // Get service folder
515       Node serviceFolder = (Node) session.getItem("/exo:services");
516 
517       // Get service node
518       Node serviceNode = serviceFolder.hasNode(serviceName) ?
519           serviceFolder.getNode(serviceName) : serviceFolder.addNode(serviceName, NodetypeConstant.NT_UNSTRUCTURED);
520 
521       // Get log node of service
522       String serviceLogName = serviceName + "_" + logType;
523       Node serviceLogNode = serviceNode.hasNode(serviceLogName) ?
524           serviceNode.getNode(serviceLogName) : serviceNode.addNode(serviceLogName, NodetypeConstant.NT_FILE);
525 
526       // Get service log content
527       if (serviceLogNode.hasNode(NodetypeConstant.JCR_CONTENT)) {
528         serviceLogContentNode = serviceLogNode.getNode(NodetypeConstant.JCR_CONTENT);
529       } else {
530         serviceLogContentNode = serviceLogNode.addNode(NodetypeConstant.JCR_CONTENT, NodetypeConstant.NT_RESOURCE);
531         serviceLogContentNode.setProperty(NodetypeConstant.JCR_ENCODING, "UTF-8");
532         serviceLogContentNode.setProperty(NodetypeConstant.JCR_MIME_TYPE, MediaType.TEXT_PLAIN);
533         serviceLogContentNode.setProperty(NodetypeConstant.JCR_DATA, StringUtils.EMPTY);
534         serviceLogContentNode.setProperty(NodetypeConstant.JCR_LAST_MODIFIED, new Date().getTime());
535       }
536       session.save();
537     } catch (PathNotFoundException ex) {
538       LOG.warn("Could not find /exo:services node");
539     }
540     return serviceLogContentNode;
541   }
542   /**
543    * Get Service Log Content Node of specific service.
544    *
545    * @param serviceName
546    * @return
547    * @throws Exception
548    */
549   
550   public static Node getServiceLogContentNode(String serviceName, String logType) throws Exception {
551     return getServiceLogContentNode(WCMCoreUtils.getSystemSessionProvider(), serviceName, logType);
552   }
553 
554   /**
555    * Get all the templates which have been added into the system
556    * @param className Simple name of class.
557    * @param id The unique value which used to build service log name.
558    * @param skipActivities To skip raising activities on activity stream.
559    * @return A Set of templates name which have been added.
560    * @throws Exception
561   */
562   public static Set<String> getAllEditedConfiguredData(String className, String id, boolean skipActivities) throws Exception {
563     SessionProvider systemProvider = SessionProvider.createSystemProvider();
564     try {
565       DocumentContext.getCurrent().getAttributes().put(DocumentContext.IS_SKIP_RAISE_ACT, skipActivities);
566       HashSet<String> editedConfigTemplates = new HashSet<String>();
567       Node serviceLogContentNode= getServiceLogContentNode(systemProvider, className, id);
568       if (serviceLogContentNode != null) {
569         String logData = serviceLogContentNode.getProperty(NodetypeConstant.JCR_DATA).getString();
570         editedConfigTemplates.addAll(Arrays.asList(logData.split(";")));
571       }
572       return editedConfigTemplates;
573     } finally {
574       systemProvider.close();
575     }
576   }
577 
578   /**
579    * Keep the name of templates in jcr:data property at the first time loaded.
580    * @param template Name of template which will be kept in jcr:data property
581    * @param className A simple class name
582    * @param id The unique value which used to build service log name.
583    * @param skipActivities To skip raising activities on activity stream.
584    * @throws Exception
585  */
586   public static void addEditedConfiguredData(String template, String className, String id, boolean skipActivities) throws Exception {
587     SessionProvider systemProvider = SessionProvider.createSystemProvider();
588     try {
589       DocumentContext.getCurrent().getAttributes().put(DocumentContext.IS_SKIP_RAISE_ACT, skipActivities);
590       Node serviceLogContentNode = getServiceLogContentNode(systemProvider, className, id);
591       if (serviceLogContentNode != null) {
592         String logData = serviceLogContentNode.getProperty(NodetypeConstant.JCR_DATA).getString();
593         if (StringUtils.isEmpty(logData)) logData = template;
594         else if (logData.indexOf(template) == -1) logData = logData.concat(";").concat(template);
595         serviceLogContentNode.setProperty(NodetypeConstant.JCR_DATA, logData);
596         serviceLogContentNode.getSession().save();
597       }
598     } finally {
599       systemProvider.close();
600     }
601   }
602 
603   public static void removeEditedConfiguredData(String template,
604                                                 String className,
605                                                 String id,
606                                                 boolean skipActivities) throws Exception {
607     SessionProvider systemProvider = SessionProvider.createSystemProvider();
608     try {
609       DocumentContext.getCurrent()
610                      .getAttributes()
611                      .put(DocumentContext.IS_SKIP_RAISE_ACT, skipActivities);
612       Node serviceLogContentNode = getServiceLogContentNode(systemProvider, className, id);
613       if (serviceLogContentNode == null)
614         return;
615       String logData = serviceLogContentNode.getProperty(NodetypeConstant.JCR_DATA).getString();
616       if (StringUtils.isNotBlank(logData)) {
617         logData = ";".concat(logData).replace(";".concat(template), StringUtils.EMPTY);
618         logData = StringUtils.substring(logData, 1);
619         serviceLogContentNode.setProperty(NodetypeConstant.JCR_DATA, logData);
620         serviceLogContentNode.getSession().save();
621       }
622     } finally {
623       systemProvider.close();
624     }
625   }
626 
627   public static String getObjectId(String nodePath) throws UnsupportedEncodingException {
628     return URLEncoder.encode(nodePath.replaceAll("'", "\\\\'"), "utf-8");
629   }
630 
631   /**
632    * Clean string.
633    *
634    * @param str the str
635    *
636    * @return the string
637    */
638   public static String cleanString(String str) {
639     Transliterator accentsconverter = Transliterator.getInstance("Latin; NFD; [:Nonspacing Mark:] Remove; NFC;");
640     str = accentsconverter.transliterate(str);
641     //the character ? seems to not be changed to d by the transliterate function
642     StringBuffer cleanedStr = new StringBuffer(str.trim());
643     // delete special character
644     for(int i = 0; i < cleanedStr.length(); i++) {
645       char c = cleanedStr.charAt(i);
646       if(c == ' ') {
647         if (i > 0 && cleanedStr.charAt(i - 1) == '-') {
648           cleanedStr.deleteCharAt(i--);
649         } else {
650           c = '-';
651           cleanedStr.setCharAt(i, c);
652         }
653         continue;
654       }
655       if(i > 0 && !(Character.isLetterOrDigit(c) || c == '-')) {
656         cleanedStr.deleteCharAt(i--);
657         continue;
658       }
659       if(i > 0 && c == '-' && cleanedStr.charAt(i-1) == '-')
660         cleanedStr.deleteCharAt(i--);
661     }
662     while (StringUtils.isNotEmpty(cleanedStr.toString()) && !Character.isLetterOrDigit(cleanedStr.charAt(0))) {
663       cleanedStr.deleteCharAt(0);
664     }
665     String clean = cleanedStr.toString().toLowerCase();
666     if (clean.endsWith("-")) {
667       clean = clean.substring(0, clean.length()-1);
668     }
669 
670     return clean;
671   }
672   
673   /**
674    * Clean string. Replace specialChar by "-"
675    *
676    * @param oldName the str
677    *
678    * @return the string
679    */
680 
681   public static String cleanName(String oldName) {
682     if (StringUtils.isEmpty(oldName)) return oldName;
683     String specialChar = "[]/'\":;";
684     StringBuilder ret = new StringBuilder();
685     for (int i = 0; i < oldName.length(); i++) {
686       char currentChar = oldName.charAt(i);
687       if (specialChar.indexOf(currentChar) > -1) {
688         ret.append('-');
689       } else {
690         ret.append(currentChar);
691       }
692     }
693     return ret.toString();
694   }
695 
696   /** Return name after cleaning
697    * @param fileName file name
698    * @return cleaned name
699    */
700   public static String cleanNameWithAccents(String fileName) {
701     Transliterator accentsconverter = Transliterator.getInstance("Latin; NFD; [:Nonspacing Mark:] Remove; NFC;");
702     if (fileName.indexOf('.') > 0) {
703       String ext = fileName.substring(fileName.lastIndexOf('.'));
704       fileName = accentsconverter.transliterate(fileName.substring(0, fileName.lastIndexOf('.'))).concat(ext);
705     } else {
706       fileName = accentsconverter.transliterate(fileName);
707     }
708     return Text.escapeIllegalJcrChars(fileName);
709 
710   }
711 
712   public static List<String> getMemberships() throws Exception {
713     List<String> userMemberships = new ArrayList<String>();
714     String userId = ConversationState.getCurrent().getIdentity().getUserId();
715     if (StringUtils.isNotEmpty(userId)) {
716       userMemberships.add(userId);
717       Collection<MembershipEntry> memberships = getUserMembershipsFromIdentityRegistry(userId);
718       for (MembershipEntry membership : memberships) {
719         String role = membership.getMembershipType() + ":" + membership.getGroup();
720         userMemberships.add(role);
721       }
722     }
723     return userMemberships;
724   }
725 
726   /**
727    * this method retrieves memberships of the user having the given id using the
728    * IdentityRegistry service instead of the Organization service to allow JAAS
729    * based authorization
730    *
731    * @param authenticatedUser the authenticated user id
732    * @return a collection of MembershipEntry
733    */
734   private static Collection<MembershipEntry> getUserMembershipsFromIdentityRegistry(String authenticatedUser) {
735     IdentityRegistry identityRegistry = WCMCoreUtils.getService(IdentityRegistry.class);
736     Identity currentUserIdentity = identityRegistry.getIdentity(authenticatedUser);
737     if (currentUserIdentity == null) {
738       return Collections.<MembershipEntry>emptySet();
739     } else {
740       return currentUserIdentity.getMemberships();
741     }
742   }
743 
744   public static String getNodeTypeIcon(Node node, String appended, String mode)
745       throws RepositoryException {
746     if (node == null)
747       return "";
748 
749     // Primary node type
750     String nodeType = node.getPrimaryNodeType().getName();
751 
752     // Get real node if node is symlink
753     LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
754     if (linkManager.isLink(node)) {
755       try {
756         nodeType = node.getProperty(NodetypeConstant.EXO_PRIMARYTYPE).getString();
757         node = linkManager.getTarget(node, IdentityConstants.SYSTEM.equals(node.getSession().getUserID()));
758         if (node == null)
759           return "";
760       } catch (Exception e) {
761         return "";
762       }
763     }
764 
765     if (node.isNodeType(NodetypeConstant.EXO_TRASH_FOLDER)) {
766       nodeType = NodetypeConstant.EXO_TRASH_FOLDER;
767     }
768     else if (node.isNodeType(NodetypeConstant.EXO_FAVOURITE_FOLDER)) {
769       nodeType = NodetypeConstant.EXO_FAVOURITE_FOLDER;
770     }
771     else if (nodeType.equals(NodetypeConstant.NT_UNSTRUCTURED) || nodeType.equals(NodetypeConstant.NT_FOLDER)) {
772       if (PRIVATE.equals(node.getName()) || PUBLIC.equals(node.getName())) {
773           nodeType = String.format("exo:%sFolder", node.getName().toLowerCase());
774       } else {
775         for (String specificFolder : NodetypeConstant.SPECIFIC_FOLDERS) {
776           if (node.isNodeType(specificFolder)) {
777             nodeType = specificFolder;
778             break;
779           }
780         }
781       }
782     }
783 
784     nodeType = nodeType.replace(':', '_');
785 
786     // Default css class
787     String defaultCssClass;
788     if (node.isNodeType(NodetypeConstant.NT_UNSTRUCTURED) || node.isNodeType(NodetypeConstant.NT_FOLDER)) {
789       defaultCssClass = "Folder";
790     } else if (node.isNodeType(NodetypeConstant.NT_FILE)) {
791       defaultCssClass = "File";
792     } else {
793       defaultCssClass = nodeType;
794     }
795     defaultCssClass = defaultCssClass.concat("Default");
796 
797     StringBuilder str = new StringBuilder();
798     str.append(appended);
799     str.append(defaultCssClass);
800     str.append(" ");
801     str.append(appended);
802     str.append(nodeType);
803     if (mode != null && mode.equalsIgnoreCase("Collapse"))
804       str.append(' ').append(mode).append(appended).append(nodeType);
805     if (node.isNodeType(NodetypeConstant.NT_FILE)) {
806       if (node.hasNode(NodetypeConstant.JCR_CONTENT)) {
807         Node jcrContentNode = node.getNode(NodetypeConstant.JCR_CONTENT);
808         str.append(' ').append(appended).append(
809                                                 jcrContentNode.getProperty(NodetypeConstant.JCR_MIMETYPE).getString().toLowerCase().replaceAll(
810                                                                                                                                                "/|\\.", ""));
811       }
812     }
813     return str.toString();
814   }
815 
816   public static String getNodeTypeIcon(Node node, String appended)
817       throws RepositoryException {
818     return getNodeTypeIcon(node, appended, null);
819   }
820   
821   public static String getFileType(Node node) throws Exception {
822     return getNodeTypeIcon(node, "").
823             replace(".", "").replace("/", "").replace("\\","").replace(".", "");
824   }
825 
826   public static String getFileType(NodeImpl node) throws Exception {
827     return getNodeTypeIcon(node, "").
828         replace(".", "").replace("/", "").replace("\\","").replace(".", "");
829   }
830 
831   /**
832    * Check if a node is document type.
833    * @param node
834    * @return true: is document; false: not document
835    * @throws Exception
836    */
837   public static boolean isDocument(Node node) throws Exception {
838     TemplateService templateService = WCMCoreUtils.getService(TemplateService.class);
839     if (templateService==null) return false;
840     List<String> documentTypeList = templateService.getDocumentTemplates();
841     if (documentTypeList==null) return false;
842     for (String documentType : documentTypeList) {
843       if (node.getPrimaryNodeType().isNodeType(documentType)) {
844         return true;
845       }
846     }
847     return false;
848   }
849   /**
850    * get Last Modify date of jcr:content of a node
851    * 
852    * @param node
853    * @return Last Modify date of jcr:content of a node
854    * @throws Exception
855    */
856   public static String getJcrContentLastModified(Node node) throws Exception {
857     String lastModified = "";
858     if(node.hasProperty("jcr:content/jcr:lastModified")){
859       lastModified = node.getProperty("jcr:content/jcr:lastModified").getString();
860     }else if(node.hasProperty("jcr:content/exo:dateModified")){
861       lastModified =node.getProperty("jcr:content/exo:dateModified").getString();
862     }else if(node.hasProperty("jcr:content/exo:lastModifiedDate")){
863       lastModified =node.getProperty("jcr:content/exo:lastModifiedDate").getString();
864     }
865     return lastModified;
866   }
867 
868   /**
869    *
870    * get jcr:baseVersion of a node
871    * @param node
872    * @return jcr:baseVersion of a node
873    * @throws Exception
874    */
875   public static String getJcrContentBaseVersion(Node node) throws Exception {
876     return node.hasProperty("jcr:baseVersion")? node.getProperty("jcr:baseVersion").getString() : null;
877   }
878   
879   
880   public static Calendar getDate(Node node) throws Exception {
881       return node.hasProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE) ? 
882                    node.getProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE).getDate() :
883                    node.getProperty(NodetypeConstant.EXO_DATE_CREATED).getDate();
884   }
885   
886   public static String getOwner(Node node) throws Exception {
887       return node.hasProperty(NodetypeConstant.EXO_OWNER) ? 
888                    node.getProperty(NodetypeConstant.EXO_OWNER).getString() :
889                    "";
890   }
891   
892   /**
893    * gets the file size in friendly format
894    * @param node the file node
895    * @return the file size
896    * @throws Exception
897    */
898   public static String fileSize(Node node) throws Exception {
899     if (node == null || !node.isNodeType("nt:file")) {
900       return "";
901     }
902     StringBuilder ret = new StringBuilder();
903     ret.append(" - ");
904     long size = 0;
905     try {
906       size = node.getProperty("jcr:content/jcr:data").getLength();
907     } catch (Exception e) {
908       LOG.error("Can not get file size", e);
909     }
910 
911     ret.append(formatSize(size));
912 
913     return ret.toString();
914   }
915 
916   public static String formatSize(long size) {
917     StringBuilder ret = new StringBuilder();
918     long byteSize = size % KB;
919     long kbSize = (size % MB) / KB;
920     long mbSize = (size % GB) / MB;
921     long gbSize = size / GB;
922 
923     if (gbSize >= 1) {
924       ret.append(gbSize).append(refine(mbSize)).append(" GB");
925     } else if (mbSize >= 1) {
926       ret.append(mbSize).append(refine(kbSize)).append(" MB");
927     } else if (kbSize > 1) {
928       ret.append(kbSize).append(refine(byteSize)).append(" KB");
929     } else {
930       ret.append("1 KB");
931     }
932 
933     return ret.toString();
934   }
935 
936   public static boolean isSupportThumbnailView(String mimeType) {
937     List<String> thumbnailMimeTypes = new ArrayList<String>();
938     List<ComponentPlugin> componentPlugins = WCMCoreUtils.getService(ThumbnailService.class).getComponentPlugins();
939     for (ComponentPlugin plugin : componentPlugins) {
940       if (plugin instanceof ThumbnailPlugin) {
941         thumbnailMimeTypes.addAll(((ThumbnailPlugin) plugin).getMimeTypes());
942       }
943     }
944     return thumbnailMimeTypes.contains(mimeType);
945   }
946 
947   /**
948    * refines the size up to 3 digits, add '0' in front if necessary.
949    * @param size the size
950    * @return the size in 3 digit format
951    */
952   private static String refine(long size) {
953     if (size == 0) {
954       return "";
955     }
956     String strSize = String.valueOf(size);
957     while (strSize.length() < 3) {
958       strSize = "0" + strSize;
959     }
960     return "," + Math.round(Double.valueOf(Integer.valueOf(strSize) / 100.0));
961   }
962   
963   /* check if a node is folder 
964    * @param node node to check
965    * @return folder or not
966    */
967   public static boolean isFolder(Node node) throws RepositoryException  {
968     return node.isNodeType(NodetypeConstant.NT_FOLDER)
969         || node.isNodeType(NodetypeConstant.NT_UNSTRUCTURED);
970  }
971 
972 }