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