001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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: */
016: package org.springframework.webflow.definition.registry;
017:
018: import java.util.Iterator;
019: import java.util.LinkedList;
020: import java.util.Map;
021: import java.util.TreeMap;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.springframework.core.style.ToStringCreator;
026: import org.springframework.util.Assert;
027: import org.springframework.webflow.definition.FlowDefinition;
028:
029: /**
030: * A generic registry implementation for housing one or more flow definitions.
031: * <p>
032: * This registry may be refreshed at runtime to "hot reload" refreshable flow
033: * definitions. Note that the refresh will only reload already registered
034: * flow definitions but will not detect any new flow definitions or remove
035: * flow definitions that no longer exist.
036: *
037: * @author Keith Donald
038: */
039: public class FlowDefinitionRegistryImpl implements
040: FlowDefinitionRegistry {
041:
042: private static final Log logger = LogFactory
043: .getLog(FlowDefinitionRegistryImpl.class);
044:
045: /**
046: * The map of loaded Flow definitions maintained in this registry.
047: */
048: private Map flowDefinitions = new TreeMap();
049:
050: /**
051: * An optional parent flow definition registry.
052: */
053: private FlowDefinitionRegistry parent;
054:
055: // implementing FlowDefinitionRegistryMBean
056:
057: public String[] getFlowDefinitionIds() {
058: return (String[]) flowDefinitions.keySet().toArray(
059: new String[flowDefinitions.size()]);
060: }
061:
062: public int getFlowDefinitionCount() {
063: return flowDefinitions.size();
064: }
065:
066: public boolean containsFlowDefinition(String id) {
067: Assert.hasText(id, "The flow id is required");
068: return flowDefinitions.get(id) != null;
069: }
070:
071: public void refresh() throws FlowDefinitionConstructionException {
072: if (logger.isDebugEnabled()) {
073: logger.debug("Refreshing flow definition registry '" + this
074: + "'");
075: }
076: ClassLoader loader = Thread.currentThread()
077: .getContextClassLoader();
078: try {
079: // workaround for JMX
080: Thread.currentThread().setContextClassLoader(
081: getClass().getClassLoader());
082: LinkedList needsReindexing = new LinkedList();
083: Iterator it = flowDefinitions.entrySet().iterator();
084: while (it.hasNext()) {
085: Map.Entry entry = (Map.Entry) it.next();
086: String key = (String) entry.getKey();
087: FlowDefinitionHolder holder = (FlowDefinitionHolder) entry
088: .getValue();
089: holder.refresh();
090: if (!holder.getFlowDefinitionId().equals(key)) {
091: needsReindexing.add(new Indexed(key, holder));
092: }
093: }
094: it = needsReindexing.iterator();
095: while (it.hasNext()) {
096: Indexed indexed = (Indexed) it.next();
097: reindex(indexed.holder, indexed.key);
098: }
099: } finally {
100: Thread.currentThread().setContextClassLoader(loader);
101: }
102: }
103:
104: public void refresh(String flowId)
105: throws NoSuchFlowDefinitionException,
106: FlowDefinitionConstructionException {
107: if (logger.isDebugEnabled()) {
108: logger.debug("Refreshing flow with id '" + flowId + "'");
109: }
110: ClassLoader loader = Thread.currentThread()
111: .getContextClassLoader();
112: try {
113: // workaround for JMX
114: Thread.currentThread().setContextClassLoader(
115: getClass().getClassLoader());
116: FlowDefinitionHolder holder = getFlowDefinitionHolder(flowId);
117: holder.refresh();
118: if (!holder.getFlowDefinitionId().equals(flowId)) {
119: reindex(holder, flowId);
120: }
121: } finally {
122: Thread.currentThread().setContextClassLoader(loader);
123: }
124: }
125:
126: // implementing FlowDefinitionLocator
127:
128: public FlowDefinition getFlowDefinition(String id)
129: throws NoSuchFlowDefinitionException,
130: FlowDefinitionConstructionException {
131: Assert
132: .hasText(
133: id,
134: "Unable to load a flow definition: no flow id was provided. Please provide a valid flow identifier.");
135: if (logger.isDebugEnabled()) {
136: logger
137: .debug("Getting flow definition with id '" + id
138: + "'");
139: }
140: try {
141: return getFlowDefinitionHolder(id).getFlowDefinition();
142: } catch (NoSuchFlowDefinitionException e) {
143: if (parent != null) {
144: // try parent
145: return parent.getFlowDefinition(id);
146: }
147: throw e;
148: }
149: }
150:
151: // implementing FlowDefinitionRegistry
152:
153: public void setParent(FlowDefinitionRegistry parent) {
154: if (logger.isDebugEnabled()) {
155: logger.debug("Setting parent flow definition registry to '"
156: + parent + "'");
157: }
158: this .parent = parent;
159: }
160:
161: public FlowDefinition[] getFlowDefinitions()
162: throws FlowDefinitionConstructionException {
163: FlowDefinition[] flows = new FlowDefinition[flowDefinitions
164: .size()];
165: Iterator it = flowDefinitions.values().iterator();
166: int i = 0;
167: while (it.hasNext()) {
168: FlowDefinitionHolder holder = (FlowDefinitionHolder) it
169: .next();
170: flows[i] = holder.getFlowDefinition();
171: i++;
172: }
173: return flows;
174: }
175:
176: public void registerFlowDefinition(FlowDefinitionHolder flowHolder) {
177: Assert.notNull(flowHolder,
178: "The flow definition holder to register is required");
179: if (logger.isDebugEnabled()) {
180: logger.debug("Registering flow definition with id '"
181: + flowHolder.getFlowDefinitionId() + "'");
182: }
183: index(flowHolder);
184: }
185:
186: /**
187: * Remove identified flow definition from this registry. If the given
188: * id is not known in this registry, nothing will happen.
189: * @param id the flow definition id
190: */
191: public void removeFlowDefinition(String id) {
192: Assert.hasText(id, "The flow id is required");
193: if (logger.isDebugEnabled()) {
194: logger.debug("Removing flow definition with id '" + id
195: + "'");
196: }
197: flowDefinitions.remove(id);
198: }
199:
200: // internal helpers
201:
202: /**
203: * Reindex given flow definition.
204: * @param holder the holder holding the flow definition to reindex
205: * @param oldId the id that was previously assigned to given flow definition
206: */
207: private void reindex(FlowDefinitionHolder holder, String oldId) {
208: flowDefinitions.remove(oldId);
209: index(holder);
210: }
211:
212: /**
213: * Index given flow definition.
214: * @param holder the holder holding the flow definition to index
215: */
216: private void index(FlowDefinitionHolder holder) {
217: Assert
218: .hasText(holder.getFlowDefinitionId(),
219: "The flow holder to index must return a non-blank flow id");
220: flowDefinitions.put(holder.getFlowDefinitionId(), holder);
221: }
222:
223: /**
224: * Returns the identified flow definition holder. Throws an exception
225: * if it cannot be found.
226: */
227: private FlowDefinitionHolder getFlowDefinitionHolder(String id)
228: throws NoSuchFlowDefinitionException {
229: FlowDefinitionHolder flowHolder = (FlowDefinitionHolder) flowDefinitions
230: .get(id);
231: if (flowHolder == null) {
232: throw new NoSuchFlowDefinitionException(id,
233: getFlowDefinitionIds());
234: }
235: return flowHolder;
236: }
237:
238: /**
239: * Simple value object that holds the key for an indexed flow definition
240: * holder in this registry. Used to support reindexing on a refresh.
241: *
242: * @author Keith Donald
243: */
244: private static class Indexed {
245:
246: private String key;
247:
248: private FlowDefinitionHolder holder;
249:
250: public Indexed(String key, FlowDefinitionHolder holder) {
251: this .key = key;
252: this .holder = holder;
253: }
254: }
255:
256: public String toString() {
257: return new ToStringCreator(this ).append("flowDefinitions",
258: flowDefinitions).append("parent", parent).toString();
259: }
260: }
|