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.cms.gui.dialog;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.core.ItemType;
38  import info.magnolia.cms.core.NodeData;
39  import info.magnolia.cms.i18n.Messages;
40  import info.magnolia.cms.i18n.MessagesManager;
41  import info.magnolia.cms.i18n.MessagesUtil;
42  import info.magnolia.cms.util.AlertUtil;
43  import info.magnolia.cms.util.ContentUtil;
44  import info.magnolia.cms.util.NodeDataUtil;
45  import info.magnolia.cms.util.RequestFormUtil;
46  
47  import java.io.IOException;
48  import java.io.Writer;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.Comparator;
53  import java.util.Date;
54  import java.util.Hashtable;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Map;
58  import java.util.regex.Pattern;
59  
60  import javax.jcr.PathNotFoundException;
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.BooleanUtils;
67  import org.apache.commons.lang.StringUtils;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  /**
72   * @author Vinzenz Wyser
73   * @version 2.0
74   */
75  public abstract class DialogControlImpl implements DialogControl {
76  
77      private static final String REQUIRED_PROPERTY = "required";
78  
79      public static final String VALIDATION_PATTERN_PROPERTY = "validationPattern";
80  
81      public static final String DEFAULT_VALUE_PROPERTY = "defaultValue";
82  
83      private static final String I18N_BASENAME_PROPERTY = "i18nBasename";
84  
85      public static final String SESSION_ATTRIBUTENAME_DIALOGOBJECT = "mgnlSessionAttribute"; //$NON-NLS-1$
86  
87      public static final String SESSION_ATTRIBUTENAME_DIALOGOBJECT_REMOVE = "mgnlSessionAttributeRemove"; //$NON-NLS-1$
88  
89      /**
90       * Logger.
91       */
92      private static Logger log = LoggerFactory.getLogger(DialogControlImpl.class);
93  
94      /**
95       * Current request.
96       */
97      private HttpServletRequest request;
98  
99      /**
100      * Current response.
101      */
102     private HttpServletResponse response;
103 
104     /**
105      * content data.
106      */
107     private Content storageNode;
108 
109     /**
110      * config data.
111      */
112     private Map config = new Hashtable();
113 
114     /**
115      * Sub controls.
116      */
117     private final List subs = new ArrayList();
118 
119     /**
120      * options (radio, checkbox...).
121      */
122     private List options = new ArrayList();
123 
124     /**
125      * The id is used to make the controls unic. Used by the javascripts. This is not a configurable value. See method
126      * set and getName().
127      */
128     private String id = "mgnlControl"; //$NON-NLS-1$
129 
130     protected String value;
131 
132     /**
133      * multiple values, e.g. checkbox.
134      */
135     private List values;
136 
137     private DialogControlImpl parent;
138 
139     private DialogControlImpl topParent;
140 
141     /**
142      * Used if this control has its own message bundle defined or if this is the dialog object itself. Use getMessages
143      * method to get the object for a control.
144      */
145     private Messages messages;
146 
147     /**
148      */
149     public void init(HttpServletRequest request, HttpServletResponse response, Content storageNode, Content configNode)
150             throws RepositoryException {
151 
152         if (log.isDebugEnabled()) {
153             log.debug("Init " + getClass().getName()); //$NON-NLS-1$
154         }
155 
156         this.storageNode = storageNode;
157         this.request = request;
158         this.response = response;
159 
160         this.initializeConfig(configNode);
161     }
162 
163     /**
164      * @see info.magnolia.cms.gui.dialog.DialogControl#drawHtml(Writer)
165      */
166     public void drawHtml(Writer out) throws IOException {
167         this.drawHtmlPreSubs(out);
168         this.drawSubs(out);
169         this.drawHtmlPostSubs(out);
170     }
171 
172     public void addSub(Object o) {
173         this.getSubs().add(o);
174         if(o instanceof DialogControlImpl){
175             ((DialogControlImpl)o).setParent(this);
176         }
177     }
178 
179     public void setConfig(String key, String value) {
180         if (value != null) {
181             this.config.put(key, value);
182         }
183     }
184 
185     public void setConfig(String key, boolean value) {
186         this.config.put(key, BooleanUtils.toBooleanObject(value).toString());
187     }
188 
189     public void setConfig(String key, int value) {
190         this.config.put(key, Integer.toString(value));
191     }
192 
193     public String getConfigValue(String key, String nullValue) {
194         if (this.config.containsKey(key)) {
195             return (String) this.config.get(key);
196         }
197 
198         return nullValue;
199     }
200 
201     public String getConfigValue(String key) {
202         return this.getConfigValue(key, StringUtils.EMPTY);
203     }
204 
205     public void setValue(String s) {
206         this.value = s;
207     }
208 
209     public String getValue() {
210         if (this.value == null) {
211             if (this.getStorageNode() != null) {
212                 this.value = readValue();
213                 if(this instanceof UUIDDialogControl){
214                     String repository = ((UUIDDialogControl)this).getRepository();
215                     this.value = ContentUtil.uuid2path(repository, this.value);
216                 }
217             }
218             RequestFormUtil params = new RequestFormUtil(request);
219             if (params.getParameter(this.getName()) != null) {
220                 this.value = params.getParameter(this.getName());
221             }
222 
223             if (this.value == null && StringUtils.isNotEmpty(getConfigValue(DEFAULT_VALUE_PROPERTY))) {
224                 return this.getMessage(this.getConfigValue(DEFAULT_VALUE_PROPERTY));
225             }
226 
227             if (this.value == null) {
228                 this.value = StringUtils.EMPTY;
229             }
230         }
231         return this.value;
232     }
233 
234     protected String readValue() {
235         try {
236             if(!this.getStorageNode().hasNodeData(this.getName())){
237                 return null;
238             }
239         }
240         catch (RepositoryException e) {
241             log.error("can't read nodedata [" + this.getName() + "]", e);
242             return null;
243         }
244         return this.getStorageNode().getNodeData(this.getName()).getString();
245     }
246 
247     public void setSaveInfo(boolean b) {
248         this.setConfig("saveInfo", b); //$NON-NLS-1$
249     }
250 
251     /**
252      * Set the name of this control. This is not the same value as the id setted by the parent. In common this value is
253      * setted in the dialog configuration.
254      */
255     public void setName(String s) {
256         this.setConfig("name", s); //$NON-NLS-1$
257     }
258 
259     /**
260      * Return the configured name of this control (not the id).
261      * @return the name
262      */
263     public String getName() {
264         return this.getConfigValue("name"); //$NON-NLS-1$
265     }
266 
267     public void addOption(Object o) {
268         this.getOptions().add(o);
269     }
270 
271     public Content getStorageNode() {
272         return this.storageNode;
273     }
274 
275     public void setLabel(String s) {
276         this.config.put("label", s); //$NON-NLS-1$
277     }
278 
279     public void setDescription(String s) {
280         this.config.put("description", s); //$NON-NLS-1$
281     }
282 
283     public void removeSessionAttribute() {
284         String name = this.getConfigValue(SESSION_ATTRIBUTENAME_DIALOGOBJECT);
285         HttpServletRequest request = this.getRequest();
286         if (request == null) {
287             request = this.getTopParent().getRequest();
288         }
289         try {
290             HttpSession httpsession = request.getSession(false);
291             if (httpsession != null) {
292                 httpsession.removeAttribute(name);
293             }
294         }
295         catch (Exception e) {
296             if (log.isDebugEnabled()) {
297                 log.debug("removeSessionAttribute() for " + name + " failed because this.request is null"); //$NON-NLS-1$ //$NON-NLS-2$
298             }
299         }
300     }
301 
302     public HttpServletRequest getRequest() {
303         return this.request;
304     }
305 
306     public void setOptions(List options) {
307         this.options = options;
308     }
309 
310     protected void drawHtmlPreSubs(Writer out) throws IOException {
311         // do nothing
312     }
313 
314     protected void drawSubs(Writer out) throws IOException {
315         Iterator it = this.getSubs().iterator();
316         int i = 0;
317         while (it.hasNext()) {
318             // use underscore (not divis)! could be used as js variable names
319             String dsId = this.getId() + "_" + i; //$NON-NLS-1$
320             DialogControlImpl ds = (DialogControlImpl) it.next();
321             // set the parent in case not yet set. this is the case when custom code manipulates the subs list manually
322             ds.setParent(this);
323             ds.setId(dsId);
324             ds.drawHtml(out);
325             i++;
326         }
327     }
328 
329     protected void drawHtmlPostSubs(Writer out) throws IOException {
330         // do nothing
331     }
332 
333     public DialogControlImpl getParent() {
334         return this.parent;
335     }
336 
337     protected void setTopParent(DialogControlImpl top) {
338         this.topParent = top;
339     }
340 
341     public DialogControlImpl getTopParent() {
342         DialogControlImpl topParent = this;
343         if(this.topParent ==  null){
344             while(topParent.getParent() != null){
345                 topParent = topParent.getParent();
346             }
347             this.topParent = topParent;
348         }
349         return this.topParent;
350     }
351 
352     public List getSubs() {
353         return this.subs;
354     }
355 
356     /**
357      * Find a control by its name
358      * @param name the name of the control to find
359      * @return the found control or null
360      */
361     public DialogControlImpl getSub(String name) {
362         DialogControlImpl found;
363         for (Iterator iter = subs.iterator(); iter.hasNext();) {
364             Object control = iter.next();
365 
366             // could be an implementation of DialogControl only
367             if (control instanceof DialogControlImpl) {
368                 if (StringUtils.equals(((DialogControlImpl) control).getName(), name)) {
369                     return (DialogControlImpl) control;
370                 }
371                 found = ((DialogControlImpl) control).getSub(name);
372                 if (found != null) {
373                     return found;
374                 }
375             }
376         }
377         return null;
378     }
379 
380     protected HttpServletResponse getResponse() {
381         return this.response;
382     }
383 
384     /**
385      * @deprecated websitenode should only be set in init(), this is a workaround used in DialogDate
386      */
387     @Deprecated
388     protected void clearWebsiteNode() {
389         this.storageNode = null;
390     }
391 
392     public String getId() {
393         return this.id;
394     }
395 
396     public String getLabel() {
397         return this.getConfigValue("label", StringUtils.EMPTY); //$NON-NLS-1$
398     }
399 
400     public String getDescription() {
401         return this.getConfigValue("description", StringUtils.EMPTY); //$NON-NLS-1$
402     }
403 
404     public List getOptions() {
405         return this.options;
406     }
407 
408     public List getValues() {
409         if (this.values == null) {
410             this.values = readValues();
411 
412             if(this instanceof UUIDDialogControl){
413                 String repository = ((UUIDDialogControl)this).getRepository();
414                 List pathes = new ArrayList();
415                 for (Iterator iter = this.values.iterator(); iter.hasNext();) {
416                     String uuid = (String) iter.next();
417                     String path = ContentUtil.uuid2path(repository, uuid);
418                     pathes.add(path);
419                 }
420                 this.values = pathes;
421             }
422 
423             if (request != null) {
424                 RequestFormUtil params = new RequestFormUtil(request);
425                 String[] values = params.getParameterValues(this.getName());
426                 if (values != null && values.length > 0) {
427                     this.values.clear();
428                     for (int i = 0; i < values.length; i++) {
429                         String value = values[i];
430                         this.values.add(value);
431                     }
432                 }
433             }
434         }
435 
436         return this.values;
437     }
438 
439     protected List readValues() {
440         List values = new ArrayList();
441         if (this.getStorageNode() != null) {
442             try {
443                 NodeData node = this.getStorageNode().getNodeData(this.getName());
444                 if(node.isMultiValue() == NodeData.MULTIVALUE_TRUE) {
445                     values = NodeDataUtil.getValuesStringList(node.getValues());
446                 } else {
447                     Collection<NodeData> nodeDataCollection = this.getStorageNode().getContent(this.getName()).getNodeDataCollection();
448                     List<NodeData> nodeDatas = new ArrayList<NodeData>(nodeDataCollection);
449                     Collections.sort(nodeDatas, new Comparator<NodeData> () {
450                         public int compare(NodeData o1, NodeData o2) {
451                             return o1.getName().compareTo(o2.getName());
452                         }
453                     });
454                     Iterator<NodeData> it = nodeDatas.iterator();
455                     while (it.hasNext()) {
456                         NodeData data = it.next();
457                         values.add(data.getString());
458                     }
459                 }
460             }
461             catch (PathNotFoundException e) {
462                 // not yet existing: OK
463             }
464             catch (RepositoryException re) {
465                 log.error("can't set values", re);
466             }
467         }
468         return values;
469     }
470 
471     /**
472      * This method sets a control into the session
473      */
474     public void setSessionAttribute() {
475         String name = SESSION_ATTRIBUTENAME_DIALOGOBJECT + "_" + this.getName() + "_" + new Date().getTime(); //$NON-NLS-1$ //$NON-NLS-2$
476         this.setConfig(SESSION_ATTRIBUTENAME_DIALOGOBJECT, name);
477         HttpServletRequest request = this.getRequest();
478         if (request == null) {
479             request = this.getTopParent().getRequest();
480         }
481         try {
482 
483             // @todo IMPORTANT remove use of http session
484             HttpSession httpsession = request.getSession(true);
485             httpsession.setAttribute(name, this);
486         }
487         catch (Exception e) {
488             log.error("setSessionAttribute() for " + name + " failed because this.request is null"); //$NON-NLS-1$ //$NON-NLS-2$
489         }
490     }
491 
492     private void setId(String id) {
493         this.id = id;
494     }
495 
496     private void initializeConfig(Content configNodeParent) throws RepositoryException {
497 
498         // create config and subs out of dialog structure
499         Map config = new Hashtable();
500 
501         if (configNodeParent == null) {
502             // can happen only if Dialog is instantiated directly
503             return;
504         }
505 
506         // get properties -> to this.config
507         Iterator itProps = configNodeParent.getNodeDataCollection().iterator();
508         while (itProps.hasNext()) {
509             NodeData data = (NodeData) itProps.next();
510             String name = data.getName();
511             String value = data.getString();
512             config.put(name, value);
513         }
514 
515         config.put("handle", configNodeParent.getHandle());
516 
517         // name is usually mandatory, use node name if a name property is not set
518         if (!config.containsKey("name")) {
519             config.put("name", configNodeParent.getName());
520         }
521 
522         this.config = config;
523 
524         Iterator it = configNodeParent.getChildren(ItemType.CONTENTNODE).iterator();
525         while (it.hasNext()) {
526             Content configNode = (Content) it.next();
527 
528             // allow references
529             while(configNode.hasNodeData("reference")){
530                 configNode = configNode.getNodeData("reference").getReferencedContent();
531             }
532 
533             String controlType = configNode.getNodeData("controlType").getString(); //$NON-NLS-1$
534 
535             if (StringUtils.isEmpty(controlType)) {
536                 String name = configNode.getName();
537                 if (!name.startsWith("options")) { //$NON-NLS-1$
538                     log.debug("Missing control type for configNode " + name); //$NON-NLS-1$
539                 }
540                 return;
541             }
542 
543             if (log.isDebugEnabled()) {
544                 log.debug("Loading control \"" + controlType + "\" for " + configNode.getHandle()); //$NON-NLS-1$ //$NON-NLS-2$
545             }
546             DialogControl dialogControl = DialogFactory.loadDialog(request, response, this.getStorageNode(), configNode);
547             this.addSub(dialogControl);
548         }
549     }
550 
551     private void setParent(DialogControlImpl parent) {
552         this.parent = parent;
553     }
554 
555     /**
556      * Get the AbstractMessagesImpl object for this dialog/control. It checks first if there was a bundle defined
557      * <code>i18nBasename</code>, then it tries to find the parent with the first definition.
558      * @return
559      */
560     protected Messages getMessages() {
561         if (messages == null) {
562             // if this is the root
563             if (this.getParent() == null) {
564                 messages = MessagesManager.getMessages();
565             }
566             else {
567                 // try to get it from the control nearest to the root
568                 messages = this.getParent().getMessages();
569             }
570             // if this control defines a bundle (basename in the terms of jstl)
571             String basename = this.getConfigValue(I18N_BASENAME_PROPERTY);
572             if (StringUtils.isNotEmpty(basename)) {
573                 // extend the chain with this bundle
574                 messages = MessagesUtil.chain(basename, messages);
575             }
576         }
577         return messages;
578     }
579 
580     /**
581      * Get the message.
582      * @param key key
583      * @return message
584      */
585     public String getMessage(String key) {
586         return this.getMessages().getWithDefault(key, key);
587     }
588 
589     /**
590      * Get the message with replacement strings. Use the {nr} syntax
591      * @param key key
592      * @param args replacement strings
593      * @return message
594      */
595     public String getMessage(String key, Object[] args) {
596         return this.getMessages().getWithDefault(key, args, key);
597     }
598 
599     /**
600      * If the validation fails the code will set a message in the context using the AlertUtil.
601      * @return true if valid
602      */
603     public boolean validate() {
604 
605         if (this.isRequired()) {
606             boolean valueFound = false;
607             for (Iterator iter = this.getValues().iterator(); iter.hasNext();) {
608                 String value = (String) iter.next();
609                 if(!StringUtils.isEmpty(value)){
610                     valueFound = true;
611                     break;
612                 }
613             }
614             if (!valueFound && StringUtils.isEmpty(this.getValue())) {
615                 setValidationMessage("dialogs.validation.required");
616                 return false;
617             }
618         }
619         if(StringUtils.isNotEmpty(getValidationPattern()) && StringUtils.isNotEmpty(this.getValue())){
620             if(!Pattern.matches(getValidationPattern(), this.getValue())){
621                 setValidationMessage("dialogs.validation.invalid");
622                 return false;
623             }
624         }
625         for (Iterator iter = this.getSubs().iterator(); iter.hasNext();) {
626             DialogControl sub = (DialogControl) iter.next();
627             if (sub instanceof DialogControlImpl) {
628                 DialogControlImpl subImpl = (DialogControlImpl) sub;
629                 subImpl.setParent(this);
630                 if (!subImpl.validate()) {
631                     return false;
632                 }
633             }
634 
635         }
636         return true;
637     }
638 
639     protected void setValidationMessage(String msg) {
640         String name = this.getMessage(this.getLabel());
641         String tabName = "";
642         if (this.getParent() instanceof DialogTab) {
643             DialogTab tab = (DialogTab) this.getParent();
644             tabName = tab.getMessage(tab.getLabel());
645         }
646         AlertUtil.setMessage(this.getMessage(msg, new Object[]{name, tabName, this.getValue()}));
647     }
648 
649     public String getValidationPattern() {
650         return this.getConfigValue(VALIDATION_PATTERN_PROPERTY);
651     }
652 
653     /**
654      * True if a value is required. Set it in the configuration
655      * @return
656      */
657     public boolean isRequired() {
658         if (BooleanUtils.toBoolean(this.getConfigValue(REQUIRED_PROPERTY))) {
659             return true;
660         }
661         return false;
662     }
663 
664     public void setRequired(boolean required) {
665         this.setConfig(REQUIRED_PROPERTY, BooleanUtils.toStringTrueFalse(required));
666     }
667 
668     /**
669      * @deprecated use getStorageNode()
670      */
671     @Deprecated
672     public Content getWebsiteNode() {
673         return getStorageNode();
674     }
675 
676 }