View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.admininterface;
35  
36  import info.magnolia.cms.beans.config.ContentRepository;
37  import info.magnolia.cms.beans.runtime.MultipartForm;
38  import info.magnolia.cms.core.Content;
39  import info.magnolia.cms.core.HierarchyManager;
40  import info.magnolia.cms.core.ItemType;
41  import info.magnolia.cms.gui.dialog.Dialog;
42  import info.magnolia.cms.gui.dialog.DialogControlImpl;
43  import info.magnolia.cms.gui.dialog.DialogFactory;
44  import info.magnolia.cms.gui.i18n.I18nAuthoringSupport;
45  import info.magnolia.cms.gui.misc.Sources;
46  import info.magnolia.cms.i18n.Messages;
47  import info.magnolia.cms.i18n.MessagesManager;
48  import info.magnolia.cms.security.AccessDeniedException;
49  import info.magnolia.cms.servlets.MVCServletHandlerImpl;
50  import info.magnolia.cms.util.ExclusiveWrite;
51  import info.magnolia.cms.util.NodeDataUtil;
52  import info.magnolia.cms.util.RequestFormUtil;
53  import info.magnolia.context.MgnlContext;
54  import info.magnolia.objectfactory.Classes;
55  import info.magnolia.objectfactory.Components;
56  import info.magnolia.objectfactory.MgnlInstantiationException;
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 41137 2011-01-06 18:19:25Z gjoseph $
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 = ItemType.CONTENTNODE.getSystemName();
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     public String getCommand() {
177         if (form != null) {
178             return COMMAND_SAVE;
179         }
180         return COMMAND_SHOW_DIALOG;
181     }
182 
183     /**
184      * Calls createDialog and sets the common parameters on the dialog
185      */
186     public String showDialog() {
187         return VIEW_SHOW_DIALOG;
188     }
189 
190     protected void configureDialog(Dialog dialog) {
191         dialog.setConfig("dialog", getName()); //$NON-NLS-1$
192         dialog.setConfig("path", path); //$NON-NLS-1$
193         dialog.setConfig("nodeCollection", nodeCollectionName); //$NON-NLS-1$
194         dialog.setConfig("node", nodeName); //$NON-NLS-1$
195         dialog.setConfig("richE", richE); //$NON-NLS-1$
196         dialog.setConfig("richEPaste", richEPaste); //$NON-NLS-1$
197         dialog.setConfig("repository", repository); //$NON-NLS-1$
198         if (StringUtils.isNotEmpty(locale)) {
199             dialog.setConfig("locale", locale);
200         }
201 
202         i18nAuthoringSupport.i18nIze(dialog);
203     }
204 
205     /**
206      * Is called during showDialog(). Here can you create/ add controls for the dialog.
207      * @param configNode
208      * @param storageNode
209      * @throws RepositoryException
210      */
211     protected Dialog createDialog(Content configNode, Content storageNode) throws RepositoryException {
212         return DialogFactory.getDialogInstance(this.getRequest(), this.getResponse(), storageNode, configNode);
213     }
214 
215     /**
216      * Uses the SaveControl. Override to take influence.
217      * todo - this should take care of exceptions thrown while writing
218      * - onPreSave, Save and onPostSave should be allowed to throw an exception on failure
219      * @return close view name
220      */
221     public String save() {
222         if (!validate()) {
223             return onValidationFailed();
224         }
225         SaveHandler saveHandler = getSaveHandler();
226         if (!onPreSave(saveHandler)) {
227             return onSaveFailed();
228         }
229         if (!onSave(saveHandler)) {
230             return onSaveFailed();
231         }
232         synchronized (ExclusiveWrite.getInstance()) {
233             if (!onPostSave(saveHandler)) {
234                 return onSaveFailed();
235             }
236         }
237         removeSessionAttributes();
238         return VIEW_CLOSE_WINDOW;
239     }
240 
241     private String onValidationFailed() {
242         return showDialog();
243     }
244 
245     protected boolean validate() {
246         boolean passed = this.getDialog().validate();
247 
248         SaveHandler saveHandler = this.getSaveHandler();
249         if (saveHandler instanceof ValidatingSaveHandler) {
250             passed = ((ValidatingSaveHandler) saveHandler).validate();
251         }
252 
253         if (!passed) {
254             // after failing validation on creation of the paragraph, the paragraph info would get lost
255             if (StringUtils.isEmpty(this.dialog.getConfigValue("paragraph"))) {
256                 // make sure paragraph name is set even after reloading the dialog due to failed validation
257                 String paragraphName = params.getParameter("mgnlParagraph");
258                 this.dialog.setConfig("paragraph", paragraphName);
259             }
260         }
261 
262         return passed;
263     }
264 
265     /**
266      * Called if the save failed.
267      */
268     protected String onSaveFailed() {
269         return showDialog();
270     }
271 
272     /**
273      * Returns the save handler used by this dialog handler.
274      * @return the handler
275      */
276     protected SaveHandler getSaveHandler() {
277         if (this.saveHandler == null) {
278             createSaveHandler();
279 
280             configureSaveHandler(this.saveHandler);
281         }
282         return this.saveHandler;
283     }
284 
285     /**
286      * If there is the property saveHandler defined in the config it instantiates the configured save handler. Else it
287      * instantiates the default save handler
288      */
289     protected void createSaveHandler() {
290         Content configNode = this.getConfigNode();
291         if (configNode != null) {
292             String className = NodeDataUtil.getString(configNode, "saveHandler");
293             if (StringUtils.isNotEmpty(className)) {
294                 try {
295                     this.saveHandler = Classes.newInstance(className);
296                 }
297                 catch (MgnlInstantiationException e) {
298                     log.error("can't create save handler", e);
299                 }
300                 catch (ClassNotFoundException e) {
301                     log.error("can't create save handler", e);
302                 }
303             }
304         }
305 
306         if (this.saveHandler == null) {
307             this.saveHandler = Components.getComponentProvider().newInstance(SaveHandler.class);
308         }
309     }
310 
311     /**
312      * Configure the save control
313      */
314     protected void configureSaveHandler(SaveHandler saveHandler) {
315         saveHandler.init(form);
316 
317         saveHandler.setPath(form.getParameter("mgnlPath")); //$NON-NLS-1$
318         saveHandler.setNodeCollectionName(form.getParameter("mgnlNodeCollection")); //$NON-NLS-1$
319         saveHandler.setNodeName(form.getParameter("mgnlNode")); //$NON-NLS-1$
320         saveHandler.setParagraph(form.getParameter("mgnlParagraph")); //$NON-NLS-1$
321         saveHandler.setRepository(form.getParameter("mgnlRepository")); //$NON-NLS-1$
322         saveHandler.setCreationItemType(new ItemType(getItemType()));
323 
324         if (this.saveHandler instanceof DialogAwareSaveHandler) {
325             ((DialogAwareSaveHandler) saveHandler).setDialog(this.getDialog());
326         }
327     }
328 
329     protected boolean onPreSave(SaveHandler control) {
330         return true;
331     }
332 
333     protected boolean onSave(SaveHandler control) {
334         boolean result = control.save();
335         if (result) {
336             this.nodeName = control.getNodeName();
337         }
338         return result;
339     }
340 
341     protected boolean onPostSave(SaveHandler control) {
342         return true;
343     }
344 
345     /**
346      * Defines the node/page containing the data editing in this dialog. The default implementation is using the path
347      * parameter
348      */
349     public Content getStorageNode() {
350         // hm is null if this dialog is not used to show content
351         if (storageNode == null && hm != null) {
352             try {
353                 if(this.path == null){
354                     log.debug("No path defined for a dialog called by the url [{}]", this.getRequest().getRequestURL());
355                     return null;
356                 }
357                 Content parentContent = hm.getContent(path);
358                 if (StringUtils.isEmpty(nodeName)) {
359                     if (StringUtils.isEmpty(nodeCollectionName)) {
360                         storageNode = parentContent;
361                     }
362                     else {
363                         storageNode = parentContent.getContent(nodeCollectionName);
364                     }
365                 }
366                 else {
367                     if (StringUtils.isEmpty(nodeCollectionName)) {
368                         storageNode = parentContent.getContent(nodeName);
369 
370                     }
371                     else {
372                         storageNode = parentContent.getContent(nodeCollectionName).getContent(nodeName);
373 
374                     }
375                 }
376             }
377             catch(AccessDeniedException ade){
378                 log.error("can't read content to edit", ade);
379             }
380             catch (RepositoryException re) {
381                 // content does not exist yet
382                 log.debug("can't read content or it does not exist yet", re);
383             }
384         }
385         return storageNode;
386     }
387 
388     /**
389      * Returns the node with the dialog definition. Default: null
390      */
391     public Content getConfigNode() {
392         return null;
393     }
394 
395     /**
396      * @see info.magnolia.cms.servlets.MVCServletHandler#renderHtml(java.lang.String)
397      */
398     public void renderHtml(String view) throws IOException {
399         PrintWriter out = this.getResponse().getWriter();
400 
401         // after saving
402         if (VIEW_CLOSE_WINDOW.equals(view)) {
403 
404             out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" ");
405             out.write(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
406             out.println("<html>"); //$NON-NLS-1$
407             out.println(new Sources(this.getRequest().getContextPath()).getHtmlJs());
408             out.println("<script type=\"text/javascript\">"); //$NON-NLS-1$
409             out.println("var path = '" + this.path + "'");
410             out.println(StringUtils.defaultIfEmpty(getJsExecutedAfterSaving(), "mgnlDialogReloadOpener();")); //$NON-NLS-1$
411             out.println("window.close();"); //$NON-NLS-1$
412             out.println("</script></html>"); //$NON-NLS-1$
413         }
414         // show the created dialog
415         else if (VIEW_SHOW_DIALOG.equals(view)) {
416             try {
417                 getDialog().drawHtml(out);
418             }
419             catch (IOException e) {
420                 log.error("Exception caught", e);
421             }
422         }
423     }
424 
425     /**
426      * @return the default repository
427      */
428     public String getRepository() {
429         return ContentRepository.WEBSITE;
430     }
431 
432     public void removeSessionAttributes() {
433         String[] toRemove = form.getParameterValues(DialogControlImpl.SESSION_ATTRIBUTENAME_DIALOGOBJECT_REMOVE);
434         if (toRemove != null) {
435             for (int i = 0; i < toRemove.length; i++) {
436                 HttpSession httpsession = this.getRequest().getSession(false);
437                 if (httpsession != null) {
438                     httpsession.removeAttribute(toRemove[i]);
439                 }
440             }
441         }
442     }
443 
444     /**
445      * @param dialog The dialog to set.
446      */
447     public void setDialog(Dialog dialog) {
448         this.dialog = dialog;
449     }
450 
451     /**
452      * @return Returns the dialog.
453      */
454     public Dialog getDialog() {
455         if (this.dialog == null) {
456             try {
457                 this.dialog = createDialog(this.getConfigNode(), this.getStorageNode());
458             }
459             catch (RepositoryException e) {
460                 log.error("can't create dialog", e);
461             }
462             configureDialog(this.dialog);
463         }
464         return this.dialog;
465     }
466 
467 
468     public String getJsExecutedAfterSaving() {
469         return this.jsExecutedAfterSaving;
470     }
471 
472 
473     public void setJsExecutedAfterSaving(String jsExecutedAfterSaving) {
474         this.jsExecutedAfterSaving = jsExecutedAfterSaving;
475     }
476 
477 
478     public String getItemType() {
479         return this.itemType;
480     }
481 
482 
483     public void setItemType(String itemType) {
484         this.itemType = itemType;
485     }
486 }