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.i18n.impl;
18  
19  import java.io.ByteArrayInputStream;
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.GregorianCalendar;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.jcr.ItemExistsException;
28  import javax.jcr.Node;
29  import javax.jcr.NodeIterator;
30  import javax.jcr.PathNotFoundException;
31  import javax.jcr.Property;
32  import javax.jcr.PropertyIterator;
33  import javax.jcr.PropertyType;
34  import javax.jcr.RepositoryException;
35  import javax.jcr.Session;
36  import javax.jcr.Value;
37  import javax.jcr.Workspace;
38  import javax.jcr.nodetype.NodeType;
39  import javax.jcr.nodetype.PropertyDefinition;
40  
41  import org.exoplatform.commons.utils.ISO8601;
42  import org.exoplatform.services.cms.CmsService;
43  import org.exoplatform.services.cms.JcrInputProperty;
44  import org.exoplatform.services.cms.i18n.MultiLanguageService;
45  import org.exoplatform.services.cms.impl.Utils;
46  import org.exoplatform.services.cms.link.LinkManager;
47  import org.exoplatform.services.exceptions.SameAsDefaultLangException;
48  import org.exoplatform.services.jcr.access.PermissionType;
49  import org.exoplatform.services.jcr.core.ExtendedNode;
50  import org.exoplatform.services.jcr.impl.core.value.DateValue;
51  import org.exoplatform.services.jcr.impl.core.value.StringValue;
52  import org.exoplatform.services.log.ExoLogger;
53  import org.exoplatform.services.log.Log;
54  import org.exoplatform.services.security.IdentityConstants;
55  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
56  
57  public class MultiLanguageServiceImpl implements MultiLanguageService {
58  
59    /**
60     * Path to child node keep content of parent node
61     */
62    final static public String  JCRCONTENT           = "jcr:content";
63  
64    /**
65     * Property name keep data of node
66     */
67    final static public String  JCRDATA              = "jcr:data";
68  
69    /**
70     * Property name keep mimeType of data
71     */
72    final static public String  JCR_MIMETYPE         = "jcr:mimeType";
73  
74    /**
75     * NodeType name nt:unstructured
76     */
77    final static public String  NTUNSTRUCTURED       = "nt:unstructured";
78  
79    /**
80     * NodeType name nt:folder
81     */
82    final static public String  NTFOLDER             = "nt:folder";
83  
84    /**
85     * NodeType name nt:file
86     */
87    final static public String  NTFILE               = "nt:file";
88  
89    /**
90     * Property name jcr:lastModified
91     */
92    final static public String  JCR_LASTMODIFIED     = "jcr:lastModified";
93  
94    /**
95     * Property name exo:voter
96     */
97    final static String         VOTER_PROP           = "exo:voter";
98  
99    /**
100    * Property name exo:votingRate
101    */
102   final static String         VOTING_RATE_PROP     = "exo:votingRate";
103 
104   /**
105    * Property name exo:voteTotal
106    */
107   final static String         VOTE_TOTAL_PROP      = "exo:voteTotal";
108 
109   /**
110    * Property name exo:boteTotalOfLang
111    */
112   final static String         VOTE_TOTAL_LANG_PROP = "exo:voteTotalOfLang";
113 
114   /**
115    * Node path
116    */
117   final static String         NODE                 = "/node/";
118 
119   /**
120    * Node path for language
121    */
122   final static String         NODE_LANGUAGE        = "/node/languages/";
123 
124   /**
125    * Path to content node
126    */
127   final static String         CONTENT_PATH         = "/node/jcr:content/";
128 
129   /**
130    * Name of temporatory node
131    */
132   final static String         TEMP_NODE            = "temp";
133 
134   private static final String MIX_REFERENCEABLE    = "mix:referenceable";
135 
136   private static final String MIX_COMMENTABLE ="mix:commentable";
137 
138   private static final String COUNTRY_VARIANT      = "_";
139 
140   private static final Log       LOG             = ExoLogger.getLogger(MultiLanguageServiceImpl.class.getName());
141 
142   /**
143    * CmsService
144    */
145   private CmsService cmsService_ ;
146 
147 
148   /**
149    * Constructor method
150    * @param cmsService  CmsService object
151    * @throws Exception
152    */
153   public MultiLanguageServiceImpl(CmsService cmsService) throws Exception {
154     cmsService_ = cmsService ;
155   }
156 
157   /**
158    * Set property for given node with value, property name, PropertyType, multiple
159    * @param propertyName  name of property set to node
160    * @param node          node which is added new property
161    * @param requiredtype  Type of property
162    * @param value         Value of property
163    * @param isMultiple    This property is multiple if isMultiple = true or not if isMultiple = false
164    * @throws Exception
165    */
166   private void setPropertyValue(String propertyName,
167                                 Node node,
168                                 int requiredtype,
169                                 Object value,
170                                 boolean isMultiple) throws Exception {
171     switch (requiredtype) {
172     case PropertyType.STRING:
173       if (value == null) {
174         node.setProperty(propertyName, "");
175       } else {
176         if(isMultiple) {
177           if (value instanceof String) node.setProperty(propertyName, new String[] { value.toString()});
178           else if(value instanceof String[]) node.setProperty(propertyName, (String[]) value);
179         } else {
180           if(value instanceof StringValue) {
181             StringValue strValue = (StringValue) value ;
182             node.setProperty(propertyName, strValue.getString());
183           } else {
184             node.setProperty(propertyName, value.toString());
185           }
186         }
187       }
188       break;
189     case PropertyType.BINARY:
190       if (value == null)
191         node.setProperty(propertyName, "");
192       else if (value instanceof byte[])
193         node.setProperty(propertyName, new ByteArrayInputStream((byte[]) value));
194       else if (value instanceof String)
195         node.setProperty(propertyName, new ByteArrayInputStream((value.toString()).getBytes()));
196       else if (value instanceof String[])
197         node.setProperty(propertyName, new ByteArrayInputStream((((String[]) value)).toString()
198                                                                                     .getBytes()));
199       break;
200     case PropertyType.BOOLEAN:
201       if (value == null)
202         node.setProperty(propertyName, false);
203       else if (value instanceof String)
204         node.setProperty(propertyName, new Boolean(value.toString()).booleanValue());
205       else if (value instanceof String[])
206         node.setProperty(propertyName, (String[]) value);
207       break;
208     case PropertyType.LONG:
209       if (value == null || "".equals(value))
210         node.setProperty(propertyName, 0);
211       else if (value instanceof String)
212         node.setProperty(propertyName, new Long(value.toString()).longValue());
213       else if (value instanceof String[])
214         node.setProperty(propertyName, (String[]) value);
215       break;
216     case PropertyType.DOUBLE:
217       if (value == null || "".equals(value))
218         node.setProperty(propertyName, 0);
219       else if (value instanceof String)
220         node.setProperty(propertyName, new Double(value.toString()).doubleValue());
221       else if (value instanceof String[])
222         node.setProperty(propertyName, (String[]) value);
223       break;
224     case PropertyType.DATE:
225       if (value == null) {
226         node.setProperty(propertyName, new GregorianCalendar());
227       } else {
228         if(isMultiple) {
229           Session session = node.getSession() ;
230           if (value instanceof String) {
231             Value value2add = session.getValueFactory().createValue(ISO8601.parse((String) value));
232             node.setProperty(propertyName, new Value[] {value2add});
233           } else if (value instanceof String[]) {
234             String[] values = (String[]) value;
235             Value[] convertedCalendarValues = new Value[values.length];
236             int i = 0;
237             for (String stringValue : values) {
238               Value value2add = session.getValueFactory().createValue(ISO8601.parse(stringValue));
239               convertedCalendarValues[i] = value2add;
240               i++;
241             }
242             node.setProperty(propertyName, convertedCalendarValues);
243           }
244         } else {
245           if(value instanceof String) {
246             node.setProperty(propertyName, ISO8601.parse(value.toString()));
247           } else if(value instanceof GregorianCalendar) {
248             node.setProperty(propertyName, (GregorianCalendar) value);
249           } else if(value instanceof DateValue) {
250             DateValue dateValue = (DateValue) value ;
251             node.setProperty(propertyName, dateValue.getDate());
252           }
253         }
254       }
255       break;
256     case PropertyType.REFERENCE :
257       if (value == null) {
258         node.setProperty(propertyName, "");
259       } else if (value instanceof Value) {
260         node.setProperty(propertyName, (Value)value);
261       } else if (value instanceof Value[]) {
262         node.setProperty(propertyName, (Value[]) value);
263       } else if (value instanceof String) {
264         Session session = node.getSession();
265         Node catNode = null;
266         String itemPath = value.toString();
267         if ((itemPath != null) && (itemPath.length() > 0)) {
268           if (itemPath.indexOf(":/") > -1) {
269             if (itemPath.split(":/").length > 0) itemPath = "/" + itemPath.split(":/")[1];
270           }
271           try {
272             catNode = (Node)session.getItem(itemPath);
273           } catch (PathNotFoundException e) {
274             catNode = session.getRootNode().getNode(itemPath);
275           }
276           if (catNode != null) {
277             if(!catNode.isNodeType(MIX_REFERENCEABLE)) {
278               catNode.addMixin(MIX_REFERENCEABLE);
279               catNode.save();
280             }
281             Value value2add = session.getValueFactory().createValue(catNode);
282             if(isMultiple) {
283               node.setProperty(propertyName, new Value[] {value2add});
284             } else {
285               node.setProperty(propertyName, value2add);
286             }
287           } else {
288             node.setProperty(propertyName, value.toString());
289           }
290         }
291       }
292       break ;
293     }
294   }
295 
296   /**
297    * Add all available mixintype from current node to newLang Node
298    * Set value of all mixintype from current node to newLang Node
299    * @param node        current node
300    * @param newLang     new node that is added mixintype
301    * @param setValuesOnyIfCanAddMixin indicates if the values must be set if we cannot add the mixin
302    * @throws Exception
303    */
304   private void setMixin(Node node, Node newLang, boolean setValuesOnyIfCanAddMixin) throws Exception {
305     NodeType[] mixins = node.getMixinNodeTypes() ;
306     for(NodeType mixin:mixins) {
307       if(canCopy(mixin)) {
308         boolean mixinAdded = false;
309         if(newLang.canAddMixin(mixin.getName())) {
310           newLang.addMixin(mixin.getName()) ;
311           mixinAdded = true;
312         }
313         if (!setValuesOnyIfCanAddMixin || mixinAdded) {
314           for(PropertyDefinition def: mixin.getPropertyDefinitions()) {
315             if(!def.isProtected()) {
316               String propName = def.getName() ;
317               if(def.isMandatory() && !def.isAutoCreated()) {
318                 if(def.isMultiple()) {
319                   newLang.setProperty(propName,node.getProperty(propName).getValues()) ;
320                 } else {
321                   newLang.setProperty(propName,node.getProperty(propName).getValue()) ;
322                 }
323               }
324             }
325           }
326         }
327       }
328     }
329   }
330 
331   /**
332    * Add all available mixintype from current node to newLang Node
333    * Set value of all mixintype from current node to newLang Node
334    * @param node        current node
335    * @param newLang     new node that is added mixintype
336    * @throws Exception
337    */
338   private void setMixin(Node node, Node newLang) throws Exception {
339     setMixin(node, newLang, true);
340   }
341 
342   /**
343    * Add new file with data = value to newLanguageNode node
344    * @param fileName        name of file
345    * @param newLanguageNode current node to add file
346    * @param value           data of file
347    * @param lastModified    datetime of modification
348    * @param mimeType        mimitype
349    * @param repositoryName  name of repository
350    * @return                Node which is added with data is file
351    * @throws Exception
352    */
353   private Node addNewFileNode(String fileName,
354                               Node newLanguageNode,
355                               Value value,
356                               Object lastModified,
357                               String mimeType,
358                               String repositoryName) throws Exception {
359     Map<String,JcrInputProperty> inputProperties = new HashMap<String,JcrInputProperty>() ;
360     JcrInputProperty nodeInput = new JcrInputProperty() ;
361     nodeInput.setJcrPath("/node") ;
362     nodeInput.setValue(fileName) ;
363     nodeInput.setMixintype("mix:i18n,mix:votable,mix:commentable") ;
364     nodeInput.setType(JcrInputProperty.NODE) ;
365     inputProperties.put("/node",nodeInput) ;
366 
367     JcrInputProperty jcrContent = new JcrInputProperty() ;
368     jcrContent.setJcrPath("/node/jcr:content") ;
369     jcrContent.setValue("") ;
370     jcrContent.setMixintype("dc:elementSet") ;
371     jcrContent.setNodetype("nt:resource") ;
372     jcrContent.setType(JcrInputProperty.NODE) ;
373     inputProperties.put("/node/jcr:content",jcrContent) ;
374 
375     JcrInputProperty jcrData = new JcrInputProperty() ;
376     jcrData.setJcrPath("/node/jcr:content/jcr:data") ;
377     jcrData.setValue(value.getStream()) ;
378     inputProperties.put("/node/jcr:content/jcr:data",jcrData) ;
379 
380     JcrInputProperty jcrMimeType = new JcrInputProperty() ;
381     jcrMimeType.setJcrPath("/node/jcr:content/jcr:mimeType") ;
382     jcrMimeType.setValue(mimeType) ;
383     inputProperties.put("/node/jcr:content/jcr:mimeType",jcrMimeType) ;
384 
385     JcrInputProperty jcrLastModified = new JcrInputProperty() ;
386     jcrLastModified.setJcrPath("/node/jcr:content/jcr:lastModified") ;
387     jcrLastModified.setValue(lastModified) ;
388     inputProperties.put("/node/jcr:content/jcr:lastModified",jcrLastModified) ;
389 
390     JcrInputProperty jcrEncoding = new JcrInputProperty() ;
391     jcrEncoding.setJcrPath("/node/jcr:content/jcr:encoding") ;
392     jcrEncoding.setValue("UTF-8") ;
393     inputProperties.put("/node/jcr:content/jcr:encoding",jcrEncoding) ;
394     cmsService_.storeNode(NTFILE, newLanguageNode, inputProperties, true) ;
395     return newLanguageNode.getNode(fileName) ;
396   }
397 
398   /**
399    * {@inheritDoc}
400    */
401   private Node getFileLangNode(Node languageNode) throws Exception {
402     if(languageNode.getNodes().getSize() > 0) {
403       NodeIterator nodeIter = languageNode.getNodes() ;
404       while(nodeIter.hasNext()) {
405         Node ntFile = nodeIter.nextNode() ;
406         if(ntFile.isNodeType(NTFILE)) {
407           return ntFile ;
408         }
409       }
410       return languageNode ;
411     }
412     return languageNode ;
413   }
414 
415   /**
416    * {@inheritDoc}
417    */
418   public void addLanguage(Node node, Map inputs, String language, boolean isDefault) throws Exception {
419     Node newLanguageNode = null ;
420     Node languagesNode = null ;
421     String defaultLanguage = getDefault(node) ;
422     String primaryNodeTypeName = node.getPrimaryNodeType().getName();
423     if(node.hasNode(LANGUAGES)) languagesNode = node.getNode(LANGUAGES) ;
424     else  {
425       languagesNode = node.addNode(LANGUAGES, NTUNSTRUCTURED) ;
426       if(languagesNode.canAddMixin("exo:hiddenable"))
427         languagesNode.addMixin("exo:hiddenable");
428     }
429     if(!defaultLanguage.equals(language)){
430       if(isDefault) {
431         if(languagesNode.hasNode(defaultLanguage)) {
432           newLanguageNode = languagesNode.getNode(defaultLanguage) ;
433         } else {
434           newLanguageNode = languagesNode.addNode(defaultLanguage, primaryNodeTypeName) ;
435           setMixin(node, newLanguageNode, false);
436         }
437       } else {
438         if(languagesNode.hasNode(language)) {
439           newLanguageNode = languagesNode.getNode(language) ;
440         } else {
441           newLanguageNode = languagesNode.addNode(language, primaryNodeTypeName) ;
442           setMixin(node, newLanguageNode, false);
443           newLanguageNode.setProperty(EXO_LANGUAGE, language) ;
444         }
445       }
446     }
447 
448     setPropertyLanguage(node, newLanguageNode, inputs, isDefault, defaultLanguage, language);
449     if(isDefault && languagesNode.hasNode(language)) languagesNode.getNode(language).remove() ;
450   }
451 
452   /**
453    * {@inheritDoc}
454    */
455   public void addLinkedLanguage(Node node, Node translationNode, boolean forceReplace) throws Exception {
456     Node languagesNode;
457     if (node.hasNode(LANGUAGES))
458       languagesNode = node.getNode(LANGUAGES);
459     else {
460       languagesNode = node.addNode(LANGUAGES, "nt:unstructured");
461       if (languagesNode.canAddMixin("exo:hiddenable"))
462         languagesNode.addMixin("exo:hiddenable");
463     }
464     if (!translationNode.isNodeType("mix:i18n")) {
465       translationNode.addMixin("mix:i18n");
466       translationNode.save();
467     }
468     if (!node.isNodeType("mix:i18n")) {
469       node.addMixin("mix:i18n");
470       node.save();
471     }
472     String lang = translationNode.getProperty("exo:language").getString();
473     if (languagesNode.hasNode(lang)) {
474       if (forceReplace) {
475         languagesNode.getNode(lang).remove();
476         languagesNode.save();
477       } else {
478         throw new ItemExistsException();
479       }
480     } else if (getDefault(node).equals(lang)) {
481       throw new SameAsDefaultLangException();
482     }
483     LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
484     Node linkNode = linkManager.createLink(languagesNode, "exo:symlink", translationNode, lang);
485     ((ExtendedNode)linkNode).setPermission(IdentityConstants.ANY, new String[]{PermissionType.READ});
486     linkNode.getSession().save();
487   }
488 
489   /**
490    * {@inheritDoc}
491    */
492   public void addLinkedLanguage(Node node, Node translationNode) throws Exception {
493     addLinkedLanguage(node, translationNode, false);
494   }
495   
496   /**
497    * {@inheritDoc}
498    */
499   public void addSynchronizedLinkedLanguage(Node selectedNode, Node newTranslationNode) throws Exception {
500     if (newTranslationNode != null && newTranslationNode.isNodeType(Utils.EXO_SYMLINK)) {
501       newTranslationNode = WCMCoreUtils.getService(LinkManager.class).getTarget(newTranslationNode);
502     }
503 
504     if (!newTranslationNode.isNodeType("mix:i18n")) {
505       newTranslationNode.addMixin("mix:i18n");
506       newTranslationNode.save();
507     }
508 
509     String newLang = newTranslationNode.getProperty("exo:language").getString();
510 
511     // Only add new translation if lang of new translation
512     // has not existed yet inside selected Node
513     if (getLanguage(selectedNode, newLang) == null) {
514 
515       // Get all real translation Nodes of selected node.
516       // If there are some, add new translation for them
517       List<Node> realTranslationNodes = getRealTranslationNodes(selectedNode);
518       for (Node node : realTranslationNodes) {
519         try {
520           addLinkedLanguage(node, newTranslationNode);
521         }
522         catch(ItemExistsException ex) {
523           if (LOG.isInfoEnabled()) {
524             LOG.info(String.format("Language %s already existed for %s", newLang, node.getPath()));
525           }
526         }
527 
528         // Update translations for new translation Node
529         try {
530           addLinkedLanguage(newTranslationNode, node);
531         }
532         catch(ItemExistsException ex) {
533           if (LOG.isInfoEnabled()) {
534             LOG.info(String.format("Language %s already existed for %s",
535                    node.getProperty("exo:language").getString(),
536                    newTranslationNode.getPath()));
537           }
538         }
539       }
540 
541       try {
542         addLinkedLanguage(newTranslationNode, selectedNode);
543       }
544       catch(ItemExistsException ex) {
545         if (LOG.isInfoEnabled()) {
546           LOG.info(String.format("Language %s already existed for %s",
547                  selectedNode.getProperty("exo:language").getString(),
548                  newTranslationNode.getPath()));
549         }
550       }
551 
552       // Add new translation to selected Node
553       addLinkedLanguage(selectedNode, newTranslationNode);
554     } else {
555       throw new ItemExistsException();
556     }
557   }
558 
559   /**
560    * {@inheritDoc}
561    */
562   public void addLanguage(Node node, Map inputs, String language, boolean isDefault, String nodeType) throws Exception {
563     Node newLanguageNode = null ;
564     Node languagesNode = null ;
565     String primaryNodeTypeName = node.getPrimaryNodeType().getName();
566     String defaultLanguage = getDefault(node) ;
567     Workspace ws = node.getSession().getWorkspace() ;
568     if(node.hasNode(LANGUAGES)) languagesNode = node.getNode(LANGUAGES) ;
569     else  {
570       languagesNode = node.addNode(LANGUAGES, NTUNSTRUCTURED) ;
571       if(languagesNode.canAddMixin("exo:hiddenable"))
572         languagesNode.addMixin("exo:hiddenable");
573     }
574     if(!defaultLanguage.equals(language)){
575       if(isDefault) {
576         if(languagesNode.hasNode(defaultLanguage)) {
577           newLanguageNode = languagesNode.getNode(defaultLanguage) ;
578         } else {
579           newLanguageNode = languagesNode.addNode(defaultLanguage, primaryNodeTypeName) ;
580           setMixin(node, newLanguageNode, false);
581         }
582       } else {
583         if(languagesNode.hasNode(language)) {
584           newLanguageNode = languagesNode.getNode(language) ;
585         } else {
586           newLanguageNode = languagesNode.addNode(language, primaryNodeTypeName) ;
587           setMixin(node, newLanguageNode, false);
588           newLanguageNode.setProperty(EXO_LANGUAGE, language) ;
589         }
590       }
591       Node jcrContent = node.getNode(nodeType) ;
592       if ("jcr:content".equals(nodeType)) {
593         Node jcrContentNode = newLanguageNode.addNode("jcr:content", "nt:resource");
594         jcrContentNode.setProperty("jcr:lastModified", new GregorianCalendar());
595         jcrContentNode.setProperty("jcr:mimeType", "text/plain");
596         jcrContentNode.setProperty("jcr:data", "");
597       }
598       node.save() ;
599       if(!newLanguageNode.hasNode(nodeType)) {
600         ws.copy(jcrContent.getPath(), newLanguageNode.getPath() + "/" + jcrContent.getName()) ;
601       }
602       Node newContentNode = newLanguageNode.getNode(nodeType) ;
603       PropertyIterator props = newContentNode.getProperties() ;
604       while(props.hasNext()) {
605         Property prop = props.nextProperty() ;
606         if(inputs.containsKey(NODE + nodeType + "/" + prop.getName())) {
607           JcrInputProperty inputVariable = (JcrInputProperty) inputs.get(NODE + nodeType + "/" + prop.getName()) ;
608           boolean isMultiple = prop.getDefinition().isMultiple() ;
609           setPropertyValue(prop.getName(), newContentNode, prop.getType(), inputVariable.getValue(), isMultiple) ;
610         }
611       }
612       if(isDefault) {
613         Node tempNode = node.addNode(TEMP_NODE, "nt:unstructured") ;
614         node.getSession().move(node.getNode(nodeType).getPath(), tempNode.getPath() + "/" + nodeType) ;
615         node.getSession().move(newLanguageNode.getNode(nodeType).getPath(), node.getPath() + "/" + nodeType) ;
616         node.getSession().move(tempNode.getNode(nodeType).getPath(),
617                                languagesNode.getPath() + "/" + defaultLanguage + "/" + nodeType);
618         tempNode.remove() ;
619       }
620     } else {
621       JcrInputProperty inputVariable = (JcrInputProperty) inputs.get(NODE + nodeType + "/" + JCRDATA) ;
622       setPropertyValue(JCRDATA, node.getNode(nodeType), inputVariable.getType(), inputVariable.getValue(), false) ;
623     }
624     setPropertyLanguage(node, newLanguageNode, inputs, isDefault, defaultLanguage, language);
625     if(isDefault && languagesNode.hasNode(language)) languagesNode.getNode(language).remove() ;
626   }
627 
628   /**
629    * {@inheritDoc}
630    */
631   public void addFileLanguage(Node node,
632                               String fileName,
633                               Value value,
634                               String mimeType,
635                               String language,
636                               String repositoryName,
637                               boolean isDefault) throws Exception {
638     Node newLanguageNode = null ;
639     Node languagesNode = null ;
640     String defaultLanguage = getDefault(node) ;
641     Node ntFileLangNode = null ;
642     Node oldJcrContent = node.getNode(JCRCONTENT) ;
643     String olfFileName = node.getName() ;
644     Value oldValue = oldJcrContent.getProperty(JCRDATA).getValue() ;
645     String oldMimeType = oldJcrContent.getProperty(JCR_MIMETYPE).getString() ;
646     Calendar oldLastModified = new GregorianCalendar();
647     oldLastModified.setTime(oldJcrContent.getProperty(JCR_LASTMODIFIED).getDate().getTime());
648     try {
649       languagesNode = node.getNode(LANGUAGES) ;
650     } catch(PathNotFoundException pe) {
651       languagesNode = node.addNode(LANGUAGES, NTUNSTRUCTURED) ;
652       if(languagesNode.canAddMixin("exo:hiddenable"))
653         languagesNode.addMixin("exo:hiddenable");
654     }
655     if(!defaultLanguage.equals(language)){
656       if(isDefault) {
657         try {
658           newLanguageNode = languagesNode.getNode(defaultLanguage) ;
659         } catch(PathNotFoundException pe) {
660           newLanguageNode = languagesNode.addNode(defaultLanguage) ;
661           if (newLanguageNode.canAddMixin(MIX_COMMENTABLE)) {
662             newLanguageNode.addMixin(MIX_COMMENTABLE);
663           }
664         }
665         oldJcrContent.setProperty(JCR_MIMETYPE, mimeType) ;
666         oldJcrContent.setProperty(JCRDATA, value) ;
667         oldJcrContent.setProperty(JCR_LASTMODIFIED, new GregorianCalendar()) ;
668         oldJcrContent.save();
669       } else {
670         try {
671           newLanguageNode = languagesNode.getNode(language) ;
672         } catch(PathNotFoundException pe) {
673           newLanguageNode = languagesNode.addNode(language) ;
674           if (newLanguageNode.canAddMixin(MIX_COMMENTABLE)) {
675             newLanguageNode.addMixin(MIX_COMMENTABLE);
676           }
677           if(languagesNode.canAddMixin("exo:hiddenable"))
678             languagesNode.addMixin("exo:hiddenable");
679         }
680       }
681       try {
682         ntFileLangNode = newLanguageNode.getNode(fileName) ;
683       } catch(PathNotFoundException pe) {
684         node.save();
685         if(isDefault) {
686           ntFileLangNode = addNewFileNode(olfFileName,
687                                           newLanguageNode,
688                                           oldValue,
689                                           oldLastModified,
690                                           oldMimeType,
691                                           repositoryName);
692         } else {
693           ntFileLangNode = addNewFileNode(fileName, newLanguageNode, value,
694               new GregorianCalendar(), mimeType, repositoryName) ;
695 
696         }
697       }
698       Node newJcrContent = ntFileLangNode.getNode(JCRCONTENT) ;
699       newJcrContent.setProperty(JCR_LASTMODIFIED, new GregorianCalendar());
700       setMixin(node, ntFileLangNode) ;
701     } else {
702       node.getNode(JCRCONTENT).setProperty(JCRDATA, value) ;
703     }
704     if(!defaultLanguage.equals(language) && isDefault){
705       Node selectedFileLangeNode = null ;
706       if(languagesNode.hasNode(language)) {
707         Node selectedLangNode = languagesNode.getNode(language) ;
708         selectedFileLangeNode = selectedLangNode.getNode(node.getName()) ;
709       }
710       setVoteProperty(ntFileLangNode, node, selectedFileLangeNode) ;
711       setCommentNode(node, ntFileLangNode, selectedFileLangeNode) ;
712     }
713     if(isDefault) node.setProperty(EXO_LANGUAGE, language) ;
714     node.getSession().save() ;
715   }
716 
717   /**
718    * {@inheritDoc}
719    */
720   public void addFileLanguage(Node node, String language, Map mappings, boolean isDefault) throws Exception {
721     Node newLanguageNode = null ;
722     Node languagesNode = null ;
723     String primaryNodeTypeName = node.getPrimaryNodeType().getName();
724     String defaultLanguage = getDefault(node) ;
725     if(node.hasNode(LANGUAGES)) languagesNode = node.getNode(LANGUAGES) ;
726     else  {
727       languagesNode = node.addNode(LANGUAGES, NTUNSTRUCTURED) ;
728       if(languagesNode.canAddMixin("exo:hiddenable"))
729         languagesNode.addMixin("exo:hiddenable");
730     }
731     if(!defaultLanguage.equals(language)){
732       if(isDefault) {
733         if(languagesNode.hasNode(defaultLanguage)) newLanguageNode = languagesNode.getNode(defaultLanguage) ;
734         else newLanguageNode = languagesNode.addNode(defaultLanguage, primaryNodeTypeName) ;
735       } else {
736         if(languagesNode.hasNode(language)) newLanguageNode = languagesNode.getNode(language) ;
737         else newLanguageNode = languagesNode.addNode(language, primaryNodeTypeName) ;
738       }
739       Node jcrContent = node.getNode(JCRCONTENT) ;
740       if(!newLanguageNode.hasNode(JCRCONTENT)) {
741         Node newJcrContent = newLanguageNode.addNode(JCRCONTENT, "nt:resource");
742         newJcrContent.setProperty(JCR_MIMETYPE, jcrContent.getProperty(JCR_MIMETYPE).getValue());
743         newJcrContent.setProperty(JCRDATA, jcrContent.getProperty(JCRDATA).getValue()) ;
744         newJcrContent.setProperty(JCR_LASTMODIFIED, new GregorianCalendar());
745       }
746       node.save() ;
747       Node newContentNode = newLanguageNode.getNode(JCRCONTENT) ;
748       PropertyIterator props = newContentNode.getProperties() ;
749       while (props.hasNext()) {
750         Property prop = props.nextProperty() ;
751         if(mappings.containsKey(CONTENT_PATH + prop.getName())) {
752           JcrInputProperty inputVariable = (JcrInputProperty) mappings.get(CONTENT_PATH + prop.getName()) ;
753           boolean isMultiple = prop.getDefinition().isMultiple() ;
754           setPropertyValue(prop.getName(), newContentNode, prop.getType(), inputVariable.getValue(), isMultiple) ;
755         }
756       }
757       if (isDefault) {
758         Node tempNode = node.addNode(TEMP_NODE, "nt:unstructured");
759         node.getSession().move(node.getNode(JCRCONTENT).getPath(),
760                                tempNode.getPath() + "/" + JCRCONTENT);
761         node.getSession().move(newLanguageNode.getNode(JCRCONTENT).getPath(),
762                                node.getPath() + "/" + JCRCONTENT);
763         node.getSession().move(tempNode.getNode(JCRCONTENT).getPath(),
764                                languagesNode.getPath() + "/" + defaultLanguage + "/" + JCRCONTENT);
765         tempNode.remove();
766       }
767       // add mixin type for node
768       setMixin(node, newLanguageNode) ;
769     } else {
770       JcrInputProperty inputVariable = (JcrInputProperty) mappings.get(CONTENT_PATH + JCRDATA) ;
771       setPropertyValue(JCRDATA, node.getNode(JCRCONTENT), inputVariable.getType(), inputVariable.getValue(), false) ;
772     }
773     setPropertyLanguage(node, newLanguageNode, mappings, isDefault, defaultLanguage, language);
774   }
775 
776   /**
777    * {@inheritDoc}
778    */
779   public String getDefault(Node node) throws Exception {
780     if(node.hasProperty(EXO_LANGUAGE)) return node.getProperty(EXO_LANGUAGE).getString() ;
781     return null ;
782   }
783 
784   /**
785    * {@inheritDoc}
786    */
787   public List<String> getSupportedLanguages(Node node) throws Exception {
788     List<String> languages = new ArrayList<String>();
789     String defaultLang = getDefault(node) ;
790     if(defaultLang != null) languages.add(defaultLang) ;
791     if(node.hasNode(LANGUAGES)){
792       Node languageNode = node.getNode(LANGUAGES) ;
793       NodeIterator iter  = languageNode.getNodes() ;
794       while(iter.hasNext()) {
795         languages.add(iter.nextNode().getName());
796       }
797     }
798     return languages;
799   }
800 
801   /**
802    * Get all current supported translation Nodes of specified node
803    * @param node Specified Node
804    * @return All current supported translation Nodes
805    * @throws Exception
806    */
807   private List<Node> getRealTranslationNodes(Node node) throws Exception {
808     LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
809     List<Node> translationNodes = new ArrayList<Node>();
810     if(node.hasNode(LANGUAGES)){
811       Node languageNode = node.getNode(LANGUAGES) ;
812       NodeIterator iter  = languageNode.getNodes() ;
813       while(iter.hasNext()) {
814         Node currNode = iter.nextNode();
815         if (currNode.isNodeType("exo:symlink")) {
816           translationNodes.add(linkManager.getTarget(currNode));
817         }
818       }
819     }
820     return translationNodes;
821   }
822 
823   /**
824    * Set property concerning vote for newLang node from node.
825    * Set property concerning vote for node from selectedLangNode
826    * @param newLang               node for new language
827    * @param node                  current node
828    * @param selectedLangNode      selected language node
829    * @throws Exception
830    */
831   private void setVoteProperty(Node newLang, Node node, Node selectedLangNode) throws Exception {
832     if(hasMixin(newLang, "mix:votable")) {
833       newLang.setProperty(VOTE_TOTAL_PROP, getVoteTotal(node)) ;
834       newLang.setProperty(VOTE_TOTAL_LANG_PROP, node.getProperty(VOTE_TOTAL_LANG_PROP).getLong()) ;
835       newLang.setProperty(VOTING_RATE_PROP, node.getProperty(VOTING_RATE_PROP).getLong()) ;
836       if(node.hasProperty(VOTER_PROP)) {
837         newLang.setProperty(VOTER_PROP, node.getProperty(VOTER_PROP).getValues()) ;
838       }
839       if(selectedLangNode != null) {
840         node.setProperty(VOTE_TOTAL_PROP, getVoteTotal(node)) ;
841         if(selectedLangNode.hasProperty(VOTE_TOTAL_LANG_PROP)) {
842           node.setProperty(VOTE_TOTAL_LANG_PROP, selectedLangNode.getProperty(VOTE_TOTAL_LANG_PROP).getLong()) ;
843         } else {
844           node.setProperty(VOTE_TOTAL_LANG_PROP, 0) ;
845         }
846         if(selectedLangNode.hasProperty(VOTING_RATE_PROP)) {
847           node.setProperty(VOTING_RATE_PROP, selectedLangNode.getProperty(VOTING_RATE_PROP).getLong()) ;
848         } else {
849           node.setProperty(VOTING_RATE_PROP, 0) ;
850         }
851         if(selectedLangNode.hasProperty(VOTER_PROP)) {
852           node.setProperty(VOTER_PROP, selectedLangNode.getProperty(VOTER_PROP).getValues()) ;
853         }
854       } else {
855         node.setProperty(VOTE_TOTAL_PROP, getVoteTotal(node)) ;
856         node.setProperty(VOTE_TOTAL_LANG_PROP, 0) ;
857         node.setProperty(VOTING_RATE_PROP, 0) ;
858       }
859     }
860   }
861 
862   /**
863    * Move COMMENTS child node of current node to newLang node
864    * Move COMMENTS child node of selected node to current node
865    * @param newLang               node for new language
866    * @param node                  current node
867    * @param selectedLangNode      selected language node
868    * @throws Exception
869    */
870   private void setCommentNode(Node node, Node newLang, Node selectedLangNode) throws Exception {
871     if(node.hasNode(COMMENTS)) {
872       node.getSession().move(node.getPath() + "/" + COMMENTS, newLang.getPath() + "/" + COMMENTS) ;
873     }
874     if(selectedLangNode != null && selectedLangNode.hasNode(COMMENTS)) {
875       node.getSession().move(selectedLangNode.getPath() + "/" + COMMENTS, node.getPath() + "/" + COMMENTS) ;
876     }
877   }
878 
879   /**
880    * Get total value in VOTE_TOTAL_LANG_PROP property of current node and all file child node
881    * @param node        current node
882    * @return
883    * @throws Exception
884    */
885   private long getVoteTotal(Node node) throws Exception {
886     long voteTotal = 0;
887     if(!node.hasNode(LANGUAGES) && node.hasProperty(VOTE_TOTAL_PROP)) {
888       return node.getProperty(VOTE_TOTAL_LANG_PROP).getLong() ;
889     }
890     Node multiLanguages = node.getNode(LANGUAGES) ;
891     if (node.hasProperty(VOTE_TOTAL_LANG_PROP))
892       voteTotal = node.getProperty(VOTE_TOTAL_LANG_PROP).getLong();
893     NodeIterator nodeIter = multiLanguages.getNodes() ;
894     String defaultLang = getDefault(node) ;
895     while(nodeIter.hasNext()) {
896       Node languageNode = nodeIter.nextNode() ;
897       if(node.isNodeType(NTFILE)) {
898         Node jcrContentNode = node.getNode(JCRCONTENT);
899         if(!jcrContentNode.getProperty(JCR_MIMETYPE).getString().startsWith("text")) {
900           languageNode = getFileLangNode(languageNode) ;
901         }
902       }
903       if(!languageNode.getName().equals(defaultLang) && languageNode.hasProperty(VOTE_TOTAL_LANG_PROP)) {
904         voteTotal = voteTotal + languageNode.getProperty(VOTE_TOTAL_LANG_PROP).getLong() ;
905       }
906     }
907     return voteTotal ;
908   }
909 
910   /**
911    * Check whether current node has MixinTypes name = noteTypeName
912    * @param node
913    * @param nodeTypeName
914    * @return  true: if exist noteTypeName in MixtinTypes
915    *          false: if not exist
916    * @throws Exception
917    */
918   private boolean hasMixin(Node node, String nodeTypeName) throws Exception {
919     NodeType[] mixinTypes = node.getMixinNodeTypes() ;
920     for(NodeType nodeType : mixinTypes) {
921       if(nodeType.getName().equals(nodeTypeName)) return true ;
922     }
923     return false ;
924   }
925 
926   /**
927    * Check if the given mixin type can be copied
928    * @param mixin the mixin type to check
929    * @return <code>true</code> if it the mixin can be copied, <code>false</code> otherwise
930    */
931   private boolean canCopy(NodeType mixin) {
932     final String name = mixin.getName();
933     // We must prevent to copy "mix:versionable" to avoid
934     // ECM-4028: Restoring previous version of multilanguage article loose all languages except root one
935     return !name.equals("exo:actionable") && !name.equals("mix:versionable");
936   }
937 
938   private void setPropertyLanguage(Node node,
939                                    Node newLanguageNode,
940                                    Map mappings,
941                                    boolean isDefault,
942                                    String defaultLanguage,
943                                    String language) throws Exception {
944     PropertyDefinition[] properties = node.getPrimaryNodeType().getPropertyDefinitions() ;
945     for(PropertyDefinition pro : properties){
946       if(!pro.isProtected()) {
947         String propertyName = pro.getName() ;
948         JcrInputProperty property = (JcrInputProperty)mappings.get(NODE + propertyName) ;
949         if(defaultLanguage.equals(language) && property != null) {
950           setPropertyValue(propertyName, node, pro.getRequiredType(), property.getValue(), pro.isMultiple()) ;
951         } else {
952           if(isDefault) {
953             if(node.hasProperty(propertyName)) {
954               Object value = null ;
955               int requiredType = node.getProperty(propertyName).getDefinition().getRequiredType() ;
956               boolean isMultiple = node.getProperty(propertyName).getDefinition().isMultiple() ;
957               if(isMultiple) value = node.getProperty(propertyName).getValues() ;
958               else value = node.getProperty(propertyName).getValue() ;
959               setPropertyValue(propertyName, newLanguageNode, requiredType, value, isMultiple) ;
960             }
961             if(property != null) {
962               setPropertyValue(propertyName, node, pro.getRequiredType(), property.getValue(), pro.isMultiple()) ;
963             }
964           } else {
965             if (property != null) {
966               setPropertyValue(propertyName,
967                                newLanguageNode,
968                                pro.getRequiredType(),
969                                property.getValue(),
970                                pro.isMultiple());
971             }
972           }
973         }
974       }
975     }
976     if (!defaultLanguage.equals(language) && isDefault) {
977       Node selectedLangNode = null;
978       Node languagesNode = node.getNode(LANGUAGES);
979       if (languagesNode.hasNode(language))
980         selectedLangNode = languagesNode.getNode(language);
981       setVoteProperty(newLanguageNode, node, selectedLangNode);
982       setCommentNode(node, newLanguageNode, selectedLangNode);
983     }
984     if(isDefault) node.setProperty(EXO_LANGUAGE, language) ;
985     node.save();
986     node.getSession().save();
987   }
988   /**
989    * {@inheritDoc}
990    */
991   public void setDefault(Node node, String language, String repositoryName) throws Exception {
992     String defaultLanguage = getDefault(node) ;
993     String nodeTypeName = node.getPrimaryNodeType().getName();
994     if(!defaultLanguage.equals(language)){
995       Node languagesNode = null ;
996       try {
997         languagesNode = node.getNode(LANGUAGES) ;
998       } catch(PathNotFoundException pe) {
999         languagesNode = node.addNode(LANGUAGES, NTUNSTRUCTURED) ;
1000         if(languagesNode.canAddMixin("exo:hiddenable"))
1001           languagesNode.addMixin("exo:hiddenable");
1002       }
1003       Node selectedLangNode = languagesNode.getNode(language) ;
1004       Node newLang = null;
1005       if(nodeTypeName.equals(NTFILE)) {
1006         Node jcrContentNode = node.getNode(JCRCONTENT) ;
1007         if(!jcrContentNode.getProperty(JCR_MIMETYPE).getString().startsWith("text")) {
1008           newLang = languagesNode.addNode(defaultLanguage);
1009           selectedLangNode = getFileLangNode(selectedLangNode) ;
1010           node.save();
1011           newLang = addNewFileNode(node.getName(), newLang, jcrContentNode.getProperty(JCRDATA).getValue(),
1012               new GregorianCalendar(), jcrContentNode.getProperty(JCR_MIMETYPE).getString(), repositoryName) ;
1013           Node newJcrContent = newLang.getNode(JCRCONTENT) ;
1014           newJcrContent.setProperty(JCRDATA, jcrContentNode.getProperty(JCRDATA).getValue()) ;
1015           newJcrContent.setProperty(JCR_MIMETYPE, jcrContentNode.getProperty(JCR_MIMETYPE).getString()) ;
1016         } else {
1017           newLang = languagesNode.addNode(defaultLanguage, nodeTypeName) ;
1018         }
1019       } else if(node.isNodeType(NTUNSTRUCTURED) || node.isNodeType(NTFOLDER)) {
1020         newLang = languagesNode.addNode(defaultLanguage);
1021         selectedLangNode = selectedLangNode.getNode(node.getName());
1022         newLang = newLang.addNode(node.getName(), nodeTypeName);
1023       } else {
1024         newLang = languagesNode.addNode(defaultLanguage, nodeTypeName);
1025       }
1026 
1027       PropertyDefinition[] properties = node.getPrimaryNodeType().getPropertyDefinitions() ;
1028       for(PropertyDefinition pro : properties){
1029         if(!pro.isProtected()){
1030           String propertyName = pro.getName() ;
1031           if(node.hasProperty(propertyName)) {
1032             if(node.getProperty(propertyName).getDefinition().isMultiple()) {
1033               Value[] values = node.getProperty(propertyName).getValues() ;
1034               newLang.setProperty(propertyName, values) ;
1035             } else {
1036               newLang.setProperty(propertyName, node.getProperty(propertyName).getValue()) ;
1037             }
1038           }
1039           if(selectedLangNode.hasProperty(propertyName)) {
1040             if(selectedLangNode.getProperty(propertyName).getDefinition().isMultiple()) {
1041               Value[] values = selectedLangNode.getProperty(propertyName).getValues() ;
1042               node.setProperty(propertyName, values) ;
1043             } else {
1044               node.setProperty(propertyName, selectedLangNode.getProperty(propertyName).getValue()) ;
1045             }
1046           }
1047         }
1048       }
1049       setMixin(node, newLang);
1050       if(nodeTypeName.equals(NTFILE)) {
1051         Node tempNode = node.addNode(TEMP_NODE, NTUNSTRUCTURED) ;
1052         node.getSession().move(node.getNode(JCRCONTENT).getPath(), tempNode.getPath() + "/" + JCRCONTENT) ;
1053         node.getSession().move(selectedLangNode.getNode(JCRCONTENT).getPath(), node.getPath() + "/" + JCRCONTENT) ;
1054         if(node.getNode(JCRCONTENT).getProperty(JCR_MIMETYPE).getString().startsWith("text")) {
1055           node.getSession().move(tempNode.getPath() + "/" + JCRCONTENT, newLang.getPath() + "/" + JCRCONTENT);
1056         }
1057         tempNode.remove() ;
1058       } else if(node.isNodeType(NTUNSTRUCTURED) || node.isNodeType(NTFOLDER)) {
1059         processFolderNode(node, selectedLangNode, newLang);
1060       } else if(hasNodeTypeNTResource(node)) {
1061         processWithDataChildNode(node, selectedLangNode, languagesNode, defaultLanguage, getChildNodeType(node)) ;
1062       }
1063       setVoteProperty(newLang, node, selectedLangNode) ;
1064       node.setProperty(EXO_LANGUAGE, language) ;
1065       setCommentNode(node, newLang, selectedLangNode) ;
1066       if(nodeTypeName.equals(NTFILE) || node.isNodeType(NTUNSTRUCTURED) ||
1067           node.isNodeType(NTFOLDER)) {
1068         languagesNode.getNode(language).remove() ;
1069       } else {
1070         selectedLangNode.remove() ;
1071       }
1072       node.save() ;
1073       node.getSession().save() ;
1074     }
1075   }
1076 
1077   /**
1078    * Exchange child node of current node with the node
1079    * @param node
1080    * @param selectedLangNode
1081    * @param newLang
1082    * @throws RepositoryException
1083    */
1084   private void processFolderNode(Node node, Node selectedLangNode,
1085       Node newLang) throws RepositoryException {
1086     NodeIterator nodeIter = node.getNodes();
1087     while(nodeIter.hasNext()) {
1088       Node child = nodeIter.nextNode();
1089       if(child.getName().equals(LANGUAGES)) continue;
1090       if(!node.getSession().itemExists(newLang.getPath() + "/" + child.getName()))
1091         node.getSession().move(child.getPath(), newLang.getPath() + "/" + child.getName());
1092     }
1093     NodeIterator selectedIter = selectedLangNode.getNodes();
1094     while(selectedIter.hasNext()) {
1095       Node child = selectedIter.nextNode();
1096       if(!node.getSession().itemExists(node.getPath() + "/" + child.getName()))
1097         node.getSession().move(child.getPath(), node.getPath() + "/" + child.getName());
1098     }
1099   }
1100 
1101   /**
1102    * Exchange child node of current node with the default node
1103    * @param node
1104    * @param newLang
1105    * @throws RepositoryException
1106    */
1107   private void processFolderNode(Node node, Node newLang) throws RepositoryException {
1108     NodeIterator nodeIter = node.getNodes();
1109     Node tempNode = newLang.addNode(TEMP_NODE, NTUNSTRUCTURED);
1110     Node selectedLangNode = newLang.getNode(node.getName());
1111     while(nodeIter.hasNext()) {
1112       Node child = nodeIter.nextNode();
1113       if(child.getName().equals(LANGUAGES)) continue;
1114       if(!node.getSession().itemExists(tempNode.getPath() + "/" + child.getName()))
1115         node.getSession().move(child.getPath(), tempNode.getPath() + "/" + child.getName());
1116     }
1117 
1118     NodeIterator selectedIter = selectedLangNode.getNodes();
1119     while(selectedIter.hasNext()) {
1120       Node child = selectedIter.nextNode();
1121       node.getSession().move(child.getPath(), node.getPath() + "/" + child.getName());
1122     }
1123     NodeIterator tempIter = tempNode.getNodes();
1124     while(tempIter.hasNext()) {
1125       Node child = tempIter.nextNode();
1126       if(!node.getSession().itemExists(selectedLangNode.getPath() + "/" + child.getName()))
1127         node.getSession().move(child.getPath(), selectedLangNode.getPath() + "/" + child.getName());
1128     }
1129     tempNode.remove();
1130   }
1131 
1132   /**
1133    * Exchange child node of current node with name = nodeType to
1134    * child node of selectedLangNode with the same name
1135    * @param node              current node
1136    * @param selectedLangNode  selected language node
1137    * @param languagesNode     language node
1138    * @param defaultLanguage   default language of node
1139    * @param nodeType
1140    * @throws Exception
1141    */
1142   private void processWithDataChildNode(Node node, Node selectedLangNode, Node languagesNode,
1143       String defaultLanguage, String nodeType) throws Exception {
1144     Node tempNode = node.addNode(TEMP_NODE, NTUNSTRUCTURED) ;
1145     if(!node.getSession().itemExists(tempNode.getPath() + "/" + nodeType))
1146       node.getSession().move(node.getNode(nodeType).getPath(), tempNode.getPath() + "/" + nodeType) ;
1147     if(!node.getSession().itemExists(node.getPath() + "/" + nodeType))
1148       node.getSession().move(selectedLangNode.getNode(nodeType).getPath(), node.getPath() + "/" + nodeType) ;
1149     if(!node.getSession().itemExists(languagesNode.getPath() + "/" + defaultLanguage + "/" + nodeType))
1150       node.getSession().move(tempNode.getNode(nodeType).getPath(),
1151                              languagesNode.getPath() + "/" + defaultLanguage + "/" + nodeType);
1152     tempNode.remove() ;
1153   }
1154 
1155   /**
1156    * Check whether child node with primary node type = nt:resource exists
1157    * @param node  current node
1158    * @return  true: exist
1159    *          false: not exist
1160    * @throws Exception
1161    */
1162   private boolean hasNodeTypeNTResource(Node node) throws Exception {
1163     if(node.hasNodes()) {
1164       NodeIterator nodeIter = node.getNodes() ;
1165       while(nodeIter.hasNext()) {
1166         Node childNode = nodeIter.nextNode() ;
1167         if(childNode.isNodeType("nt:resource")) return true ;
1168       }
1169     }
1170     return false ;
1171   }
1172 
1173   /**
1174    * Get name of child node of current node with name of PrimaryNodeType in child node = nt:resource
1175    * @param node  current node
1176    * @return name of child node if exist child node with PrimaryNodeType = nt:resource
1177    *         null if not exist
1178    * @throws Exception
1179    */
1180   private String getChildNodeType(Node node) throws Exception {
1181     if(node.hasNodes()) {
1182       NodeIterator nodeIter = node.getNodes() ;
1183       while(nodeIter.hasNext()) {
1184         Node childNode = nodeIter.nextNode() ;
1185         if(childNode.isNodeType("nt:resource")) return childNode.getName() ;
1186       }
1187     }
1188     return null ;
1189   }
1190   
1191   /**
1192    * {@inheritDoc}
1193    */
1194   public Node getLanguage(Node node, String language) throws Exception {
1195     if(node.hasNode(LANGUAGES + "/"+ language)) {
1196       Node target = node.getNode(LANGUAGES + "/"+ language) ;
1197       if (target.isNodeType("exo:symlink")) {
1198         LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
1199         target = linkManager.getTarget(target);
1200       }
1201       return target;
1202     }
1203     if (language.contains(COUNTRY_VARIANT)) {
1204       String pureLanguage = language.substring(0, language.indexOf(COUNTRY_VARIANT) ) ;
1205       if(node.hasNode(LANGUAGES + "/"+ pureLanguage)) {
1206         Node target = node.getNode(LANGUAGES + "/"+ pureLanguage) ;
1207         if (target.isNodeType("exo:symlink")) {
1208           LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
1209           target = linkManager.getTarget(target);
1210         }
1211         return target;
1212       }
1213     }
1214     return null;
1215   }
1216 
1217   public void addFolderLanguage(Node node, Map inputs, String language,
1218       boolean isDefault, String nodeType, String repositoryName) throws Exception {
1219     Node newLanguageNode = null ;
1220     Node languagesNode = null ;
1221     String defaultLanguage = getDefault(node) ;
1222     boolean isAddNew = false;
1223     if(node.hasNode(LANGUAGES)) {
1224       languagesNode = node.getNode(LANGUAGES) ;
1225     } else  {
1226       languagesNode = node.addNode(LANGUAGES, NTUNSTRUCTURED) ;
1227       if(languagesNode.canAddMixin("exo:hiddenable")) languagesNode.addMixin("exo:hiddenable");
1228     }
1229     if(!defaultLanguage.equals(language)){
1230       String addedLange = language;
1231       if(isDefault) addedLange = defaultLanguage;
1232       try {
1233         isAddNew = false;
1234         newLanguageNode = languagesNode.getNode(addedLange) ;
1235       } catch(PathNotFoundException e) {
1236         isAddNew = true;
1237         newLanguageNode = languagesNode.addNode(addedLange) ;
1238       }
1239     }
1240     String nodePath = cmsService_.storeNode(nodeType, newLanguageNode, inputs, isAddNew);
1241     Node selectedNode = (Node)node.getSession().getItem(nodePath);
1242     if(isAddNew) {
1243       setMixin(node, selectedNode, false);
1244       selectedNode.setProperty(EXO_LANGUAGE, language) ;
1245     }
1246     setPropertyLanguage(node, selectedNode, inputs, isDefault, defaultLanguage, language);
1247     if(isDefault) processFolderNode(node, newLanguageNode);
1248     node.getSession().save();
1249     if(isDefault && languagesNode.hasNode(language)) languagesNode.getNode(language).remove() ;
1250     languagesNode.save();
1251   }
1252 }