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