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.engine.builder;
017:
018: import java.io.IOException;
019:
020: import org.apache.commons.logging.Log;
021: import org.apache.commons.logging.LogFactory;
022: import org.springframework.core.io.Resource;
023: import org.springframework.webflow.definition.FlowDefinition;
024: import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
025: import org.springframework.webflow.definition.registry.FlowDefinitionHolder;
026: import org.springframework.webflow.engine.Flow;
027: import org.springframework.webflow.util.ResourceHolder;
028:
029: /**
030: * A flow definition holder that can detect changes on an underlying flow
031: * definition resource and refresh that resource automatically.
032: * <p>
033: * This class is threadsafe.
034: * <p>
035: * Note that this {@link FlowDefinition} holder uses a {@link Flow} assembler.
036: * This is normal since a {@link Flow} is a {@link FlowDefinition}! This class
037: * bridges the <i>abstract</i> world of {@link FlowDefinition flow definitions}
038: * with the <i>concrete</i> world of {@link Flow flow implementations}.
039: *
040: * @see FlowDefinition
041: * @see Flow
042: * @see FlowAssembler
043: *
044: * @author Keith Donald
045: */
046: public class RefreshableFlowDefinitionHolder implements
047: FlowDefinitionHolder {
048:
049: private static final Log logger = LogFactory
050: .getLog(RefreshableFlowDefinitionHolder.class);
051:
052: /**
053: * The flow definition assembled by this assembler.
054: */
055: private FlowDefinition flowDefinition;
056:
057: /**
058: * The flow assembler.
059: */
060: private FlowAssembler assembler;
061:
062: /**
063: * A last modified date for the backing flow definition resource, used to support
064: * automatic reassembly on resource change.
065: */
066: private long lastModified;
067:
068: /**
069: * A flag indicating whether or not this holder is in the middle of the
070: * assembly process.
071: */
072: private boolean assembling;
073:
074: /**
075: * Creates a new refreshable flow definition holder that uses the configured
076: * assembler (GOF director) to drive flow assembly, on initial use and on any
077: * resource change or refresh.
078: * @param assembler the flow assembler to use
079: */
080: public RefreshableFlowDefinitionHolder(FlowAssembler assembler) {
081: this .assembler = assembler;
082: }
083:
084: public String getFlowDefinitionId() {
085: return assembler.getFlowId();
086: }
087:
088: public synchronized FlowDefinition getFlowDefinition()
089: throws FlowDefinitionConstructionException {
090: if (assembling) {
091: // must return early assembly result
092: return getFlowBuilder().getFlow();
093: }
094: if (!isAssembled()) {
095: lastModified = calculateLastModified();
096: assembleFlow();
097: } else {
098: refreshIfChanged();
099: }
100: return flowDefinition;
101: }
102:
103: public synchronized void refresh() throws FlowBuilderException {
104: assembleFlow();
105: }
106:
107: // internal helpers
108:
109: /**
110: * Returns the flow builder that actually builds the Flow definition.
111: */
112: protected FlowBuilder getFlowBuilder() {
113: return assembler.getFlowBuilder();
114: }
115:
116: /**
117: * Reassemble the flow if its underlying resource has changed.
118: */
119: protected void refreshIfChanged() {
120: if (this .lastModified == -1) {
121: // just ignore, tracking last modified date not supported
122: return;
123: }
124: long calculatedLastModified = calculateLastModified();
125: if (this .lastModified < calculatedLastModified) {
126: if (logger.isDebugEnabled()) {
127: logger
128: .debug("Resource modification detected, reloading flow definition with id '"
129: + assembler.getFlowId() + "'");
130: }
131: assembleFlow();
132: this .lastModified = calculatedLastModified;
133: }
134: }
135:
136: /**
137: * Helper that retrieves the last modified date by querying the backing flow
138: * resource.
139: * @return the last modified date, or -1 if it could not be retrieved
140: */
141: protected long calculateLastModified() {
142: if (getFlowBuilder() instanceof ResourceHolder) {
143: Resource resource = ((ResourceHolder) getFlowBuilder())
144: .getResource();
145: if (logger.isDebugEnabled()) {
146: logger
147: .debug("Calculating last modified timestamp for flow definition resource '"
148: + resource + "'");
149: }
150: try {
151: return resource.getFile().lastModified();
152: } catch (IOException e) {
153: // ignore, last modified checks not supported
154: }
155: }
156: return -1;
157: }
158:
159: /**
160: * Returns the last modifed date of the backed flow definition resource.
161: * @return the last modified date
162: */
163: protected long getLastModified() {
164: return lastModified;
165: }
166:
167: /**
168: * Assemble the held flow definition, delegating to the configured
169: * FlowAssembler (director).
170: */
171: protected void assembleFlow() throws FlowBuilderException {
172: if (logger.isDebugEnabled()) {
173: logger.debug("Assembling flow definition with id '"
174: + assembler.getFlowId() + "'");
175: }
176: try {
177: assembling = true;
178: flowDefinition = assembler.assembleFlow();
179: } finally {
180: assembling = false;
181: }
182: }
183:
184: /**
185: * Returns a flag indicating if this holder has performed and completed
186: * flow definition assembly.
187: */
188: protected boolean isAssembled() {
189: return flowDefinition != null;
190: }
191:
192: /**
193: * Returns a flag indicating if this holder is performing assembly.
194: */
195: protected boolean isAssembling() {
196: return assembling;
197: }
198: }
|