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