View Javadoc

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