View Javadoc

1   /**
2    * This file Copyright (c) 2003-2012 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.admininterface;
35  
36  import info.magnolia.cms.beans.config.URI2RepositoryManager;
37  import info.magnolia.cms.beans.config.URI2RepositoryMapping;
38  import info.magnolia.cms.beans.runtime.Document;
39  import info.magnolia.cms.beans.runtime.FileProperties;
40  import info.magnolia.cms.beans.runtime.MultipartForm;
41  import info.magnolia.cms.core.Content;
42  import info.magnolia.cms.core.HierarchyManager;
43  import info.magnolia.cms.core.ItemType;
44  import info.magnolia.cms.core.MgnlNodeType;
45  import info.magnolia.cms.core.NodeData;
46  import info.magnolia.cms.core.Path;
47  import info.magnolia.cms.gui.control.ControlImpl;
48  import info.magnolia.cms.gui.control.File;
49  import info.magnolia.cms.gui.fckeditor.FCKEditorTmpFiles;
50  import info.magnolia.cms.security.AccessDeniedException;
51  import info.magnolia.cms.security.SecurityUtil;
52  import info.magnolia.cms.util.ContentUtil;
53  import info.magnolia.cms.util.DateUtil;
54  import info.magnolia.cms.util.ExclusiveWrite;
55  import info.magnolia.cms.util.NodeDataUtil;
56  import info.magnolia.context.MgnlContext;
57  import info.magnolia.link.LinkUtil;
58  import info.magnolia.objectfactory.Classes;
59  import info.magnolia.objectfactory.Components;
60  import info.magnolia.objectfactory.MgnlInstantiationException;
61  import info.magnolia.repository.RepositoryConstants;
62  
63  import java.io.FileInputStream;
64  import java.io.FileNotFoundException;
65  import java.io.IOException;
66  import java.io.InputStream;
67  import java.util.ArrayList;
68  import java.util.Calendar;
69  import java.util.Collection;
70  import java.util.GregorianCalendar;
71  import java.util.Iterator;
72  import java.util.List;
73  import java.util.Map;
74  import java.util.TimeZone;
75  import java.util.regex.Matcher;
76  import java.util.regex.Pattern;
77  
78  import javax.jcr.Node;
79  import javax.jcr.PathNotFoundException;
80  import javax.jcr.PropertyType;
81  import javax.jcr.RepositoryException;
82  import javax.jcr.Value;
83  import javax.jcr.ValueFactory;
84  
85  import org.apache.commons.codec.binary.Base64;
86  import org.apache.commons.io.FileUtils;
87  import org.apache.commons.io.IOUtils;
88  import org.apache.commons.lang.StringEscapeUtils;
89  import org.apache.commons.lang.StringUtils;
90  import org.devlib.schmidt.imageinfo.ImageInfo;
91  import org.slf4j.Logger;
92  import org.slf4j.LoggerFactory;
93  
94  
95  /**
96   * <p>
97   * This class handles the saving in the dialogs. It uses the mgnlSaveInfo parameters sent from the browser to store
98   * the data in the node.The structure of the parameter is the following: <br>
99   * <code>name, type, valueType, isRichEditValue, encoding</code>
100  * </p>
101  * <p>To find the consts see ControlImpl</p>
102  * <table>
103  * <tr>
104  * <td>name</td>
105  * <td>the name of the field</td>
106  * </tr>
107  * <tr>
108  * <td>type</td>
109  * <td>string, boolean, ...</td>
110  * </tr>
111  * <tr>
112  * <td>valueType</td>
113  * <td>single, multiple</td>
114  * </tr>
115  * <tr>
116  * <td>isRichEditValue</td>
117  * <td>value from an editor</td>
118  * </tr>
119  * <tr>
120  * <td>encoding</td>
121  * <td>base64, unix, none</td>
122  * </tr>
123  * </table>
124  * <p>
125  * You can specify a custom save handled (a class that implements info.magnolia.module.admininterface.CustomSaveHandler)
126  * by adding a "saveHandler" property to a dialog field configuration.
127  * </p>
128  * @author Vinzenz Wyser
129  * @version 2.0
130  */
131 public class SaveHandlerImpl implements SaveHandler {
132 
133     /**
134      * Logger
135      */
136     public static Logger log = LoggerFactory.getLogger(SaveHandlerImpl.class);
137 
138     /**
139      * The from, containing all the fields and files. This form is generated by magnolia.
140      */
141     private MultipartForm form;
142 
143     /**
144      * creates the node if it is not present
145      */
146     private boolean create;
147 
148     private ItemType creationItemType = ItemType.CONTENTNODE;
149     private ItemType collectionNodeCreationItemType = ItemType.CONTENTNODE;
150 
151     /**
152      * The name of the repository to store the data. Website is default.
153      */
154     private String repository = RepositoryConstants.WEBSITE;
155 
156     /**
157      * Path to the page
158      */
159     private String path;
160 
161     /**
162      * If the saved paragraph is saved to a node collection
163      */
164     private String nodeCollectionName;
165 
166     /**
167      * The name of the node saving to. mgnlNew if a new node should get creaetd
168      */
169     private String nodeName;
170 
171     /**
172      * The current paragraph type. This information is saved under the node to enable a proper rendering.
173      */
174     private String paragraph;
175 
176     /**
177      * Call init to initialize the object
178      */
179     public SaveHandlerImpl() {
180     }
181 
182     /**
183      * Initialize the SaveHandlerImpl control.
184      * @param form the form generated from the request due to handle multipart forms
185      */
186     @Override
187     public void init(MultipartForm form) {
188         this.setForm(form);
189     }
190 
191     /**
192      * @see info.magnolia.module.admininterface.SaveHandler#save()
193      */
194     @Override
195     public boolean save() {
196 
197         String[] saveInfos = getForm().getParameterValues("mgnlSaveInfo"); // name,type,propertyOrNode
198 
199         if (saveInfos == null) {
200             log.info("Nothing to save, mgnlSaveInfo parameter not found.");
201             return true;
202         }
203 
204         synchronized (ExclusiveWrite.getInstance()) {
205             // //$NON-NLS-1$
206             String path = this.getPath();
207 
208             HierarchyManager hm = MgnlContext.getHierarchyManager(this.getRepository());
209             try {
210                 // get the node to save
211                 Content page = this.getPageNode(hm);
212 
213                 if (page == null) {
214                     // an error should have been logged in getPageNode() avoid NPEs!
215                     return false;
216                 }
217 
218                 Content node = this.getSaveNode(hm, page);
219 
220                 // this value can get used later on to find this node
221                 this.setNodeName(node.getName());
222                 if (StringUtils.isEmpty(node.getMetaData().getTemplate())) {
223                     node.getMetaData().setTemplate(this.getParagraph());
224                 }
225 
226                 // loop all saveInfo controls; saveInfo format: name, type, valueType(single|multiple, )
227                 for (int i = 0; i < saveInfos.length; i++) {
228                     String saveInfo = saveInfos[i];
229                     processSaveInfo(node, saveInfo);
230                 }
231 
232                 // deleting all documents
233                 try {
234                     MultipartForm form = getForm();
235                     Map docs = form.getDocuments();
236                     Iterator iter = docs.keySet().iterator();
237                     while (iter.hasNext()) {
238                         form.getDocument((String) iter.next()).delete();
239                     }
240                 }
241                 catch (Exception e) {
242                     log.error("Could not delete temp documents from form");
243                 }
244 
245                 log.debug("Saving {}", path); //$NON-NLS-1$
246 
247                 // update meta data (e.g. last modified) of this paragraph and the page
248                 updateMetaData(node);
249 
250                 // FIX for MAGNOLIA-1814
251                 // mark page as changed also for nested paragraph changes
252                 if (this.getRepository().equals(RepositoryConstants.WEBSITE)) {
253                     while (node.getItemType().equals(ItemType.CONTENTNODE)) {
254                         node = node.getParent();
255                         node.updateMetaData();
256                     }
257                 }
258 
259                 hm.save();
260             }
261             catch (RepositoryException re) {
262                 log.error(re.getMessage(), re);
263                 return false;
264             }
265         }
266         return true;
267     }
268 
269     /**
270      * Update recursively the node Metadata until the parent node is of type
271      * mgnl:content or mgnl:file or deph=1.
272      * If it's not the 'website' workspace, do not perform recursivity.
273      */
274     private void updateMetaData(Content currentNode) throws AccessDeniedException, RepositoryException {
275         if(currentNode.isNodeType(MgnlNodeType.NT_FOLDER)) {
276             return;
277         }
278         // Update
279         currentNode.updateMetaData();
280         // Break or perform a recursive call
281         if(!RepositoryConstants.WEBSITE.equals(currentNode.getWorkspace().getName()) || currentNode.isNodeType(MgnlNodeType.NT_CONTENT) || currentNode.isNodeType(MgnlNodeType.NT_FOLDER) || currentNode.getLevel()<2) {
282             return;
283         } else {
284             updateMetaData(currentNode.getParent());
285         }
286     }
287 
288     /**
289      * This method cares about one mgnlSaveInfo. It adds the value to the node
290      * @param node node to add data
291      * @param saveInfo <code>name, type, valueType, isRichEditValue, encoding</code>
292      * @throws PathNotFoundException exception
293      * @throws RepositoryException exception
294      * @throws AccessDeniedException no access
295      */
296     protected void processSaveInfo(Content node, String saveInfo) throws PathNotFoundException, RepositoryException,
297     AccessDeniedException {
298 
299         String name;
300         int type = PropertyType.STRING;
301         int valueType = ControlImpl.VALUETYPE_SINGLE;
302         int isRichEditValue = 0;
303         int encoding = ControlImpl.ENCRYPTION_NO;
304         String[] values = {StringUtils.EMPTY};
305         if (StringUtils.contains(saveInfo, ',')) {
306             String[] info = StringUtils.split(saveInfo, ',');
307             name = info[0];
308             if (info.length >= 2) {
309                 type = PropertyType.valueFromName(info[1]);
310             }
311             if (info.length >= 3) {
312                 valueType = Integer.valueOf(info[2]).intValue();
313             }
314             if (info.length >= 4) {
315                 isRichEditValue = Integer.valueOf(info[3]).intValue();
316             }
317             if (info.length >= 5) {
318                 encoding = Integer.valueOf(info[4]).intValue();
319             }
320         }
321         else {
322             name = saveInfo;
323         }
324 
325         // handing of custom save handler start
326         String customSaveHandler = this.getForm().getParameter(name + "_saveHandler");
327         if (!StringUtils.isEmpty(customSaveHandler)) {
328             try {
329                 Class cshClazz = Classes.getClassFactory().forName(customSaveHandler);
330                 if (!FieldSaveHandler.class.isAssignableFrom(cshClazz)) {
331                     log.error("Class {} must implement the FieldSaveHandler interface", cshClazz);
332                     throw new ClassCastException("Class " + cshClazz + " must implement the FieldSaveHandler interface");
333                 }
334                 else {
335                     FieldSaveHandler csh = (FieldSaveHandler)Components.newInstance(cshClazz);
336                     HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.CONFIG);
337 
338                     String configPath = this.getForm().getParameter(name + "_configNode");
339                     Content configNode = null;
340                     if (StringUtils.isNotEmpty(configPath))
341                     {
342                         configNode = hm.getContent(configPath);
343                     }
344 
345                     csh.save(
346                             node,
347                             configNode,
348                             name,
349                             getForm(),
350                             type,
351                             valueType,
352                             isRichEditValue,
353                             encoding);
354                 }
355             }
356             catch (ClassNotFoundException e) {
357                 log.error("Error loading class " + customSaveHandler, e);
358             }
359             catch (MgnlInstantiationException e) {
360                 log.error("Error creating instance of class " + customSaveHandler, e);
361             }
362         }
363         else
364             // handing of custom save handler end
365 
366             if (type == PropertyType.BINARY) {
367                 processBinary(node, name);
368             }
369             else {
370                 values = getForm().getParameterValues(name);
371                 if (valueType == ControlImpl.VALUETYPE_MULTIPLE) {
372                     processMultiple(node, name, type, values);
373                 }
374                 else if (isRichEditValue != ControlImpl.RICHEDIT_NONE) {
375                     processRichEdit(node, name, type, isRichEditValue, encoding, values);
376                 }
377                 else if (type == PropertyType.DATE) {
378                     processDate(node, name, type, valueType, encoding, values);
379                 }
380                 else {
381                     processCommon(node, name, type, valueType, encoding, values);
382                 }
383             }
384     }
385 
386     protected void processDate(Content node, String name, int type, int valueType, int encoding, String[] values) {
387         try {
388             if (StringUtils.isEmpty(values[0])) {
389                 log.debug("Date has no value. Deleting node data {}", name);
390                 if (node.hasNodeData(name)) {
391                     node.deleteNodeData(name);
392                 }
393             }
394             else {
395                 Calendar utc = DateUtil.getUTCCalendarFromDialogString(values[0]);
396                 NodeDataUtil.getOrCreate(node, name).setValue(utc);
397             }
398         }
399         catch (Exception e) {
400             log.error("Could not update date value of node:" + node.getHandle() + " of property:" + name, e);
401         }
402     }
403 
404     /**
405      * Parse the value returned by a rich text editor and update the links and linebreaks.
406      * @param node
407      * @param name
408      * @param type
409      * @param isRichEditValue
410      * @param encoding
411      * @param values
412      * @throws PathNotFoundException
413      * @throws RepositoryException
414      * @throws AccessDeniedException
415      */
416     protected void processRichEdit(Content node, String name, int type, int isRichEditValue, int encoding,
417             String[] values) throws PathNotFoundException, RepositoryException, AccessDeniedException {
418         String valueStr = StringUtils.EMPTY;
419         if (values != null) {
420             valueStr = values[0]; // values is null when the expected field would not exis, e.g no
421         }
422 
423         valueStr = cleanLineBreaks(valueStr, isRichEditValue);
424 
425         if (isRichEditValue == ControlImpl.RICHEDIT_FCK) {
426             // MAGNOLIA-3384: remove scayt <span> http://dev.ckeditor.com/ticket/3560
427             valueStr = valueStr.replaceAll("<span scaytid=\"\\d*\" scayt_word=\".*?\">(.*?)</span>", "$1");
428             valueStr = updateLinks(node, name, valueStr);
429         }
430 
431         processString(node, name, type, encoding, values, valueStr);
432     }
433 
434     /**
435      * Clean up the linebreaks and
436      * <p>
437      * , <br>
438      * tags returned by the rich text editors
439      * @param valueStr
440      * @return the cleaned string
441      */
442     protected String cleanLineBreaks(String valueStr, int isRichEditValue) {
443         valueStr = StringUtils.replace(valueStr, "\r\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
444         valueStr = StringUtils.replace(valueStr, "\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
445 
446         // ie inserts some strange br...
447         valueStr = StringUtils.replace(valueStr, "</br>", StringUtils.EMPTY); //$NON-NLS-1$
448         valueStr = StringUtils.replace(valueStr, "<br>", "<br/>"); //$NON-NLS-1$ //$NON-NLS-2$
449         valueStr = StringUtils.replace(valueStr, "<BR>", "<br />"); //$NON-NLS-1$ //$NON-NLS-2$
450         valueStr = StringUtils.replace(valueStr, "<br/>", "<br />"); //$NON-NLS-1$ //$NON-NLS-2$
451         valueStr = StringUtils.replace(valueStr, "<P><br />", "<P>"); //$NON-NLS-1$ //$NON-NLS-2$
452 
453         // replace <P>
454         if (isRichEditValue != ControlImpl.RICHEDIT_FCK) {
455             valueStr = replacePByBr(valueStr, "p"); //$NON-NLS-1$
456         }
457         return valueStr;
458     }
459 
460     /**
461      * Update the links in a string returned by a rich text editor. If there are links to temporary files created due
462      * the fckeditor upload mechanism those filese are written to the node.
463      * @param node node saving to. used to save the files and fileinfod to
464      * @param name the name of the field. used to make a subnode for the files
465      * @param valueStr the value containing the links
466      * @return the cleaned value
467      * @throws AccessDeniedException
468      * @throws RepositoryException
469      * @throws PathNotFoundException
470      */
471     protected String updateLinks(Content node, String name, String valueStr) throws AccessDeniedException,
472     RepositoryException, PathNotFoundException {
473 
474         // process the images and uploaded files
475         HierarchyManager hm = MgnlContext.getHierarchyManager(this.getRepository());
476 
477         Pattern imageOrDowloadPattern = Pattern.compile("(<(a|img|embed)[^>]+(href|src)[ ]*=[ ]*\")([^\"]*)(\"[^>]*>)");
478         Pattern tmpFilePattern = Pattern.compile("/tmp/fckeditor/([^/]*)/[^\"]*");
479 
480         Content filesNode = ContentUtil.getOrCreateContent(node, name + "_files", ItemType.CONTENTNODE);
481 
482         // not usedFiles are removed after saving
483         List usedFiles = new ArrayList();
484 
485         // adapt img urls
486         Matcher imageOrDowloadMatcher = imageOrDowloadPattern.matcher(valueStr);
487         StringBuffer res = new StringBuffer();
488         while (imageOrDowloadMatcher.find()) {
489             String src = imageOrDowloadMatcher.group(4);
490 
491             String link = StringUtils.removeStart(src, MgnlContext.getContextPath());
492             link = StringEscapeUtils.unescapeHtml(link);
493 
494             // process the tmporary uploaded files
495             Matcher tmpFileMatcher = tmpFilePattern.matcher(src);
496 
497             if (tmpFileMatcher.find()) {
498                 String uuid = tmpFileMatcher.group(1);
499 
500                 Document doc = FCKEditorTmpFiles.getDocument(uuid);
501                 String fileNodeName = Path.getUniqueLabel(hm, filesNode.getHandle(), "file");
502                 Content fileNode = ContentUtil.getOrCreateContent(filesNode, fileNodeName, ItemType.CONTENTNODE);
503                 SaveHandlerImpl.saveDocument(fileNode, doc, "document", "", "");
504                 link = filesNode.getHandle() + "/" + fileNodeName + "/document/" + doc.getFileNameWithExtension();
505                 doc.delete();
506                 try {
507                     FileUtils.deleteDirectory(new java.io.File(Path.getTempDirectory() + "/fckeditor/" + uuid));
508                 }
509                 catch (IOException e) {
510                     log.error("can't delete tmp file [" + Path.getTempDirectory() + "/fckeditor/" + uuid + "]");
511                 }
512             }
513 
514             String prefixRepo = "";
515             String mappedUri = "";
516             URI2RepositoryManager uri2RepositoryManager = URI2RepositoryManager.getInstance();
517             if (this.repository.equals(uri2RepositoryManager.getDefaultMapping().getRepository())) {
518                 prefixRepo = uri2RepositoryManager.getDefaultMapping().getURIPrefix();
519             } else {
520                 Collection<URI2RepositoryMapping> mappings = uri2RepositoryManager.getMappings();
521                 for (URI2RepositoryMapping mapping : mappings) {
522                     if (mapping.getRepository().equals(this.repository)) {
523                         prefixRepo = mapping.getURIPrefix();
524                         if(!StringUtils.isEmpty(mapping.getHandlePrefix()) && link.startsWith(prefixRepo)){
525                             mappedUri = mapping.getURI(filesNode.getHandle());
526                             mappedUri = StringUtils.substringBeforeLast(mappedUri, ".");
527                         }
528                         break;
529                     }
530                 }
531             }
532 
533             prefixRepo = StringUtils.removeEnd(prefixRepo, "/");
534 
535             link = cleanupLinkPrefix(filesNode, link, prefixRepo);
536 
537             // internal uuid links have a leading $
538             link = StringUtils.replace(link, "$", "\\$");
539 
540             imageOrDowloadMatcher.appendReplacement(res, "$1" + link + "$5"); //$NON-NLS-1$
541 
542             if (link.startsWith(prefixRepo + filesNode.getHandle())) {
543                 String fileNodeName = StringUtils.removeStart(link, prefixRepo + filesNode.getHandle() + "/");
544                 fileNodeName = StringUtils.substringBefore(fileNodeName, "/");
545                 usedFiles.add(fileNodeName);
546             }else if (!StringUtils.isEmpty(mappedUri) && link.startsWith(mappedUri)){
547                 String fileNodeName = StringUtils.removeStart(link, mappedUri + "/");
548                 fileNodeName = StringUtils.substringBefore(fileNodeName, "/");
549                 usedFiles.add(fileNodeName);
550             }
551         }
552 
553         // delete not used files
554         for (Iterator iter = filesNode.getChildren().iterator(); iter.hasNext();) {
555             Content fileNodeData = (Content) iter.next();
556             if (!usedFiles.contains(fileNodeData.getName())) {
557                 fileNodeData.delete();
558             }
559         }
560 
561         imageOrDowloadMatcher.appendTail(res);
562         valueStr = res.toString();
563 
564         // encode the internal links to avoid dependences from the contextpath, position of the page
565         valueStr = LinkUtil.convertAbsoluteLinksToUUIDs(valueStr);
566 
567         return valueStr;
568     }
569 
570     protected String cleanupLinkPrefix(Content filesNode, String link, String prefixRepo) {
571         if (link.contains(filesNode.getHandle()) && !link.startsWith(prefixRepo + "/")) {
572             link = prefixRepo + link;
573         }
574         return link;
575     }
576 
577     /**
578      * Process a common value
579      * @param node node where the data must be stored
580      * @param name name of the field
581      * @param type type
582      * @param valueType internal value type (according to ControlImpl)
583      * @param encoding must we encode (base64)
584      * @param values all values belonging to this field
585      * @throws PathNotFoundException exception
586      * @throws RepositoryException exception
587      * @throws AccessDeniedException exception
588      */
589     protected void processCommon(Content node, String name, int type, int valueType, int encoding, String[] values)
590     throws PathNotFoundException, RepositoryException, AccessDeniedException {
591         String valueStr = StringUtils.EMPTY;
592         if (values != null) {
593             valueStr = values[0]; // values is null when the expected field would not exis, e.g no
594         }
595 
596         processString(node, name, type, encoding, values, valueStr);
597     }
598 
599     /**
600      * Process a string. This method will encode it
601      * @param node
602      * @param name
603      * @param type
604      * @param encoding
605      * @param values
606      * @param valueStr
607      * @throws PathNotFoundException
608      * @throws RepositoryException
609      * @throws AccessDeniedException
610      */
611     protected void processString(Content node, String name, int type, int encoding, String[] values, String valueStr)
612     throws PathNotFoundException, RepositoryException, AccessDeniedException {
613         // actualy encoding does only work for control password
614         boolean remove = false;
615         boolean write = false;
616         if (encoding == ControlImpl.ENCRYPTION_NO_ENCODING_BASE64) {
617             if (StringUtils.isNotBlank(valueStr)) {
618                 valueStr = new String(Base64.encodeBase64(valueStr.getBytes()));
619                 write = true;
620             }
621         } else if (encoding == ControlImpl.ENCRYPTION_HASH_SHA) {
622             if (StringUtils.isNotEmpty(valueStr)) {
623                 valueStr = SecurityUtil.getSHA1Hex(valueStr);
624                 write = true;
625             }
626         } else if (encoding == ControlImpl.ENCRYPTION_HASH_BCRYPT) {
627             if (StringUtils.isNotEmpty(valueStr)) {
628                 valueStr = SecurityUtil.getBCrypt(valueStr);
629                 write = true;
630             }
631         } else {
632             // no encoding
633             if (values == null || StringUtils.isEmpty(valueStr)) {
634                 remove = true;
635             } else {
636                 write = true;
637             }
638         }
639         if (remove) {
640             processRemoveCommon(node, name);
641         }
642         else if (write) {
643             processWriteCommon(node, name, valueStr, type);
644         }
645     }
646 
647     /**
648      * Remove the specified property on the node.
649      */
650     protected void processRemoveCommon(Content node, String name) throws PathNotFoundException, RepositoryException {
651         NodeData data = node.getNodeData(name);
652 
653         if (data.isExist()) {
654             node.deleteNodeData(name);
655         }
656     }
657 
658     /**
659      * Writes a property value.
660      * @param node the node
661      * @param name the property name to be written
662      * @param valueStr the value of the property
663      * @throws AccessDeniedException thrown if the write access is not granted
664      * @throws RepositoryException thrown if other repository exception is thrown
665      */
666     protected void processWriteCommon(Content node, String name, String valueStr, int type)
667     throws AccessDeniedException, RepositoryException {
668         Value value = this.getValue(valueStr, type);
669         if (null != value) {
670             NodeData data = NodeDataUtil.getOrCreate(node, name);
671             data.setValue(value);
672         }
673     }
674 
675     /**
676      * Process a multiple value field
677      * @param node
678      * @param name
679      * @param type
680      * @param values
681      * @throws RepositoryException
682      * @throws PathNotFoundException
683      * @throws AccessDeniedException
684      */
685     protected void processMultiple(Content node, String name, int type, String[] values) throws RepositoryException,
686     PathNotFoundException, AccessDeniedException {
687         // remove entire content node and (re-)write each
688         try {
689             node.delete(name);
690         }
691         catch (PathNotFoundException e) {
692             if (log.isDebugEnabled()) {
693                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
694             }
695         }
696         if (values != null && values.length != 0) {
697             Content multiNode = node.createContent(name, ItemType.CONTENTNODE);
698             try {
699                 // MetaData.CREATION_DATE has private access; no method to delete it so far...
700                 multiNode.deleteNodeData("creationdate"); //$NON-NLS-1$
701             }
702             catch (RepositoryException re) {
703                 if (log.isDebugEnabled()) {
704                     log.debug("Exception caught: " + re.getMessage(), re); //$NON-NLS-1$
705                 }
706             }
707             for (int j = 0; j < values.length; j++) {
708                 String valueStr = values[j];
709                 if (StringUtils.isNotEmpty(valueStr)) {
710                     Value value = this.getValue(valueStr, type);
711                     multiNode.createNodeData(Integer.toString(j)).setValue(value);
712                 }
713             }
714         }
715     }
716 
717     /**
718      * Process binary data. File- or imageupload.
719      * @param node
720      * @param name
721      * @throws PathNotFoundException
722      * @throws RepositoryException
723      * @throws AccessDeniedException
724      */
725     protected void processBinary(Content node, String name) throws PathNotFoundException, RepositoryException,
726     AccessDeniedException {
727         Document doc = getForm().getDocument(name);
728         if (doc == null && getForm().getParameter(name + "_" + File.REMOVE) != null) { //$NON-NLS-1$
729             try {
730                 node.deleteNodeData(name);
731             }
732             catch (RepositoryException re) {
733                 if (log.isDebugEnabled()) {
734                     log.debug("Exception caught: " + re.getMessage(), re); //$NON-NLS-1$
735                 }
736             }
737         }
738         else {
739             String fileName = getForm().getParameter(name + "_" + FileProperties.PROPERTY_FILENAME);
740             String template = getForm().getParameter(name + "_" + FileProperties.PROPERTY_TEMPLATE);
741 
742             SaveHandlerImpl.saveDocument(node, doc, name, fileName, template);
743         }
744     }
745 
746     /**
747      * Get a string value
748      * @param s
749      * @return the value
750      */
751     public Value getValue(String s) {
752         return this.getValue(s, PropertyType.STRING);
753     }
754 
755     /**
756      * Get the long value
757      * @param l
758      * @return the value
759      */
760     public Value getValue(long l) {
761         HierarchyManager hm = MgnlContext.getHierarchyManager(this.getRepository());
762         ValueFactory valueFactory;
763         try {
764             valueFactory = hm.getWorkspace().getSession().getValueFactory();
765         }
766         catch (RepositoryException e) {
767             throw new RuntimeException(e);
768         }
769         return valueFactory.createValue(l);
770     }
771 
772     /**
773      * Get the value for saving in jcr
774      * @param valueStr string representation of the value
775      * @param type type of the value
776      * @return the value
777      */
778     public Value getValue(String valueStr, int type) {
779 
780         ValueFactory valueFactory = null;
781 
782         HierarchyManager hm = MgnlContext.getHierarchyManager(this.getRepository());
783         try {
784             valueFactory = hm.getWorkspace().getSession().getValueFactory();
785         }
786         catch (RepositoryException e) {
787             throw new RuntimeException(e);
788         }
789 
790         Value value = null;
791 
792         if (type == PropertyType.REFERENCE) {
793             try {
794                 Node referencedNode = hm.getWorkspace().getSession().getNodeByUUID(valueStr);
795 
796                 value = valueFactory.createValue(referencedNode);
797             }
798             catch (RepositoryException re) {
799                 if (log.isDebugEnabled()) {
800                     log.debug("Cannot retrieve the referenced node by UUID: " + valueStr, re);
801                 }
802             }
803         }
804         else {
805             value = NodeDataUtil.createValue(valueStr, type, valueFactory);
806         }
807 
808         return value;
809     }
810 
811     /**
812      * @param value
813      * @param tagName
814      */
815     protected static String replacePByBr(final String value, String tagName) {
816 
817         if (StringUtils.isBlank(value)) {
818             return value;
819         }
820 
821         String fixedValue = value;
822 
823         String pre = "<" + tagName + ">"; //$NON-NLS-1$ //$NON-NLS-2$
824         String post = "</" + tagName + ">"; //$NON-NLS-1$ //$NON-NLS-2$
825 
826         // get rid of last </p>
827         if (fixedValue.endsWith(post)) {
828             fixedValue = StringUtils.substringBeforeLast(fixedValue, post);
829         }
830 
831         fixedValue = StringUtils.replace(fixedValue, pre + "&nbsp;" + post, "\n "); //$NON-NLS-1$ //$NON-NLS-2$
832         fixedValue = StringUtils.replace(fixedValue, pre, StringUtils.EMPTY);
833         fixedValue = StringUtils.replace(fixedValue, post, "\n\n "); //$NON-NLS-1$
834 
835         if (!tagName.equals(tagName.toUpperCase())) {
836             fixedValue = replacePByBr(fixedValue, tagName.toUpperCase());
837         }
838         return fixedValue;
839     }
840 
841     /**
842      * @see info.magnolia.module.admininterface.SaveHandler#isCreate()
843      */
844     @Override
845     public boolean isCreate() {
846         return create;
847     }
848 
849     /**
850      * @see info.magnolia.module.admininterface.SaveHandler#setCreate(boolean)
851      */
852     @Override
853     public void setCreate(boolean create) {
854         this.create = create;
855     }
856 
857     /**
858      * @see info.magnolia.module.admininterface.SaveHandler#getCreationItemType()
859      */
860     @Override
861     public ItemType getCreationItemType() {
862         return creationItemType;
863     }
864 
865     /**
866      * @see info.magnolia.module.admininterface.SaveHandler#setCreationItemType(info.magnolia.cms.core.ItemType)
867      */
868     @Override
869     public void setCreationItemType(ItemType creationItemType) {
870         this.creationItemType = creationItemType;
871     }
872 
873     @Override
874     public ItemType getCollectionNodeCreationItemType() {
875         return collectionNodeCreationItemType;
876     }
877 
878     @Override
879     public void setCollectionNodeCreationItemType(ItemType collectionNodeCreationItemType) {
880         this.collectionNodeCreationItemType = collectionNodeCreationItemType;
881     }
882 
883     /**
884      * @return the form containing the values passed
885      */
886     protected MultipartForm getForm() {
887         return form;
888     }
889 
890     /**
891      * set the from
892      * @param form containing the sended values
893      */
894     protected void setForm(MultipartForm form) {
895         this.form = form;
896     }
897 
898     /**
899      * set the name of the repository saving to
900      * @param repository the name of the repository
901      */
902     @Override
903     public void setRepository(String repository) {
904         this.repository = repository;
905     }
906 
907     /**
908      * get the name of thre repository saving to
909      * @return name
910      */
911     @Override
912     public String getRepository() {
913         return repository;
914     }
915 
916     /**
917      * Returns the page. The page is created if not yet existing depending on the property create
918      * @param hm
919      * @return the node
920      * @throws RepositoryException
921      * @throws AccessDeniedException
922      * @throws PathNotFoundException
923      */
924     protected Content getPageNode(HierarchyManager hm) throws RepositoryException, AccessDeniedException,
925     PathNotFoundException {
926         Content page = null;
927         String path = this.getPath();
928         try {
929             page = hm.getContent(path);
930         }
931         catch (RepositoryException e) {
932             if (this.isCreate()) {
933                 String parentPath = StringUtils.substringBeforeLast(path, "/"); //$NON-NLS-1$
934                 String label = StringUtils.substringAfterLast(path, "/"); //$NON-NLS-1$
935                 if (StringUtils.isEmpty(parentPath)) {
936                     page = hm.getRoot();
937                 }
938                 else {
939                     page = hm.getContent(parentPath);
940                 }
941                 page = page.createContent(label, this.getCreationItemType());
942             }
943             else {
944                 log.error("Tried to save a not existing node with path {}. use create = true to force creation", path); //$NON-NLS-1$
945             }
946         }
947 
948         return page;
949     }
950 
951     /**
952      * Gets or creates the node saving to.
953      * @param hm
954      * @param rootNode the node containing the saving node. If both the nodeCollectionName and the nodeName are empty
955      * this is the returned node.
956      * @return the node to which the content is saved
957      * @throws AccessDeniedException
958      * @throws RepositoryException
959      */
960     protected Content getSaveNode(HierarchyManager hm, Content rootNode) throws AccessDeniedException,
961     RepositoryException {
962         Content node = null;
963 
964         // get or create nodeCollection
965         Content nodeCollection = null;
966         if (StringUtils.isNotEmpty(this.getNodeCollectionName())) {
967             try {
968                 nodeCollection = rootNode.getContent(this.getNodeCollectionName());
969             }
970             catch (RepositoryException re) {
971                 // nodeCollection does not exist -> create
972                 nodeCollection = rootNode.createContent(this.getNodeCollectionName(), this.getCollectionNodeCreationItemType());
973                 log.debug("Create - {}" + nodeCollection.getHandle()); //$NON-NLS-1$
974             }
975         }
976         else {
977             nodeCollection = rootNode;
978         }
979 
980         // get or create node
981         if (StringUtils.isNotEmpty(this.getNodeName())) {
982             try {
983                 node = nodeCollection.getContent(this.getNodeName());
984             }
985             catch (RepositoryException re) {
986                 // node does not exist -> create
987                 if (this.getNodeName().equals("mgnlNew")) { //$NON-NLS-1$
988                     this.setNodeName(Path.getUniqueLabel(hm, nodeCollection.getHandle(), "0")); //$NON-NLS-1$
989                 }
990                 node = nodeCollection.createContent(this.getNodeName(), this.getCreationItemType());
991             }
992         }
993         else {
994             node = nodeCollection;
995         }
996         return node;
997     }
998 
999     /**
1000      * Saves a uploaded file in the magnolia way. It creates a subnode name_properties where all the information like
1001      * the mime type is stored.
1002      * @param node the node under which the data is stored
1003      * @param name the name of the nodedata to store the data into
1004      * @param fileName If empty the original or default title is used
1005      * @param template can be empty
1006      * @throws PathNotFoundException
1007      * @throws RepositoryException
1008      * @throws AccessDeniedException
1009      */
1010     public static void saveDocument(Content node, Document doc, String name, String fileName, String template)
1011     throws PathNotFoundException, RepositoryException, AccessDeniedException {
1012 
1013         NodeData data = node.getNodeData(name);
1014         if (doc != null) {
1015             if (!data.isExist()) {
1016                 data = node.createNodeData(name, PropertyType.BINARY);
1017 
1018                 log.debug("creating under - {}", node.getHandle()); //$NON-NLS-1$
1019                 log.debug("creating node data for binary store - {}", name); //$NON-NLS-1$
1020 
1021             }
1022             data.setValue(doc.getStream());
1023             log.debug("Node data updated"); //$NON-NLS-1$
1024         }
1025         if (data != null) {
1026             if ( StringUtils.isEmpty(fileName) ) {
1027                 fileName = (doc == null) ? null : doc.getFileName();
1028             }
1029 
1030             if (StringUtils.isEmpty(fileName)) {
1031                 fileName = node.getName(); //set the filename to node name if the title is null or empty
1032             }
1033 
1034             data.setAttribute(FileProperties.PROPERTY_FILENAME, fileName);
1035 
1036             if (doc != null) {
1037                 data.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, doc.getType());
1038 
1039                 Calendar value = new GregorianCalendar(TimeZone.getDefault());
1040                 data.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, value);
1041 
1042                 data.setAttribute(FileProperties.PROPERTY_SIZE, Long.toString(doc.getLength()));
1043 
1044                 data.setAttribute(FileProperties.PROPERTY_EXTENSION, doc.getExtension());
1045 
1046                 data.setAttribute(FileProperties.PROPERTY_TEMPLATE, template);
1047 
1048                 InputStream raf = null;
1049                 try {
1050                     ImageInfo ii = new ImageInfo();
1051                     raf = new FileInputStream(doc.getFile());
1052                     ii.setInput(raf);
1053                     if (ii.check()) {
1054                         data.setAttribute(FileProperties.PROPERTY_WIDTH, Long.toString(ii.getWidth()));
1055                         data.setAttribute(FileProperties.PROPERTY_HEIGHT, Long.toString(ii.getHeight()));
1056                         // data.setAttribute(FileProperties.x, Long.toString(ii.getBitsPerPixel()));
1057                     }
1058                 }
1059                 catch (FileNotFoundException e) {
1060                     log.error("FileNotFoundException caught when parsing {}, image data will not be available", doc
1061                             .getFile()
1062                             .getAbsolutePath());
1063                 }
1064                 finally {
1065                     IOUtils.closeQuietly(raf);
1066                 }
1067 
1068                 // TODO: check this
1069                 // deleting all the documents in the form AFTER the complete save is done, since some other field save
1070                 // could need the same file.
1071                 // doc.delete();
1072             }
1073         }
1074 
1075     }
1076 
1077     /**
1078      * @see info.magnolia.module.admininterface.SaveHandler#getNodeCollectionName()
1079      */
1080     @Override
1081     public String getNodeCollectionName() {
1082         return this.nodeCollectionName;
1083     }
1084 
1085     /**
1086      * @see info.magnolia.module.admininterface.SaveHandler#setNodeCollectionName(java.lang.String)
1087      */
1088     @Override
1089     public void setNodeCollectionName(String nodeCollectionName) {
1090         this.nodeCollectionName = nodeCollectionName;
1091     }
1092 
1093     /**
1094      * @see info.magnolia.module.admininterface.SaveHandler#getNodeName()
1095      */
1096     @Override
1097     public String getNodeName() {
1098         return this.nodeName;
1099     }
1100 
1101     /**
1102      * @see info.magnolia.module.admininterface.SaveHandler#setNodeName(java.lang.String)
1103      */
1104     @Override
1105     public void setNodeName(String nodeName) {
1106         this.nodeName = nodeName;
1107     }
1108 
1109     /**
1110      * @see info.magnolia.module.admininterface.SaveHandler#getParagraph()
1111      */
1112     @Override
1113     public String getParagraph() {
1114         return this.paragraph;
1115     }
1116 
1117     /**
1118      * @see info.magnolia.module.admininterface.SaveHandler#setParagraph(java.lang.String)
1119      */
1120     @Override
1121     public void setParagraph(String paragraph) {
1122         this.paragraph = paragraph;
1123     }
1124 
1125     /**
1126      * @see info.magnolia.module.admininterface.SaveHandler#getPath()
1127      */
1128     @Override
1129     public String getPath() {
1130         return this.path;
1131     }
1132 
1133     /**
1134      * @see info.magnolia.module.admininterface.SaveHandler#setPath(java.lang.String)
1135      */
1136     @Override
1137     public void setPath(String path) {
1138         this.path = path;
1139     }
1140 
1141 }