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  import info.magnolia.objectfactory.Classes;
56  import info.magnolia.objectfactory.Components;
57  import info.magnolia.objectfactory.MgnlInstantiationException;
58  import info.magnolia.repository.RepositoryConstants;
59  
60  import java.io.FileInputStream;
61  import java.io.FileNotFoundException;
62  import java.io.IOException;
63  import java.io.InputStream;
64  import java.util.ArrayList;
65  import java.util.Calendar;
66  import java.util.GregorianCalendar;
67  import java.util.Iterator;
68  import java.util.List;
69  import java.util.Map;
70  import java.util.TimeZone;
71  import java.util.regex.Matcher;
72  import java.util.regex.Pattern;
73  
74  import javax.jcr.Node;
75  import javax.jcr.PathNotFoundException;
76  import javax.jcr.PropertyType;
77  import javax.jcr.RepositoryException;
78  import javax.jcr.Value;
79  import javax.jcr.ValueFactory;
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.ENCRYPTION_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)Components.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.ENCRYPTION_NO_ENCODING_BASE64) {
556             if (StringUtils.isNotBlank(valueStr)) {
557                 valueStr = new String(Base64.encodeBase64(valueStr.getBytes()));
558                 write = true;
559             }
560         } else if (encoding == ControlImpl.ENCRYPTION_HASH_SHA) {
561             if (StringUtils.isNotEmpty(valueStr)) {
562                 valueStr = Digester.getSHA1Hex(valueStr);
563                 write = true;
564             }
565         } else if (encoding == ControlImpl.ENCRYPTION_HASH_BCRYPT) {
566             if (StringUtils.isNotEmpty(valueStr)) {
567                 valueStr = Digester.getBCrypt(valueStr);
568                 write = true;
569             }
570         } else {
571             // no encoding
572             if (values == null || StringUtils.isEmpty(valueStr)) {
573                 remove = true;
574             } else {
575                 write = true;
576             }
577         }
578         if (remove) {
579             processRemoveCommon(node, name);
580         }
581         else if (write) {
582             processWriteCommon(node, name, valueStr, type);
583         }
584     }
585 
586     /**
587      * Remove the specified property on the node.
588      */
589     protected void processRemoveCommon(Content node, String name) throws PathNotFoundException, RepositoryException {
590         NodeData data = node.getNodeData(name);
591 
592         if (data.isExist()) {
593             node.deleteNodeData(name);
594         }
595     }
596 
597     /**
598      * Writes a property value.
599      * @param node the node
600      * @param name the property name to be written
601      * @param valueStr the value of the property
602      * @throws AccessDeniedException thrown if the write access is not granted
603      * @throws RepositoryException thrown if other repository exception is thrown
604      */
605     protected void processWriteCommon(Content node, String name, String valueStr, int type)
606     throws AccessDeniedException, RepositoryException {
607         Value value = this.getValue(valueStr, type);
608         if (null != value) {
609             NodeData data = NodeDataUtil.getOrCreate(node, name);
610             data.setValue(value);
611         }
612     }
613 
614     /**
615      * Process a multiple value field
616      * @param node
617      * @param name
618      * @param type
619      * @param values
620      * @throws RepositoryException
621      * @throws PathNotFoundException
622      * @throws AccessDeniedException
623      */
624     protected void processMultiple(Content node, String name, int type, String[] values) throws RepositoryException,
625     PathNotFoundException, AccessDeniedException {
626         // remove entire content node and (re-)write each
627         try {
628             node.delete(name);
629         }
630         catch (PathNotFoundException e) {
631             if (log.isDebugEnabled()) {
632                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
633             }
634         }
635         if (values != null && values.length != 0) {
636             Content multiNode = node.createContent(name, ItemType.CONTENTNODE);
637             try {
638                 // MetaData.CREATION_DATE has private access; no method to delete it so far...
639                 multiNode.deleteNodeData("creationdate"); //$NON-NLS-1$
640             }
641             catch (RepositoryException re) {
642                 if (log.isDebugEnabled()) {
643                     log.debug("Exception caught: " + re.getMessage(), re); //$NON-NLS-1$
644                 }
645             }
646             for (int j = 0; j < values.length; j++) {
647                 String valueStr = values[j];
648                 if (StringUtils.isNotEmpty(valueStr)) {
649                     Value value = this.getValue(valueStr, type);
650                     multiNode.createNodeData(Integer.toString(j)).setValue(value);
651                 }
652             }
653         }
654     }
655 
656     /**
657      * Process binary data. File- or imageupload.
658      * @param node
659      * @param name
660      * @throws PathNotFoundException
661      * @throws RepositoryException
662      * @throws AccessDeniedException
663      */
664     protected void processBinary(Content node, String name) throws PathNotFoundException, RepositoryException,
665     AccessDeniedException {
666         Document doc = getForm().getDocument(name);
667         if (doc == null && getForm().getParameter(name + "_" + File.REMOVE) != null) { //$NON-NLS-1$
668             try {
669                 node.deleteNodeData(name);
670             }
671             catch (RepositoryException re) {
672                 if (log.isDebugEnabled()) {
673                     log.debug("Exception caught: " + re.getMessage(), re); //$NON-NLS-1$
674                 }
675             }
676         }
677         else {
678             String fileName = getForm().getParameter(name + "_" + FileProperties.PROPERTY_FILENAME);
679             String template = getForm().getParameter(name + "_" + FileProperties.PROPERTY_TEMPLATE);
680 
681             SaveHandlerImpl.saveDocument(node, doc, name, fileName, template);
682         }
683     }
684 
685     /**
686      * Get a string value
687      * @param s
688      * @return the value
689      */
690     public Value getValue(String s) {
691         return this.getValue(s, PropertyType.STRING);
692     }
693 
694     /**
695      * Get the long value
696      * @param l
697      * @return the value
698      */
699     public Value getValue(long l) {
700         HierarchyManager hm = MgnlContext.getHierarchyManager(this.getRepository());
701         ValueFactory valueFactory;
702         try {
703             valueFactory = hm.getWorkspace().getSession().getValueFactory();
704         }
705         catch (RepositoryException e) {
706             throw new RuntimeException(e);
707         }
708         return valueFactory.createValue(l);
709     }
710 
711     /**
712      * Get the value for saving in jcr
713      * @param valueStr string representation of the value
714      * @param type type of the value
715      * @return the value
716      */
717     public Value getValue(String valueStr, int type) {
718 
719         ValueFactory valueFactory = null;
720 
721         HierarchyManager hm = MgnlContext.getHierarchyManager(this.getRepository());
722         try {
723             valueFactory = hm.getWorkspace().getSession().getValueFactory();
724         }
725         catch (RepositoryException e) {
726             throw new RuntimeException(e);
727         }
728 
729         Value value = null;
730 
731         if (type == PropertyType.REFERENCE) {
732             try {
733                 Node referencedNode = hm.getWorkspace().getSession().getNodeByUUID(valueStr);
734 
735                 value = valueFactory.createValue(referencedNode);
736             }
737             catch (RepositoryException re) {
738                 if (log.isDebugEnabled()) {
739                     log.debug("Cannot retrieve the referenced node by UUID: " + valueStr, re);
740                 }
741             }
742         }
743         else {
744             value = NodeDataUtil.createValue(valueStr, type, valueFactory);
745         }
746 
747         return value;
748     }
749 
750     /**
751      * @param value
752      * @param tagName
753      */
754     protected static String replacePByBr(final String value, String tagName) {
755 
756         if (StringUtils.isBlank(value)) {
757             return value;
758         }
759 
760         String fixedValue = value;
761 
762         String pre = "<" + tagName + ">"; //$NON-NLS-1$ //$NON-NLS-2$
763         String post = "</" + tagName + ">"; //$NON-NLS-1$ //$NON-NLS-2$
764 
765         // get rid of last </p>
766         if (fixedValue.endsWith(post)) {
767             fixedValue = StringUtils.substringBeforeLast(fixedValue, post);
768         }
769 
770         fixedValue = StringUtils.replace(fixedValue, pre + "&nbsp;" + post, "\n "); //$NON-NLS-1$ //$NON-NLS-2$
771         fixedValue = StringUtils.replace(fixedValue, pre, StringUtils.EMPTY);
772         fixedValue = StringUtils.replace(fixedValue, post, "\n\n "); //$NON-NLS-1$
773 
774         if (!tagName.equals(tagName.toUpperCase())) {
775             fixedValue = replacePByBr(fixedValue, tagName.toUpperCase());
776         }
777         return fixedValue;
778     }
779 
780     /**
781      * @see info.magnolia.module.admininterface.SaveHandler#isCreate()
782      */
783     @Override
784     public boolean isCreate() {
785         return create;
786     }
787 
788     /**
789      * @see info.magnolia.module.admininterface.SaveHandler#setCreate(boolean)
790      */
791     @Override
792     public void setCreate(boolean create) {
793         this.create = create;
794     }
795 
796     /**
797      * @see info.magnolia.module.admininterface.SaveHandler#getCreationItemType()
798      */
799     @Override
800     public ItemType getCreationItemType() {
801         return creationItemType;
802     }
803 
804     /**
805      * @see info.magnolia.module.admininterface.SaveHandler#setCreationItemType(info.magnolia.cms.core.ItemType)
806      */
807     @Override
808     public void setCreationItemType(ItemType creationItemType) {
809         this.creationItemType = creationItemType;
810     }
811 
812     @Override
813     public ItemType getCollectionNodeCreationItemType() {
814         return collectionNodeCreationItemType;
815     }
816 
817     @Override
818     public void setCollectionNodeCreationItemType(ItemType collectionNodeCreationItemType) {
819         this.collectionNodeCreationItemType = collectionNodeCreationItemType;
820     }
821 
822     /**
823      * @return the form containing the values passed
824      */
825     protected MultipartForm getForm() {
826         return form;
827     }
828 
829     /**
830      * set the from
831      * @param form containing the sended values
832      */
833     protected void setForm(MultipartForm form) {
834         this.form = form;
835     }
836 
837     /**
838      * set the name of the repository saving to
839      * @param repository the name of the repository
840      */
841     @Override
842     public void setRepository(String repository) {
843         this.repository = repository;
844     }
845 
846     /**
847      * get the name of thre repository saving to
848      * @return name
849      */
850     @Override
851     public String getRepository() {
852         return repository;
853     }
854 
855     /**
856      * Returns the page. The page is created if not yet existing depending on the property create
857      * @param hm
858      * @return the node
859      * @throws RepositoryException
860      * @throws AccessDeniedException
861      * @throws PathNotFoundException
862      */
863     protected Content getPageNode(HierarchyManager hm) throws RepositoryException, AccessDeniedException,
864     PathNotFoundException {
865         Content page = null;
866         String path = this.getPath();
867         try {
868             page = hm.getContent(path);
869         }
870         catch (RepositoryException e) {
871             if (this.isCreate()) {
872                 String parentPath = StringUtils.substringBeforeLast(path, "/"); //$NON-NLS-1$
873                 String label = StringUtils.substringAfterLast(path, "/"); //$NON-NLS-1$
874                 if (StringUtils.isEmpty(parentPath)) {
875                     page = hm.getRoot();
876                 }
877                 else {
878                     page = hm.getContent(parentPath);
879                 }
880                 page = page.createContent(label, this.getCreationItemType());
881             }
882             else {
883                 log.error("Tried to save a not existing node with path {}. use create = true to force creation", path); //$NON-NLS-1$
884             }
885         }
886 
887         return page;
888     }
889 
890     /**
891      * Gets or creates the node saving to.
892      * @param hm
893      * @param rootNode the node containing the saving node. If both the nodeCollectionName and the nodeName are empty
894      * this is the returned node.
895      * @return the node to which the content is saved
896      * @throws AccessDeniedException
897      * @throws RepositoryException
898      */
899     protected Content getSaveNode(HierarchyManager hm, Content rootNode) throws AccessDeniedException,
900     RepositoryException {
901         Content node = null;
902 
903         // get or create nodeCollection
904         Content nodeCollection = null;
905         if (StringUtils.isNotEmpty(this.getNodeCollectionName())) {
906             try {
907                 nodeCollection = rootNode.getContent(this.getNodeCollectionName());
908             }
909             catch (RepositoryException re) {
910                 // nodeCollection does not exist -> create
911                 nodeCollection = rootNode.createContent(this.getNodeCollectionName(), this.getCollectionNodeCreationItemType());
912                 log.debug("Create - {}" + nodeCollection.getHandle()); //$NON-NLS-1$
913             }
914         }
915         else {
916             nodeCollection = rootNode;
917         }
918 
919         // get or create node
920         if (StringUtils.isNotEmpty(this.getNodeName())) {
921             try {
922                 node = nodeCollection.getContent(this.getNodeName());
923             }
924             catch (RepositoryException re) {
925                 // node does not exist -> create
926                 if (this.getNodeName().equals("mgnlNew")) { //$NON-NLS-1$
927                     this.setNodeName(Path.getUniqueLabel(hm, nodeCollection.getHandle(), "0")); //$NON-NLS-1$
928                 }
929                 node = nodeCollection.createContent(this.getNodeName(), this.getCreationItemType());
930             }
931         }
932         else {
933             node = nodeCollection;
934         }
935         return node;
936     }
937 
938     /**
939      * Saves a uploaded file in the magnolia way. It creates a subnode name_properties where all the information like
940      * the mime type is stored.
941      * @param node the node under which the data is stored
942      * @param name the name of the nodedata to store the data into
943      * @param fileName If empty the original filename is used
944      * @param template can be empty
945      * @throws PathNotFoundException
946      * @throws RepositoryException
947      * @throws AccessDeniedException
948      */
949     public static void saveDocument(Content node, Document doc, String name, String fileName, String template)
950     throws PathNotFoundException, RepositoryException, AccessDeniedException {
951 
952         NodeData data = node.getNodeData(name);
953         if (doc != null) {
954             if (!data.isExist()) {
955                 data = node.createNodeData(name, PropertyType.BINARY);
956 
957                 log.debug("creating under - {}", node.getHandle()); //$NON-NLS-1$
958                 log.debug("creating node data for binary store - {}", name); //$NON-NLS-1$
959 
960             }
961             data.setValue(doc.getStream());
962             log.debug("Node data updated"); //$NON-NLS-1$
963         }
964         if (data != null) {
965             if (fileName == null || fileName.equals(StringUtils.EMPTY)) {
966                 fileName = doc.getFileName();
967             }
968             data.setAttribute(FileProperties.PROPERTY_FILENAME, fileName);
969             if (doc != null) {
970                 data.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, doc.getType());
971 
972                 Calendar value = new GregorianCalendar(TimeZone.getDefault());
973                 data.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, value);
974 
975                 data.setAttribute(FileProperties.PROPERTY_SIZE, Long.toString(doc.getLength()));
976 
977                 data.setAttribute(FileProperties.PROPERTY_EXTENSION, doc.getExtension());
978 
979                 data.setAttribute(FileProperties.PROPERTY_TEMPLATE, template);
980 
981                 InputStream raf = null;
982                 try {
983                     ImageInfo ii = new ImageInfo();
984                     raf = new FileInputStream(doc.getFile());
985                     ii.setInput(raf);
986                     if (ii.check()) {
987                         data.setAttribute(FileProperties.PROPERTY_WIDTH, Long.toString(ii.getWidth()));
988                         data.setAttribute(FileProperties.PROPERTY_HEIGHT, Long.toString(ii.getHeight()));
989                         // data.setAttribute(FileProperties.x, Long.toString(ii.getBitsPerPixel()));
990                     }
991                 }
992                 catch (FileNotFoundException e) {
993                     log.error("FileNotFoundException caught when parsing {}, image data will not be available", doc
994                             .getFile()
995                             .getAbsolutePath());
996                 }
997                 finally {
998                     IOUtils.closeQuietly(raf);
999                 }
1000 
1001                 // TODO: check this
1002                 // deleting all the documents in the form AFTER the complete save is done, since some other field save
1003                 // could need the same file.
1004                 // doc.delete();
1005             }
1006         }
1007 
1008     }
1009 
1010     /**
1011      * @see info.magnolia.module.admininterface.SaveHandler#getNodeCollectionName()
1012      */
1013     @Override
1014     public String getNodeCollectionName() {
1015         return this.nodeCollectionName;
1016     }
1017 
1018     /**
1019      * @see info.magnolia.module.admininterface.SaveHandler#setNodeCollectionName(java.lang.String)
1020      */
1021     @Override
1022     public void setNodeCollectionName(String nodeCollectionName) {
1023         this.nodeCollectionName = nodeCollectionName;
1024     }
1025 
1026     /**
1027      * @see info.magnolia.module.admininterface.SaveHandler#getNodeName()
1028      */
1029     @Override
1030     public String getNodeName() {
1031         return this.nodeName;
1032     }
1033 
1034     /**
1035      * @see info.magnolia.module.admininterface.SaveHandler#setNodeName(java.lang.String)
1036      */
1037     @Override
1038     public void setNodeName(String nodeName) {
1039         this.nodeName = nodeName;
1040     }
1041 
1042     /**
1043      * @see info.magnolia.module.admininterface.SaveHandler#getParagraph()
1044      */
1045     @Override
1046     public String getParagraph() {
1047         return this.paragraph;
1048     }
1049 
1050     /**
1051      * @see info.magnolia.module.admininterface.SaveHandler#setParagraph(java.lang.String)
1052      */
1053     @Override
1054     public void setParagraph(String paragraph) {
1055         this.paragraph = paragraph;
1056     }
1057 
1058     /**
1059      * @see info.magnolia.module.admininterface.SaveHandler#getPath()
1060      */
1061     @Override
1062     public String getPath() {
1063         return this.path;
1064     }
1065 
1066     /**
1067      * @see info.magnolia.module.admininterface.SaveHandler#setPath(java.lang.String)
1068      */
1069     @Override
1070     public void setPath(String path) {
1071         this.path = path;
1072     }
1073 
1074 }