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