001/**
002 * Copyright 2005-2014 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.util;
017
018import org.kuali.rice.core.framework.util.ReflectionUtils;
019import org.kuali.rice.krad.uif.UifConstants;
020import org.kuali.rice.krad.uif.component.Component;
021import org.kuali.rice.krad.uif.container.CollectionGroup;
022import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
023
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.LinkedList;
028import java.util.List;
029import java.util.Map;
030import java.util.Queue;
031
032/**
033 * Utility methods related to handling context for components.
034 *
035 * @author Kuali Rice Team (rice.collab@kuali.org)
036 */
037public class ContextUtils {
038
039    private ContextUtils() {}
040
041    /**
042     * places a key, value pair in each context map of a list of components
043     *
044     * @param elements the list of elements
045     * @param contextName a value to be used as a key to retrieve the object
046     * @param contextValue the value to be placed in the context
047     */
048    public static void pushObjectToContextDeep(Collection<? extends LifecycleElement> elements, String contextName,
049            Object contextValue) {
050        if (elements == null || elements.isEmpty()) {
051            return;
052        }
053
054        Queue<LifecycleElement> elementQueue = new LinkedList<LifecycleElement>();
055
056        try {
057            elementQueue.addAll(elements);
058            while (!elementQueue.isEmpty()) {
059                LifecycleElement currentElement = elementQueue.poll();
060
061                if (currentElement == null) {
062                    continue;
063                }
064
065                if (currentElement instanceof Component) {
066                    ((Component) currentElement).pushObjectToContext(contextName, contextValue);
067                }
068
069                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
070            }
071        } finally {
072            elementQueue.clear();
073            RecycleUtils.recycle(elementQueue);
074        }
075    }
076
077    /**
078     * Pushes object to a component's context so that it is available from
079     * {@link org.kuali.rice.krad.uif.component.Component#getContext()}
080     *
081     * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
082     * are also updated recursively</p>
083     *
084     * @param component the component whose context is to be updated
085     * @param contextName a value to be used as a key to retrieve the object
086     * @param contextValue the value to be placed in the context
087     */
088    public static void pushObjectToContextDeep(Component component, String contextName, Object contextValue) {
089        if (component == null) {
090            return;
091        }
092
093        pushObjectToContextDeep(Collections.singletonList(component), contextName, contextValue);
094    }
095
096    /**
097     * Pushes object to a component's context so that it is available from
098     * {@link org.kuali.rice.krad.uif.component.Component#getContext()}
099     *
100     * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
101     * are also updated recursively</p>
102     *
103     * @param component the component whose context is to be updated
104     * @param sourceContext The source context map.
105     */
106    public static void pushAllToContextDeep(Component component, Map<String, Object> sourceContext) {
107        if (component == null) {
108            return;
109        }
110
111        pushAllToContextDeep(Collections.singletonList(component), sourceContext);
112    }
113
114    /**
115     * Places a all entries from a map into each context map of a list of components.
116     *
117     * @param components The list components.
118     * @param sourceContext The source context map.
119     */
120    public static void pushAllToContextDeep(List<? extends Component> components, Map<String, Object> sourceContext) {
121        if (components == null || components.isEmpty()) {
122            return;
123        }
124
125        @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(
126                LinkedList.class);
127        try {
128            elementQueue.addAll(components);
129            while (!elementQueue.isEmpty()) {
130                LifecycleElement currentElement = elementQueue.poll();
131
132                if (currentElement == null) {
133                    continue;
134                }
135
136                if (currentElement instanceof Component) {
137                    ((Component) currentElement).pushAllToContext(sourceContext);
138                }
139
140                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
141            }
142        } finally {
143            elementQueue.clear();
144            RecycleUtils.recycle(elementQueue);
145        }
146    }
147
148    /**
149     * Update the contexts of the given components.
150     *
151     * @param components the components whose components to update
152     * @param collectionGroup collection group the components are associated with
153     * @param collectionLine an instance of the data object for the line
154     * @param lineIndex the line index
155     * @param lineSuffix id suffix for components in the line to make them unique
156     */
157    public static void updateContextsForLine(List<? extends Component> components, CollectionGroup collectionGroup,
158            Object collectionLine, int lineIndex, String lineSuffix) {
159        for (Component component : components) {
160            updateContextForLine(component, collectionGroup, collectionLine, lineIndex, lineSuffix);
161        }
162    }
163
164    /**
165     * Update the context map for the given component with the collection context.
166     *
167     * <p>The values of {@code UifConstants.ContextVariableNames.LINE} and {@code UifConstants.ContextVariableNames.INDEX}
168     * are set to {@code collectionLine} and {@code lineIndex} respectively.</p>
169     *
170     * @param component the component whose context is to be updated
171     * @param collectionGroup collection group the component is associated with
172     * @param collectionLine an instance of the data object for the line
173     * @param lineIndex the line index
174     * @param lineSuffix id suffix for components in the line to make them unique
175     */
176    public static void updateContextForLine(Component component, CollectionGroup collectionGroup, Object collectionLine,
177            int lineIndex, String lineSuffix) {
178        Map<String, Object> toUpdate = new HashMap<String, Object>(5);
179
180        toUpdate.put(UifConstants.ContextVariableNames.COLLECTION_GROUP, collectionGroup);
181        toUpdate.put(UifConstants.ContextVariableNames.LINE, collectionLine);
182        toUpdate.put(UifConstants.ContextVariableNames.INDEX, Integer.valueOf(lineIndex));
183        toUpdate.put(UifConstants.ContextVariableNames.LINE_SUFFIX, lineSuffix);
184
185        boolean isAddLine = (lineIndex == -1);
186        toUpdate.put(UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine);
187
188        pushAllToContextDeep(component, toUpdate);
189    }
190
191    /**
192     * Sets the context of the given lifecycle element to null, then using reflection recursively finds any
193     * lifecycle element children and sets their context to null.
194     *
195     * @param lifecycleElement lifecycle element instance to clean
196     */
197    public static void cleanContextDeep(LifecycleElement lifecycleElement) {
198        if (lifecycleElement == null) {
199            return;
200        }
201
202        lifecycleElement.setContext(null);
203
204        // find any children that are lifecycle elements and clean them as well
205        Class<?> elementClass = lifecycleElement.getClass();
206
207        List<java.lang.reflect.Field> fields = ReflectionUtils.getAllFields(elementClass);
208        for (java.lang.reflect.Field field : fields) {
209            // Check for lists that can contain lifecycle elements
210            if (Collection.class.isAssignableFrom(field.getType())) {
211                ReflectionUtils.makeAccessible(field);
212                Collection<Object> elements = (Collection<Object>) ReflectionUtils.getField(field, lifecycleElement);
213                if (elements != null) {
214                    for (Object element : elements) {
215                        if (element != null && LifecycleElement.class.isAssignableFrom(element.getClass())) {
216                            cleanContextDeep((LifecycleElement) element);
217                        }
218                    }
219                }
220            // Check for Maps that can contain lifecycle elements
221            } else if (Map.class.isAssignableFrom(field.getType())) {
222                ReflectionUtils.makeAccessible(field);
223                Map<Object, Object> elements = (Map<Object, Object>) ReflectionUtils.getField(field, lifecycleElement);
224                if (elements != null) {
225                    for (Object element : elements.entrySet()) {
226                        if (element != null && LifecycleElement.class.isAssignableFrom(element.getClass())) {
227                            cleanContextDeep((LifecycleElement) element);
228                        }
229                    }
230                }
231            // Check if field is a lifecycle element itself
232            } else if (LifecycleElement.class.isAssignableFrom(field.getType())) {
233                ReflectionUtils.makeAccessible(field);
234                LifecycleElement nestedElement = (LifecycleElement) ReflectionUtils.getField(field, lifecycleElement);
235
236                cleanContextDeep(nestedElement);
237            }
238        }
239    }
240}