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.runtime.MultipartForm;
37  import info.magnolia.cms.core.Content;
38  import info.magnolia.cms.core.HierarchyManager;
39  import info.magnolia.cms.core.ItemType;
40  import info.magnolia.cms.gui.dialog.Dialog;
41  import info.magnolia.cms.gui.dialog.DialogControlImpl;
42  import info.magnolia.cms.gui.dialog.DialogFactory;
43  import info.magnolia.cms.gui.i18n.I18nAuthoringSupport;
44  import info.magnolia.cms.gui.misc.Sources;
45  import info.magnolia.cms.i18n.Messages;
46  import info.magnolia.cms.i18n.MessagesManager;
47  import info.magnolia.cms.security.AccessDeniedException;
48  import info.magnolia.cms.servlets.MVCServletHandlerImpl;
49  import info.magnolia.cms.util.ExclusiveWrite;
50  import info.magnolia.cms.util.NodeDataUtil;
51  import info.magnolia.cms.util.RequestFormUtil;
52  import info.magnolia.context.MgnlContext;
53  import info.magnolia.objectfactory.Classes;
54  import info.magnolia.objectfactory.Components;
55  import info.magnolia.objectfactory.MgnlInstantiationException;
56  import info.magnolia.repository.RepositoryConstants;
57  
58  import java.io.IOException;
59  import java.io.PrintWriter;
60  
61  import javax.jcr.RepositoryException;
62  import javax.servlet.http.HttpServletRequest;
63  import javax.servlet.http.HttpServletResponse;
64  import javax.servlet.http.HttpSession;
65  
66  import org.apache.commons.lang.StringUtils;
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69  
70  
71  /**
72   * This is the MVCHandler for dialogs. You can make a subclass to take influence on creation or saving.
73   * @author Philipp Bracher
74   * @version $Id$
75   */
76  
77  public class DialogMVCHandler extends MVCServletHandlerImpl {
78  
79      /**
80       * Logger.
81       */
82      private static Logger log = LoggerFactory.getLogger(DialogMVCHandler.class);
83  
84      /*
85       * Commands
86       */
87      protected static final String COMMAND_SAVE = "save"; //$NON-NLS-1$
88  
89      protected static final String COMMAND_SELECT_PARAGRAPH = "selectParagraph"; //$NON-NLS-1$
90  
91      protected static final String COMMAND_SHOW_DIALOG = "showDialog"; //$NON-NLS-1$
92  
93      /*
94       * Views
95       */
96      protected static final String VIEW_CLOSE_WINDOW = "close"; //$NON-NLS-1$
97  
98      protected static final String VIEW_SHOW_DIALOG = "show"; //$NON-NLS-1$
99  
100     /**
101      * The posted multipart form. Use params for easy access.
102      */
103     protected MultipartForm form;
104 
105     /**
106      * Path to the node containing the data
107      */
108     protected String path = StringUtils.EMPTY;
109 
110     /**
111      * If the dialog serves a collection (multiple instances of the same dialog)
112      */
113     private String nodeCollectionName = StringUtils.EMPTY;
114 
115     /**
116      * the node containing the date for this dialog
117      */
118     protected String nodeName = StringUtils.EMPTY;
119 
120     protected String richE = StringUtils.EMPTY;
121 
122     protected String richEPaste = StringUtils.EMPTY;
123 
124     protected String repository = StringUtils.EMPTY;
125 
126     protected String locale;
127 
128     protected HierarchyManager hm;
129 
130     private Dialog dialog;
131 
132     protected Messages msgs;
133 
134     protected RequestFormUtil params;
135 
136     protected Content storageNode;
137 
138     private SaveHandler saveHandler;
139 
140     private String jsExecutedAfterSaving;
141 
142     private String itemType;
143 
144     private final I18nAuthoringSupport i18nAuthoringSupport;
145 
146     protected String treeBasePath;
147 
148     /**
149      * Initialize the used parameters: path, nodeCollectionName, nodeName, ..
150      * @param request
151      * @param response
152      */
153     public DialogMVCHandler(String name, HttpServletRequest request, HttpServletResponse response) {
154         super(name, request, response);
155 
156         form = MgnlContext.getPostedForm();
157         if(form != null || request != null){
158             params = new RequestFormUtil(request, form);
159 
160             path = params.getParameter("mgnlPath"); //$NON-NLS-1$
161             nodeCollectionName = params.getParameter("mgnlNodeCollection"); //$NON-NLS-1$
162             nodeName = params.getParameter("mgnlNode"); //$NON-NLS-1$
163             richE = params.getParameter("mgnlRichE"); //$NON-NLS-1$
164             richEPaste = params.getParameter("mgnlRichEPaste"); //$NON-NLS-1$
165             repository = params.getParameter("mgnlRepository", getRepository()); //$NON-NLS-1$
166             locale = params.getParameter("mgnlLocale"); //$NON-NLS-1$
167             treeBasePath = params.getParameter("mgnlTreeBasePath");
168             if (StringUtils.isNotEmpty(repository)) {
169                 hm = MgnlContext.getHierarchyManager(repository);
170             }
171         }
172         msgs = MessagesManager.getMessages();
173         // proxied. no need to refresh instance every time.
174         i18nAuthoringSupport = I18nAuthoringSupport.Factory.getInstance();
175 
176     }
177 
178     /*
179      * @see info.magnolia.cms.servlets.MVCServletHandler#getCommand()
180      */
181     @Override
182     public String getCommand() {
183         if (form != null) {
184             return COMMAND_SAVE;
185         }
186         return COMMAND_SHOW_DIALOG;
187     }
188 
189     /**
190      * Calls createDialog and sets the common parameters on the dialog
191      */
192     public String showDialog() {
193         return VIEW_SHOW_DIALOG;
194     }
195 
196     protected void configureDialog(Dialog dialog) {
197         dialog.setConfig("dialog", getName()); //$NON-NLS-1$
198         dialog.setConfig("path", path); //$NON-NLS-1$
199         dialog.setConfig("nodeCollection", nodeCollectionName); //$NON-NLS-1$
200         dialog.setConfig("node", nodeName); //$NON-NLS-1$
201         dialog.setConfig("richE", richE); //$NON-NLS-1$
202         dialog.setConfig("richEPaste", richEPaste); //$NON-NLS-1$
203         dialog.setConfig("repository", repository); //$NON-NLS-1$
204         dialog.setConfig("mgnlTreeBasePath", treeBasePath);
205         if (StringUtils.isNotEmpty(locale)) {
206             dialog.setConfig("locale", locale);
207         }
208 
209         i18nAuthoringSupport.i18nIze(dialog);
210     }
211 
212     /**
213      * Is called during showDialog(). Here can you create/ add controls for the dialog.
214      * @param configNode
215      * @param storageNode
216      * @throws RepositoryException
217      */
218     protected Dialog createDialog(Content configNode, Content storageNode) throws RepositoryException {
219         return DialogFactory.getDialogInstance(this.getRequest(), this.getResponse(), storageNode, configNode);
220     }
221 
222     /**
223      * Uses the SaveControl. Override to take influence.
224      * todo - this should take care of exceptions thrown while writing
225      * - onPreSave, Save and onPostSave should be allowed to throw an exception on failure
226      * @return close view name
227      */
228     public String save() {
229         if (!validate()) {
230             return onValidationFailed();
231         }
232         SaveHandler saveHandler = getSaveHandler();
233         if (!onPreSave(saveHandler)) {
234             return onSaveFailed();
235         }
236         if (!onSave(saveHandler)) {
237             return onSaveFailed();
238         }
239         synchronized (ExclusiveWrite.getInstance()) {
240             if (!onPostSave(saveHandler)) {
241                 return onSaveFailed();
242             }
243         }
244         removeSessionAttributes();
245         return VIEW_CLOSE_WINDOW;
246     }
247 
248     private String onValidationFailed() {
249         return showDialog();
250     }
251 
252     protected boolean validate() {
253         boolean passed = this.getDialog().validate();
254 
255         SaveHandler saveHandler = this.getSaveHandler();
256         if ((passed) && (saveHandler instanceof ValidatingSaveHandler)) {
257             passed = ((ValidatingSaveHandler) saveHandler).validate();
258         }
259 
260         if (!passed) {
261             // after failing validation on creation of the paragraph, the paragraph info would get lost
262             if (StringUtils.isEmpty(this.dialog.getConfigValue("paragraph"))) {
263                 // make sure paragraph name is set even after reloading the dialog due to failed validation
264                 String paragraphName = params.getParameter("mgnlParagraph");
265                 this.dialog.setConfig("paragraph", paragraphName);
266             }
267             if (StringUtils.isEmpty(this.dialog.getConfigValue("collectionNodeCreationItemType"))) {
268                 this.dialog.setConfig("collectionNodeCreationItemType", params.getParameter("mgnlCollectionNodeCreationItemType"));
269             }
270             if (StringUtils.isEmpty(this.dialog.getConfigValue("creationItemType"))) {
271                 this.dialog.setConfig("creationItemType", params.getParameter("mgnlCreationItemType"));
272             }
273         }
274 
275         return passed;
276     }
277 
278     /**
279      * Called if the save failed.
280      */
281     protected String onSaveFailed() {
282         return showDialog();
283     }
284 
285     /**
286      * Returns the save handler used by this dialog handler.
287      * @return the handler
288      */
289     protected SaveHandler getSaveHandler() {
290         if (this.saveHandler == null) {
291             createSaveHandler();
292 
293             configureSaveHandler(this.saveHandler);
294         }
295         return this.saveHandler;
296     }
297 
298     /**
299      * If there is the property saveHandler defined in the config it instantiates the configured save handler. Else it
300      * instantiates the default save handler
301      */
302     protected void createSaveHandler() {
303         Content configNode = this.getConfigNode();
304         if (configNode != null) {
305             String className = NodeDataUtil.getString(configNode, "saveHandler");
306             if (StringUtils.isNotEmpty(className)) {
307                 try {
308                     this.saveHandler = Classes.newInstance(className);
309                 }
310                 catch (MgnlInstantiationException e) {
311                     log.error("can't create save handler", e);
312                 }
313                 catch (ClassNotFoundException e) {
314                     log.error("can't create save handler", e);
315                 }
316             }
317         }
318 
319         if (this.saveHandler == null) {
320             this.saveHandler = Components.getComponentProvider().newInstance(SaveHandler.class);
321         }
322     }
323 
324     /**
325      * Configure the save control
326      */
327     protected void configureSaveHandler(SaveHandler saveHandler) {
328         saveHandler.init(form);
329 
330         saveHandler.setPath(form.getParameter("mgnlPath")); //$NON-NLS-1$
331         saveHandler.setNodeCollectionName(form.getParameter("mgnlNodeCollection")); //$NON-NLS-1$
332         saveHandler.setNodeName(form.getParameter("mgnlNode")); //$NON-NLS-1$
333         saveHandler.setParagraph(form.getParameter("mgnlParagraph")); //$NON-NLS-1$
334         saveHandler.setRepository(form.getParameter("mgnlRepository")); //$NON-NLS-1$
335         if (StringUtils.isNotEmpty(form.getParameter("mgnlCollectionNodeCreationItemType"))) {
336             saveHandler.setCollectionNodeCreationItemType(new ItemType(form.getParameter("mgnlCollectionNodeCreationItemType")));
337         }
338         if (StringUtils.isNotEmpty(getItemType())) {
339             saveHandler.setCreationItemType(new ItemType(getItemType()));
340         } else if (StringUtils.isNotEmpty(form.getParameter("mgnlCreationItemType"))) {
341             saveHandler.setCreationItemType(new ItemType(form.getParameter("mgnlCreationItemType")));
342         }
343 
344         if (this.saveHandler instanceof DialogAwareSaveHandler) {
345             ((DialogAwareSaveHandler) saveHandler).setDialog(this.getDialog());
346         }
347     }
348 
349     protected boolean onPreSave(SaveHandler control) {
350         return true;
351     }
352 
353     protected boolean onSave(SaveHandler control) {
354         boolean result = control.save();
355         if (result) {
356             this.nodeName = control.getNodeName();
357         }
358         return result;
359     }
360 
361     protected boolean onPostSave(SaveHandler control) {
362         return true;
363     }
364 
365     /**
366      * Defines the node/page containing the data editing in this dialog. The default implementation is using the path
367      * parameter
368      */
369     public Content getStorageNode() {
370         // hm is null if this dialog is not used to show content
371         if (storageNode == null && hm != null) {
372             try {
373                 if(this.path == null){
374                     log.debug("No path defined for a dialog called by the url [{}]", this.getRequest().getRequestURL());
375                     return null;
376                 }
377                 Content parentContent = hm.getContent(path);
378                 if (StringUtils.isEmpty(nodeName)) {
379                     if (StringUtils.isEmpty(nodeCollectionName)) {
380                         storageNode = parentContent;
381                     }
382                     else {
383                         storageNode = parentContent.getContent(nodeCollectionName);
384                     }
385                 }
386                 else {
387                     if (StringUtils.isEmpty(nodeCollectionName)) {
388                         storageNode = parentContent.getContent(nodeName);
389 
390                     }
391                     else {
392                         storageNode = parentContent.getContent(nodeCollectionName).getContent(nodeName);
393 
394                     }
395                 }
396             }
397             catch(AccessDeniedException ade){
398                 log.error("can't read content to edit", ade);
399             }
400             catch (RepositoryException re) {
401                 // content does not exist yet
402                 log.debug("can't read content or it does not exist yet", re);
403             }
404         }
405         return storageNode;
406     }
407 
408     /**
409      * Returns the node with the dialog definition. Default: null
410      */
411     public Content getConfigNode() {
412         return null;
413     }
414 
415     /**
416      * @see info.magnolia.cms.servlets.MVCServletHandler#renderHtml(java.lang.String)
417      */
418     @Override
419     public void renderHtml(String view) throws IOException {
420         PrintWriter out = this.getResponse().getWriter();
421 
422         // after saving
423         if (VIEW_CLOSE_WINDOW.equals(view)) {
424 
425             out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" ");
426             out.write(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
427             out.println("<html>"); //$NON-NLS-1$
428             out.println(new Sources(this.getRequest().getContextPath()).getHtmlJs());
429             out.println("<script type=\"text/javascript\">"); //$NON-NLS-1$
430             out.println("var path = '" + this.path + "'");
431             out.println(StringUtils.defaultIfEmpty(getJsExecutedAfterSaving(), "mgnlDialogReloadOpener();")); //$NON-NLS-1$
432             out.println("window.close();"); //$NON-NLS-1$
433             out.println("</script></html>"); //$NON-NLS-1$
434         }
435         // show the created dialog
436         else if (VIEW_SHOW_DIALOG.equals(view)) {
437             try {
438                 getDialog().drawHtml(out);
439             }
440             catch (IOException e) {
441                 log.error("Exception caught", e);
442             }
443         }
444     }
445 
446     /**
447      * @return the default repository
448      */
449     public String getRepository() {
450         return RepositoryConstants.WEBSITE;
451     }
452 
453     public void removeSessionAttributes() {
454         String[] toRemove = form.getParameterValues(DialogControlImpl.SESSION_ATTRIBUTENAME_DIALOGOBJECT_REMOVE);
455         if (toRemove != null) {
456             for (int i = 0; i < toRemove.length; i++) {
457                 HttpSession httpsession = this.getRequest().getSession(false);
458                 if (httpsession != null) {
459                     httpsession.removeAttribute(toRemove[i]);
460                 }
461             }
462         }
463     }
464 
465     /**
466      * @param dialog The dialog to set.
467      */
468     public void setDialog(Dialog dialog) {
469         this.dialog = dialog;
470     }
471 
472     /**
473      * @return Returns the dialog.
474      */
475     public Dialog getDialog() {
476         if (this.dialog == null) {
477             try {
478                 this.dialog = createDialog(this.getConfigNode(), this.getStorageNode());
479             }
480             catch (RepositoryException e) {
481                 log.error("can't create dialog", e);
482             }
483             configureDialog(this.dialog);
484         }
485         return this.dialog;
486     }
487 
488 
489     public String getJsExecutedAfterSaving() {
490         return this.jsExecutedAfterSaving;
491     }
492 
493 
494     public void setJsExecutedAfterSaving(String jsExecutedAfterSaving) {
495         this.jsExecutedAfterSaving = jsExecutedAfterSaving;
496     }
497 
498 
499     public String getItemType() {
500         return this.itemType;
501     }
502 
503 
504     public void setItemType(String itemType) {
505         this.itemType = itemType;
506     }
507 }