View Javadoc

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