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