View Javadoc

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