001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.layout;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.commons.lang.StringUtils;
026import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
027import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
028import org.kuali.rice.krad.uif.UifConstants;
029import org.kuali.rice.krad.uif.UifConstants.ViewStatus;
030import org.kuali.rice.krad.uif.component.PropertyReplacer;
031import org.kuali.rice.krad.uif.component.ReferenceCopy;
032import org.kuali.rice.krad.uif.container.Container;
033import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
034import org.kuali.rice.krad.uif.lifecycle.ViewLifecyclePhase;
035import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTask;
036import org.kuali.rice.krad.uif.util.LifecycleAwareList;
037import org.kuali.rice.krad.uif.util.LifecycleAwareMap;
038import org.kuali.rice.krad.uif.util.LifecycleElement;
039import org.kuali.rice.krad.uif.view.View;
040
041/**
042 * Base class for all layout managers
043 *
044 * <p>
045 * Provides general properties of all layout managers, such as the unique id,
046 * rendering template, and style settings
047 * </p>
048 *
049 * @author Kuali Rice Team (rice.collab@kuali.org)
050 */
051public abstract class LayoutManagerBase extends UifDictionaryBeanBase implements LayoutManager {
052    private static final long serialVersionUID = -2657663560459456814L;
053
054    private String id;
055    private String containerIdSuffix;
056    private String viewPath;
057    private Map<String, String> phasePathMapping;
058
059    private String template;
060    private String templateName;
061
062    private String style;
063    
064    private List<String> libraryCssClasses;
065    private List<String> cssClasses;
066    private List<String> additionalCssClasses;
067
068    @ReferenceCopy(newCollectionInstance = true)
069    private Map<String, Object> context;
070
071    private List<PropertyReplacer> propertyReplacers;
072    
073    private boolean render = true;
074    
075    private String viewStatus = UifConstants.ViewStatus.CREATED;
076
077    public LayoutManagerBase() {
078        super();
079
080        phasePathMapping = new HashMap<String, String>();
081        context = Collections.emptyMap();
082        cssClasses = Collections.emptyList();
083        libraryCssClasses = Collections.emptyList();
084        additionalCssClasses = Collections.emptyList();
085    }
086
087    /**
088     * @see LifecycleElement#checkMutable(boolean)
089     */
090    public void checkMutable(boolean legalDuringInitialization) {
091        if (UifConstants.ViewStatus.CACHED.equals(viewStatus)) {
092            ViewLifecycle.reportIllegalState("Cached layout manager " + getClass() + " " + getId()
093                    + " is immutable, use copy() to get a mutable instance");
094            return;
095        }
096
097        if (ViewLifecycle.isActive()) {
098            return;
099        }
100
101        if (UifConstants.ViewStatus.CREATED.equals(viewStatus)) {
102            if (!legalDuringInitialization) {
103                ViewLifecycle.reportIllegalState(
104                        "View has not been fully initialized, attempting to change layout manager "
105                                + getClass() + " " + getId());
106                return;
107            }
108        } else {
109            ViewLifecycle.reportIllegalState("Layout manager " + getClass() + " " + getId()
110                    + " has been initialized, but the lifecycle is not active.");
111            return;
112        }
113    }
114
115    /**
116     * @see LifecycleElement#isMutable(boolean)
117     */
118    public boolean isMutable(boolean legalDuringInitialization) {
119        return (UifConstants.ViewStatus.CREATED.equals(viewStatus) && legalDuringInitialization)
120                || ViewLifecycle.isActive();
121    }
122
123    /**
124     * Indicates what lifecycle phase the layout manager instance is in
125     * 
126     * <p>
127     * The view lifecycle begins with the CREATED status. In this status a new instance of the view
128     * has been retrieved from the dictionary, but no further processing has been done. After the
129     * initialize phase has been run the status changes to INITIALIZED. After the model has been
130     * applied and the view is ready for render the status changes to FINAL
131     * </p>
132     * 
133     * @return view status
134     * @see org.kuali.rice.krad.uif.UifConstants.ViewStatus
135     */
136    public String getViewStatus() {
137        return this.viewStatus;
138    }
139
140    /**
141     * {@inheritDoc}
142     */
143    @Override
144    public void setViewStatus(String status) {
145        this.viewStatus = status;
146    }
147
148    /**
149     * {@inheritDoc}
150     */
151    @Override
152    public void notifyCompleted(ViewLifecyclePhase phase) {
153    }
154
155    /**
156     * {@inheritDoc}
157     */
158    @Override
159    public void performInitialization(Object model) {
160        checkMutable(false);
161        
162        // set id of layout manager from container
163        if (StringUtils.isBlank(id)) {
164            Container container = (Container) ViewLifecycle.getPhase().getElement();
165            id = container.getId() + "_layout";
166        }
167    }
168
169    /**
170     * {@inheritDoc}
171     */
172    @Override
173    public void performApplyModel(Object model, LifecycleElement component) {
174        checkMutable(false);
175    }
176
177    /**
178     * {@inheritDoc}
179     */
180    @Override
181    public void performFinalize(Object model, LifecycleElement component) {
182        checkMutable(false);
183
184        // put together all css class names for this component, in order
185        List<String> finalCssClasses = new ArrayList<String>();
186        
187        View view = ViewLifecycle.getView();
188
189        if (this.libraryCssClasses != null && view.isUseLibraryCssClasses()) {
190            finalCssClasses.addAll(libraryCssClasses);
191        }
192
193        if (this.cssClasses != null) {
194            finalCssClasses.addAll(cssClasses);
195        }
196
197        if (this.additionalCssClasses != null) {
198            finalCssClasses.addAll(additionalCssClasses);
199        }
200
201        cssClasses = finalCssClasses;
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    @Override
208    public boolean skipLifecycle() {
209        return false;
210    }
211
212    /**
213     * Default Impl
214     *
215     * {@inheritDoc}
216     */
217    @Override
218    public Class<? extends Container> getSupportedContainer() {
219        return Container.class;
220    }
221
222    /**
223     * {@inheritDoc}
224     */
225    @Override
226    @BeanTagAttribute
227    public String getId() {
228        return this.id;
229    }
230
231    /**
232     * {@inheritDoc}
233     */
234    @Override
235    public void setId(String id) {
236        checkMutable(true);
237        this.id = id;
238    }
239
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    public String getContainerIdSuffix() {
245        return containerIdSuffix;
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    @Override
252    public void setContainerIdSuffix(String containerIdSuffix) {
253        this.containerIdSuffix = containerIdSuffix;
254    }
255
256    /**
257     * {@inheritDoc}
258     */
259    @Override
260    public String getViewPath() {
261        return this.viewPath;
262    }
263    
264    /**
265     * {@inheritDoc}
266     */
267    @Override
268    public void setViewPath(String viewPath) {
269        checkMutable(true);
270        this.viewPath = viewPath;
271    }
272
273    /**
274     * {@inheritDoc}
275     */
276    @Override
277    public Map<String, String> getPhasePathMapping() {
278        return phasePathMapping;
279    }
280
281    /**
282     * {@inheritDoc}
283     */
284    @Override
285    public void setPhasePathMapping(Map<String, String> phasePathMapping) {
286        this.phasePathMapping = phasePathMapping;
287    }
288
289    /**
290     * {@inheritDoc}
291     */
292    @Override
293    @BeanTagAttribute
294    public String getTemplate() {
295        return this.template;
296    }
297
298    /**
299     * {@inheritDoc}
300     */
301    @Override
302    public void setTemplate(String template) {
303        checkMutable(true);
304        this.template = template;
305    }
306
307    /**
308     * {@inheritDoc}
309     */
310    @BeanTagAttribute
311    public String getTemplateName() {
312        return templateName;
313    }
314
315    /**
316     * {@inheritDoc}
317     */
318    public void setTemplateName(String templateName) {
319        checkMutable(true);
320        this.templateName = templateName;
321    }
322
323    /**
324     * {@inheritDoc}
325     */
326    @Override
327    @BeanTagAttribute
328    public String getStyle() {
329        return this.style;
330    }
331
332    /**
333     * {@inheritDoc}
334     */
335    @Override
336    public void setStyle(String style) {
337        checkMutable(true);
338        this.style = style;
339    }
340
341    /**
342     * Additional css classes that come before css classes listed in the cssClasses property
343     * 
344     * <p>
345     * These are used by the framework for styling with a library (for example, bootstrap), and
346     * should normally not be overridden.
347     * </p>
348     * 
349     * @return the library cssClasses
350     */
351    public List<String> getLibraryCssClasses() {
352        if (libraryCssClasses == Collections.EMPTY_LIST && isMutable(true)) {
353            libraryCssClasses = new LifecycleAwareList<String>(this);
354        }
355        
356        return libraryCssClasses;
357    }
358
359    /**
360     * Set the libraryCssClasses
361     * 
362     * @param libraryCssClasses
363     */
364    public void setLibraryCssClasses(List<String> libraryCssClasses) {
365        checkMutable(true);
366
367        if (libraryCssClasses == null) {
368            this.libraryCssClasses = Collections.emptyList();
369        } else {
370            this.libraryCssClasses = new LifecycleAwareList<String>(this, libraryCssClasses);
371        }
372    }
373
374    /**
375     * @see org.kuali.rice.krad.uif.layout.LayoutManager#getCssClasses()
376     */
377    @BeanTagAttribute
378    public List<String> getCssClasses() {
379        if (cssClasses == Collections.EMPTY_LIST && isMutable(true)) {
380            cssClasses = new LifecycleAwareList<String>(this);
381        }
382        
383        return cssClasses;
384    }
385
386    /**
387     * @see org.kuali.rice.krad.uif.layout.LayoutManager#setCssClasses(java.util.List)
388     */
389    public void setCssClasses(List<String> cssClasses) {
390        checkMutable(true);
391        if (cssClasses == null) {
392            this.cssClasses = Collections.emptyList();
393        } else {
394            this.cssClasses = new LifecycleAwareList<String>(this, cssClasses);
395        }
396    }
397
398    /**
399     * @see org.kuali.rice.krad.uif.layout.LayoutManager#getAdditionalCssClasses()
400     */
401    @BeanTagAttribute
402    public List<String> getAdditionalCssClasses() {
403        if (additionalCssClasses == Collections.EMPTY_LIST && isMutable(true)) {
404            additionalCssClasses = new LifecycleAwareList<String>(this);
405        }
406        
407        return additionalCssClasses;
408    }
409
410    /**
411     * @see org.kuali.rice.krad.uif.layout.LayoutManager#setAdditionalCssClasses(java.util.List)
412     */
413    public void setAdditionalCssClasses(List<String> additionalCssClasses) {
414        checkMutable(true);
415        if (additionalCssClasses == null) {
416            this.additionalCssClasses = Collections.emptyList();
417        } else {
418            this.additionalCssClasses = new LifecycleAwareList<String>(this, additionalCssClasses);
419        }
420    }
421
422    /**
423     * Builds the HTML class attribute string by combining the styleClasses list
424     * with a space delimiter
425     *
426     * @return class attribute string
427     */
428    public String getStyleClassesAsString() {
429        if (cssClasses != null) {
430            return StringUtils.join(cssClasses, " ");
431        }
432
433        return "";
434    }
435
436    /**
437     * Sets the styleClasses list from the given string that has the classes
438     * delimited by space. This is a convenience for configuration. If a child
439     * bean needs to inherit the classes from the parent, it should configure as
440     * a list and use merge="true"
441     *
442     * @param styleClasses
443     */
444    public void setStyleClasses(String styleClasses) {
445        checkMutable(true);
446        String[] classes = StringUtils.split(styleClasses);
447        this.cssClasses = Arrays.asList(classes);
448    }
449
450    /**
451     * {@inheritDoc}
452     */
453    @Override
454    public void addStyleClass(String styleClass) {
455        checkMutable(false);
456        if (cssClasses == null || cssClasses.isEmpty()) {
457            cssClasses = new ArrayList<String>();
458        }
459        
460        if (!cssClasses.contains(styleClass)) {
461            cssClasses.add(styleClass);
462        }
463    }
464
465    /**
466     * {@inheritDoc}
467     */
468    @Override
469    public void appendToStyle(String styleRules) {
470        checkMutable(false);
471        if (style == null) {
472            style = "";
473        }
474        style = style + styleRules;
475    }
476
477    /**
478     * {@inheritDoc}
479     */
480    @Override
481    @BeanTagAttribute
482    public Map<String, Object> getContext() {
483        if (context == Collections.EMPTY_MAP && isMutable(true)) {
484            context = new LifecycleAwareMap<String, Object>(this);
485        }
486        
487        return context;
488    }
489
490    /**
491     * {@inheritDoc}
492     */
493    @Override
494    public void setContext(Map<String, Object> context) {
495        checkMutable(true);
496
497        if (context == null) {
498            this.context = Collections.emptyMap();
499        } else {
500            this.context = new LifecycleAwareMap<String, Object>(this, context);
501        }
502    }
503
504    /**
505     * {@inheritDoc}
506     */
507    @Override
508    public void pushObjectToContext(String objectName, Object object) {
509        checkMutable(false);
510        if (context == Collections.EMPTY_MAP && isMutable(true)) {
511            context = new LifecycleAwareMap<String, Object>(this);
512        }
513
514        context.put(objectName, object);
515    }
516
517    /**
518     * {@inheritDoc}
519     */
520    @Override
521    public void pushAllToContext(Map<String, Object> sourceContext) {
522        checkMutable(false);
523        if (sourceContext == null || sourceContext.isEmpty()) {
524            return;
525        }
526        
527        if (context == Collections.EMPTY_MAP && isMutable(true)) {
528            context = new LifecycleAwareMap<String, Object>(this);
529        }
530
531        this.context.putAll(sourceContext);
532    }
533
534    /**
535     * {@inheritDoc}
536     */
537    @Override
538    @BeanTagAttribute
539    public List<PropertyReplacer> getPropertyReplacers() {
540        return this.propertyReplacers;
541    }
542
543    /**
544     * {@inheritDoc}
545     */
546    @Override
547    public void setPropertyReplacers(List<PropertyReplacer> propertyReplacers) {
548        checkMutable(true);
549        this.propertyReplacers = propertyReplacers;
550    }
551
552    @Override
553    public LayoutManagerBase clone() throws CloneNotSupportedException {
554        LayoutManagerBase copy = (LayoutManagerBase) super.clone();
555
556        // Copy initialized status, but reset to created for others.
557        // This allows prototypes to bypass repeating the initialized phase.
558        if (UifConstants.ViewStatus.INITIALIZED.equals(viewStatus)) {
559            copy.viewStatus = UifConstants.ViewStatus.INITIALIZED;
560        } else {
561            copy.viewStatus = UifConstants.ViewStatus.CREATED;
562        }
563
564        return copy;
565    }
566    
567    /**
568     * Indicates whether the component has been initialized.
569     * 
570     * @return True if the component has been initialized, false if not.
571     */
572    public boolean isInitialized() {
573        return StringUtils.equals(viewStatus, ViewStatus.INITIALIZED) || isModelApplied();
574    }
575
576    /**
577     * Indicates whether the component has been updated from the model.
578     * 
579     * @return True if the component has been updated, false if not.
580     */
581    public boolean isModelApplied() {
582        return StringUtils.equals(viewStatus, ViewStatus.MODEL_APPLIED) || isFinal();
583    }
584
585    /**
586     * Indicates whether the component has been updated from the model and final updates made.
587     * 
588     * @return True if the component has been updated, false if not.
589     */
590    public boolean isFinal() {
591        return StringUtils.equals(viewStatus, ViewStatus.FINAL);
592    }
593
594    /**
595     * {@inheritDoc}
596     */
597    @Override
598    public boolean isRender() {
599        return this.render;
600    }
601
602    /**
603     * {@inheritDoc}
604     */
605    @Override
606    public void setRender(boolean render) {
607        this.render = render;
608    }
609
610}