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