001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.workplace;
029
030import org.opencms.file.CmsObject;
031import org.opencms.main.CmsException;
032import org.opencms.main.CmsIllegalArgumentException;
033import org.opencms.main.CmsRuntimeException;
034import org.opencms.util.CmsStringUtil;
035import org.opencms.widgets.A_CmsWidget;
036import org.opencms.widgets.CmsWidgetException;
037import org.opencms.widgets.I_CmsWidget;
038import org.opencms.widgets.I_CmsWidgetParameter;
039import org.opencms.widgets.Messages;
040
041import java.lang.reflect.InvocationTargetException;
042import java.util.ArrayList;
043import java.util.List;
044import java.util.SortedMap;
045
046import org.apache.commons.beanutils.ConvertUtilsBean;
047import org.apache.commons.beanutils.PropertyUtilsBean;
048
049/**
050 * Implements the widget parameter interface for the use of OpenCms widgets on dialogs that
051 * are not based on XML contents.<p>
052 *
053 * @since 6.0.0
054 */
055public class CmsWidgetDialogParameter implements I_CmsWidgetParameter {
056
057    /** The name of the default dialog page. */
058    public static final String DEFAULT_DIALOG_PAGE = "default";
059
060    /** The maximum number of occurences of a widget dialog element in a list of elements. */
061    public static final int MAX_OCCURENCES = 200;
062
063    /** The (optional) base collection for read / writing collection based parameters. */
064    protected Object m_baseCollection;
065
066    /** The (optional) base object for read / writing the parameter value to. */
067    protected Object m_baseObject;
068
069    /** The (optinal) object property to read / write this parameter value to. */
070    protected String m_baseObjectProperty;
071
072    /** The default value of the parameter. */
073    protected String m_defaultValue;
074
075    /** The name of the dialog (page) the widget is used on. */
076    protected String m_dialogPage;
077
078    /** Indicates if the widget value has an error. */
079    protected Throwable m_error;
080
081    /** The id of the parameter on the form. */
082    protected String m_id;
083
084    /** The index of this parameter in the (optional) list of parameters. */
085    protected int m_index;
086
087    /** The maximum number of occurences of this parameter. */
088    protected int m_maxOccurs;
089
090    /** The minimum number of occurences of this parameter. */
091    protected int m_minOccurs;
092
093    /** The name of the parameter. */
094    protected String m_name;
095
096    /** Optional localized key prefix identificator. */
097    protected String m_prefix;
098
099    /** The value of the parameter. */
100    protected String m_value;
101
102    /** The widget used for the parameter. */
103    protected I_CmsWidget m_widget;
104
105    /**
106     * Create a new Widget parameter.<p>
107     *
108     * @param base the base of the parameter
109     * @param index the index of this parameter in the list
110     */
111    public CmsWidgetDialogParameter(CmsWidgetDialogParameter base, int index) {
112
113        this(
114            null,
115            base.m_defaultValue,
116            base.getName(),
117            base.getWidget(),
118            base.getDialogPage(),
119            base.getMinOccurs(),
120            base.getMaxOccurs(),
121            index);
122
123        m_baseObject = base.m_baseObject;
124        m_baseObjectProperty = base.m_baseObjectProperty;
125        m_baseCollection = base.m_baseCollection;
126        m_prefix = base.m_prefix;
127    }
128
129    /**
130     * Create a new Widget parameter.<p>
131     *
132     * @param base the base of the parameter
133     * @param index the index of this parameter in the list
134     * @param originalIndex the original index in the previous version of the list
135     */
136    public CmsWidgetDialogParameter(CmsWidgetDialogParameter base, int index, int originalIndex) {
137
138        this(
139            null,
140            base.m_defaultValue,
141            base.getName(),
142            base.getWidget(),
143            base.getDialogPage(),
144            base.getMinOccurs(),
145            base.getMaxOccurs(),
146            index);
147
148        m_baseObject = base.m_baseObject;
149        m_baseObjectProperty = base.m_baseObjectProperty;
150        m_baseCollection = base.m_baseCollection;
151
152        if (m_baseCollection != null) {
153            if (m_baseCollection instanceof List) {
154                // base object is a list - make sure to set possible old value
155                List<?> baseList = (List<?>)m_baseCollection;
156                if (originalIndex < baseList.size()) {
157                    Object o = baseList.get(originalIndex);
158                    if (o != null) {
159                        m_value = o.toString();
160                    }
161                }
162            } else if (m_baseCollection instanceof SortedMap) {
163                // base object is a sorted map - make sure to set possible old value
164                SortedMap<?, ?> baseMap = (SortedMap<?, ?>)m_baseCollection;
165                @SuppressWarnings({"unchecked", "rawtypes"})
166                List<?> keyList = new ArrayList(baseMap.keySet());
167                if (originalIndex < keyList.size()) {
168                    Object key = keyList.get(originalIndex);
169                    Object value = baseMap.get(key);
170                    StringBuffer val = new StringBuffer();
171                    val.append(key != null ? key.toString() : "");
172                    val.append('=');
173                    val.append(value != null ? value.toString() : "");
174                    m_value = val.toString();
175                }
176            }
177        }
178    }
179
180    /**
181     * Create a new Widget parameter based on a given object's property.<p>
182     *
183     * @param base the base object to map the parameter to / from
184     * @param property the base object property to map the parameter to / from
185     * @param widget the widget used for this parameter
186     */
187    public CmsWidgetDialogParameter(Object base, String property, I_CmsWidget widget) {
188
189        this(base, property, DEFAULT_DIALOG_PAGE, widget);
190    }
191
192    /**
193     * Create a new Widget parameter based on a given object's property.<p>
194     *
195     * @param base the base object to map the parameter to / from
196     * @param property the base object property to map the parameter to / from
197     * @param dialogPage the dialog page to use the widget on
198     * @param widget the widget used for this parameter
199     */
200    public CmsWidgetDialogParameter(Object base, String property, String dialogPage, I_CmsWidget widget) {
201
202        this(base, property, null, dialogPage, widget, 1, 1);
203    }
204
205    /**
206     * Create a new Widget parameter based on a given object's property.<p>
207     *
208     * @param base the base object to map the parameter to / from
209     * @param property the base object property to map the parameter to / from
210     * @param htmlName the form id name to use in the generated HTML
211     * @param dialogPage the dialog page to use the widget on
212     * @param widget the widget used for this parameter
213     *
214     */
215    public CmsWidgetDialogParameter(
216        Object base,
217        String property,
218        String htmlName,
219        String dialogPage,
220        I_CmsWidget widget) {
221
222        this(base, property, htmlName, null, dialogPage, widget, 1, 1);
223    }
224
225    /**
226     * Create a new Widget parameter based on a given object's property.<p>
227     *
228     * @param base the base object to map the parameter to / from
229     * @param property the base object property to map the parameter to / from
230     * @param defaultValue the default value to use for this parameter
231     * @param dialogPage the dialog page to use the widget on
232     * @param widget the widget used for this paramete
233     * @param minOccurs the required minimum numer of occurences of this parameter
234     * @param maxOccurs the maximum allowed numer of occurences of this parameter
235     */
236    public CmsWidgetDialogParameter(
237        Object base,
238        String property,
239        String defaultValue,
240        String dialogPage,
241        I_CmsWidget widget,
242        int minOccurs,
243        int maxOccurs) {
244
245        this(base, property, property, defaultValue, dialogPage, widget, minOccurs, maxOccurs);
246    }
247
248    /**
249     * Create a new Widget parameter based on a given object's property.<p>
250     *
251     * @param base the base object to map the parameter to / from
252     * @param property the base object property to map the parameter to / from
253     * @param htmlName the form id name to use in the generated HTML
254     * @param defaultValue the default value to use for this parameter
255     * @param dialogPage the dialog page to use the widget on
256     * @param widget the widget used for this paramete
257     * @param minOccurs the required minimum numer of occurences of this parameter
258     * @param maxOccurs the maximum allowed numer of occurences of this parameter
259     */
260    public CmsWidgetDialogParameter(
261        Object base,
262        String property,
263        String htmlName,
264        String defaultValue,
265        String dialogPage,
266        I_CmsWidget widget,
267        int minOccurs,
268        int maxOccurs) {
269
270        if (htmlName == null) {
271            htmlName = property;
272        }
273
274        if ((base instanceof List) || (base instanceof SortedMap)) {
275
276            // this is a list, use custom list mappings
277            init(null, defaultValue, htmlName, widget, dialogPage, 0, MAX_OCCURENCES, 0);
278
279            m_baseObject = null;
280            m_baseObjectProperty = null;
281            m_baseCollection = base;
282
283        } else {
284
285            // generic object:use reflection to map object properties
286            init(null, defaultValue, htmlName, widget, dialogPage, minOccurs, maxOccurs, 0);
287
288            m_baseObject = base;
289            m_baseObjectProperty = property;
290            m_baseCollection = null;
291
292            PropertyUtilsBean bean = new PropertyUtilsBean();
293
294            Object value = null;
295            // make sure the base object has the requested property
296            if (!bean.isReadable(m_baseObject, m_baseObjectProperty)
297                || !bean.isWriteable(m_baseObject, m_baseObjectProperty)) {
298                try {
299                    // check if this is a mapped property
300                    value = bean.getMappedProperty(m_baseObject, m_baseObjectProperty);
301                } catch (Exception e) {
302                    throw new CmsIllegalArgumentException(
303                        Messages.get().container(Messages.ERR_NO_PROPERTY_2, base.getClass().getName(), property));
304                }
305            }
306
307            try {
308                if (value == null) {
309                    // may have been read already as a mapped property
310                    value = bean.getNestedProperty(m_baseObject, m_baseObjectProperty);
311                }
312            } catch (Exception e) {
313                throw new CmsRuntimeException(
314                    Messages.get().container(Messages.ERR_PROPERTY_READ_2, property, base.getClass().getName()),
315                    e);
316            }
317
318            if (value != null) {
319                if ((value instanceof List) || (value instanceof SortedMap)) {
320                    m_baseCollection = value;
321                    m_minOccurs = 0;
322                    m_maxOccurs = MAX_OCCURENCES;
323                } else {
324                    m_defaultValue = String.valueOf(value);
325                    m_value = m_defaultValue;
326                    if ((m_minOccurs == 0) && !m_value.equals(defaultValue)) {
327                        // if value is different from default ensure this widget is displayed
328                        m_minOccurs = 1;
329                    }
330                }
331            }
332        }
333    }
334
335    /**
336     * Create a new Widget parameter.<p>
337     *
338     * @param name the name of the parameter
339     * @param widget the widget used for this parameter
340     */
341    public CmsWidgetDialogParameter(String name, I_CmsWidget widget) {
342
343        this(null, null, name, widget, DEFAULT_DIALOG_PAGE, 1, 1, 0);
344    }
345
346    /**
347     * Create a new Widget parameter.<p>
348     *
349     * @param name the name of the parameter
350     * @param widget the widget used for this parameter
351     * @param minOccurs the required minimum numer of occurences of this parameter
352     * @param maxOccurs the maximum allowed numer of occurences of this parameter
353     */
354    public CmsWidgetDialogParameter(String name, I_CmsWidget widget, int minOccurs, int maxOccurs) {
355
356        this(null, null, name, widget, DEFAULT_DIALOG_PAGE, minOccurs, maxOccurs, 0);
357    }
358
359    /**
360     * Create a new Widget parameter with specified occurence settings.<p>
361     *
362     * @param value the initial value of the parameter
363     * @param defaultValue the default value of the parameter
364     * @param name the id of the parameter
365     * @param widget the widget used for this parameter
366     * @param dialog the dialog this parameter is used on
367     * @param minOccurs the required minimum numer of occurences of this parameter
368     * @param maxOccurs the maximum allowed numer of occurences of this parameter
369     * @param index the index of this parameter in the list
370     */
371    public CmsWidgetDialogParameter(
372        String value,
373        String defaultValue,
374        String name,
375        I_CmsWidget widget,
376        String dialog,
377        int minOccurs,
378        int maxOccurs,
379        int index) {
380
381        super();
382        init(value, defaultValue, name, widget, dialog, minOccurs, maxOccurs, index);
383    }
384
385    /**
386     * Returns a from id representation for the given widget name and id.<p>
387     *
388     * @param name the widget parameter name
389     * @param index the widget parameter index
390     *
391     * @return a from id representation for the given widget name and id
392     */
393    public static String createId(String name, int index) {
394
395        StringBuffer result = new StringBuffer();
396        result.append(name);
397        result.append('.');
398        result.append(index);
399
400        return result.toString();
401    }
402
403    /**
404     * "Commits" (writes) the value of this widget back to the underlying base object.<p>
405     *
406     * @param dialog the widget dialog where the parameter is used on
407     *
408     * @throws CmsException in case the String value of the widget is invalid for the base Object
409     */
410    @SuppressWarnings("unchecked")
411    public void commitValue(CmsWidgetDialog dialog) throws CmsException {
412
413        if (m_baseCollection == null) {
414            PropertyUtilsBean bean = new PropertyUtilsBean();
415            ConvertUtilsBean converter = new ConvertUtilsBean();
416            Object value = null;
417            try {
418                Class<?> type = bean.getPropertyType(m_baseObject, m_baseObjectProperty);
419                value = converter.convert(m_value, type);
420                bean.setNestedProperty(m_baseObject, m_baseObjectProperty, value);
421                setError(null);
422            } catch (InvocationTargetException e) {
423                setError(e.getTargetException());
424                throw new CmsWidgetException(
425                    Messages.get().container(
426                        Messages.ERR_PROPERTY_WRITE_3,
427                        value,
428                        dialog.keyDefault(A_CmsWidget.getLabelKey(this), getKey()),
429                        m_baseObject.getClass().getName()),
430                    e.getTargetException(),
431                    this);
432            } catch (Exception e) {
433                setError(e);
434                throw new CmsWidgetException(
435                    Messages.get().container(
436                        Messages.ERR_PROPERTY_WRITE_3,
437                        value,
438                        dialog.keyDefault(A_CmsWidget.getLabelKey(this), getKey()),
439                        m_baseObject.getClass().getName()),
440                    e,
441                    this);
442            }
443        } else if (m_baseCollection instanceof SortedMap) {
444            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_value)) {
445                int pos = m_value.indexOf('=');
446                if ((pos > 0) && (pos < (m_value.length() - 1))) {
447                    String key = m_value.substring(0, pos);
448                    // it is assumed strings starting or ending with white-spaces are faulty inputs
449                    String value = m_value.substring(pos + 1).trim();
450                    @SuppressWarnings("rawtypes")
451                    SortedMap map = (SortedMap)m_baseCollection;
452                    if (map.containsKey(key)) {
453                        Object val = map.get(key);
454                        CmsWidgetException error = new CmsWidgetException(
455                            Messages.get().container(
456                                Messages.ERR_MAP_DUPLICATE_KEY_3,
457                                dialog.keyDefault(A_CmsWidget.getLabelKey(this), getKey()),
458                                key,
459                                val),
460                            this);
461                        setError(error);
462                        throw error;
463                    }
464                    map.put(key, value);
465                } else {
466                    CmsWidgetException error = new CmsWidgetException(
467                        Messages.get().container(
468                            Messages.ERR_MAP_PARAMETER_FORM_1,
469                            dialog.keyDefault(A_CmsWidget.getLabelKey(this), getKey())),
470                        this);
471                    setError(error);
472                    throw error;
473                }
474            }
475        } else if (m_baseCollection instanceof List) {
476            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_value)) {
477                @SuppressWarnings("rawtypes")
478                List list = (List)m_baseCollection;
479                list.add(m_value);
480            }
481        }
482    }
483
484    /**
485     * @see org.opencms.widgets.I_CmsWidgetParameter#getDefault(org.opencms.file.CmsObject)
486     */
487    public String getDefault(CmsObject cms) {
488
489        return m_defaultValue;
490    }
491
492    /**
493     * Returns the name of the dialog (or dialog page) this widget parameter is used on.<p>
494     *
495     * This information can be used to create multi-page dialogs where the
496     * widgets are spread over several pages.<p>
497     *
498     * @return the name of the dialog (or dialog page) this widget parameter is used on
499     */
500    public String getDialogPage() {
501
502        return m_dialogPage;
503    }
504
505    /**
506     * Returns the Exception caused when this parameter value was commited, or <code>null</code>
507     * if error occurred.<p>
508     *
509     * @return the Exception caused when this parameter value was commited
510     */
511    public Throwable getError() {
512
513        return m_error;
514    }
515
516    /**
517     * @see org.opencms.widgets.I_CmsWidgetParameter#getId()
518     */
519    public String getId() {
520
521        return m_id;
522    }
523
524    /**
525     * @see org.opencms.widgets.I_CmsWidgetParameter#getIndex()
526     */
527    public int getIndex() {
528
529        return m_index;
530    }
531
532    /**
533     * @see org.opencms.widgets.I_CmsWidgetParameter#getKey()
534     */
535    public String getKey() {
536
537        StringBuffer result = new StringBuffer(128);
538        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_prefix)) {
539            result.append(m_prefix);
540            result.append('.');
541        }
542        result.append(getName());
543        return result.toString();
544    }
545
546    /**
547     * @see org.opencms.widgets.I_CmsWidgetParameter#getMaxOccurs()
548     */
549    public int getMaxOccurs() {
550
551        return m_maxOccurs;
552    }
553
554    /**
555     * @see org.opencms.widgets.I_CmsWidgetParameter#getMinOccurs()
556     */
557    public int getMinOccurs() {
558
559        return m_minOccurs;
560    }
561
562    /**
563     * @see org.opencms.widgets.I_CmsWidgetParameter#getName()
564     */
565    public String getName() {
566
567        return m_name;
568    }
569
570    /**
571     * @see org.opencms.widgets.I_CmsWidgetParameter#getStringValue(org.opencms.file.CmsObject)
572     */
573    public String getStringValue(CmsObject cms) throws CmsRuntimeException {
574
575        return m_value;
576    }
577
578    /**
579     * Returns the widget for this parameter.<p>
580     *
581     * @return the widget for this parameter
582     */
583    public I_CmsWidget getWidget() {
584
585        return m_widget;
586    }
587
588    /**
589     * @see org.opencms.widgets.I_CmsWidgetParameter#hasError()
590     */
591    public boolean hasError() {
592
593        return m_error != null;
594    }
595
596    /**
597     * Checks if a value for this widget base type with the given id is available.<p>
598     *
599     * This should only be used if the base object is a collection.<p>
600     *
601     * @param index the index to check
602     *
603     * @return <code>true</code> if a value for this widget base type with the given id is available
604     */
605    public boolean hasValue(int index) {
606
607        if (m_baseCollection instanceof List) {
608            return index < ((List<?>)m_baseCollection).size();
609        } else if (m_baseCollection instanceof SortedMap) {
610            return index < ((SortedMap<?, ?>)m_baseCollection).size();
611        }
612        return false;
613    }
614
615    /**
616     * Returns <code>true</code> if this widget parameter is mapped to a Collection base object.<p>
617     *
618     * @return <code>true</code> if this widget parameter is mapped to a Collection base object
619     */
620    public boolean isCollectionBase() {
621
622        return (m_baseCollection != null)
623            && ((m_baseCollection instanceof List) || (m_baseCollection instanceof SortedMap));
624    }
625
626    /**
627     * Prepares this widget dialog parameter to be committed.<p>
628     *
629     * This is required if the base type is mapped to a Collection object,
630     * because the collection needs to be cleared before the new values are set.<p>
631     */
632    public void prepareCommit() {
633
634        if (m_baseCollection instanceof List) {
635            List<?> list = (List<?>)m_baseCollection;
636            list.clear();
637        } else if (m_baseCollection instanceof SortedMap) {
638            SortedMap<?, ?> map = (SortedMap<?, ?>)m_baseCollection;
639            map.clear();
640        }
641    }
642
643    /**
644     * Sets the error state of this widget.<p>
645     *
646     * If the argument is <code>null</code> then the state is set to "no error".<p>
647     *
648     * @param error the error state to set
649     */
650    public void setError(Throwable error) {
651
652        m_error = error;
653    }
654
655    /**
656     * Sets the index to the provided value.<p>
657     *
658     * @param index the new index value to set
659     */
660    public void setindex(int index) {
661
662        m_index = index;
663        m_id = createId(m_name, m_index);
664    }
665
666    /**
667     * @see org.opencms.widgets.I_CmsWidgetParameter#setKeyPrefix(java.lang.String)
668     */
669    public void setKeyPrefix(String prefix) {
670
671        m_prefix = prefix;
672    }
673
674    /**
675     * @see org.opencms.widgets.I_CmsWidgetParameter#setStringValue(org.opencms.file.CmsObject, java.lang.String)
676     */
677    public void setStringValue(CmsObject cms, String value) throws CmsIllegalArgumentException {
678
679        m_value = value;
680    }
681
682    /**
683     * Initializes a widget parameter with the given values.<p>
684     *
685     * @param value the initial value of the parameter
686     * @param defaultValue the default value of the parameter
687     * @param name the id of the parameter
688     * @param widget the widget used for this parameter
689     * @param dialog the dialog this parameter is used on
690     * @param minOccurs the required minimum numer of occurences of this parameter
691     * @param maxOccurs the maximum allowed numer of occurences of this parameter
692     * @param index the index of this parameter in the list
693     */
694    protected void init(
695        String value,
696        String defaultValue,
697        String name,
698        I_CmsWidget widget,
699        String dialog,
700        int minOccurs,
701        int maxOccurs,
702        int index) {
703
704        if (defaultValue == null) {
705            m_defaultValue = "";
706        } else {
707            m_defaultValue = defaultValue;
708        }
709        if (value == null) {
710            m_value = m_defaultValue;
711        } else {
712            m_value = value;
713        }
714        m_name = name;
715        m_widget = widget;
716        if (maxOccurs < MAX_OCCURENCES) {
717            m_maxOccurs = maxOccurs;
718        } else {
719            m_maxOccurs = MAX_OCCURENCES;
720        }
721        if (minOccurs >= 0) {
722            m_minOccurs = minOccurs;
723        } else {
724            m_minOccurs = 0;
725        }
726        if (m_minOccurs > m_maxOccurs) {
727            m_minOccurs = m_maxOccurs;
728        }
729        m_dialogPage = dialog;
730        m_error = null;
731
732        setindex(index);
733    }
734}