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