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