001: //========================================================================
002: //$Id: HotDeployer.java 1113 2006-10-20 11:40:15Z janb $
003: //Copyright 2006 Mort Bay Consulting Pty. Ltd.
004: //------------------------------------------------------------------------
005: //Licensed under the Apache License, Version 2.0 (the "License");
006: //you may not use this file except in compliance with the License.
007: //You may obtain a copy of the License at
008: //http://www.apache.org/licenses/LICENSE-2.0
009: //Unless required by applicable law or agreed to in writing, software
010: //distributed under the License is distributed on an "AS IS" BASIS,
011: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: //See the License for the specific language governing permissions and
013: //limitations under the License.
014: //========================================================================
015:
016: package org.mortbay.jetty.deployer;
017:
018: import java.io.File;
019: import java.io.FilenameFilter;
020: import java.io.IOException;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: import org.mortbay.component.AbstractLifeCycle;
025: import org.mortbay.jetty.Server;
026: import org.mortbay.jetty.handler.ContextHandler;
027: import org.mortbay.jetty.handler.ContextHandlerCollection;
028: import org.mortbay.jetty.webapp.WebAppContext;
029: import org.mortbay.log.Log;
030: import org.mortbay.resource.Resource;
031: import org.mortbay.xml.XmlConfiguration;
032: import org.mortbay.util.Scanner;
033:
034: /**
035: * Context Deployer
036: *
037: * This deployer scans a designated directory by
038: * {@link #setConfigurationDir(String)} for the appearance/disappearance or
039: * changes to xml configuration files. The scan is performed at startup and at
040: * an optional hot deployment frequency specified by
041: * {@link #setScanInterval(int)}.
042: *
043: * Each configuration file is in {@link XmlConfiguration} format and represents
044: * the configuration of a instance of {@link ContextHandler} (or a subclass
045: * specified by the XML <code>Configure</code> element).
046: *
047: * The xml should configure the context and the instance is deployed to the
048: * {@link ContextHandlerCollection} specified by {@link #setContexts(Server)}.
049: *
050: * Similarly, when one of these existing files is removed, the corresponding
051: * context is undeployed; when one of these files is changed, the corresponding
052: * context is undeployed, the (changed) xml config file reapplied to it, and
053: * then (re)deployed.
054: *
055: * Note that the context itself is NOT copied into the hot deploy directory. The
056: * webapp directory or war file can exist anywhere. It is the xml config file
057: * that points to it's location and deploys it from there.
058: *
059: * It means, for example, that you can keep a "read-only" copy of your webapp
060: * somewhere, and apply different configurations to it simply by dropping
061: * different xml configuration files into the configuration directory.
062: *
063: * @org.apache.xbean.XBean element="hotDeployer" description="Creates a hot deployer
064: * to watch a directory for changes at a configurable interval."
065: *
066: */
067: public class ContextDeployer extends AbstractLifeCycle {
068: public final static String NAME = "ConfiguredDeployer";
069: private int _scanInterval = 10;
070: private Scanner _scanner;
071: private ScannerListener _scannerListener;
072: private Resource _configurationDir;
073: private Map _currentDeployments = new HashMap();
074: private ContextHandlerCollection _contexts;
075: private ConfigurationManager _configMgr;
076:
077: /* ------------------------------------------------------------ */
078: protected class ScannerListener implements Scanner.Listener {
079: /**
080: * Handle a new deployment
081: *
082: * @see org.mortbay.util.Scanner.FileAddedListener#fileAdded(java.lang.String)
083: */
084: public void fileAdded(String filename) throws Exception {
085: deploy(filename);
086: }
087:
088: /**
089: * Handle a change to an existing deployment. Undeploy then redeploy.
090: *
091: * @see org.mortbay.util.Scanner.FileChangedListener#fileChanged(java.lang.String)
092: */
093: public void fileChanged(String filename) throws Exception {
094: redeploy(filename);
095: }
096:
097: /**
098: * Handle an undeploy.
099: *
100: * @see org.mortbay.util.Scanner.FileRemovedListener#fileRemoved(java.lang.String)
101: */
102: public void fileRemoved(String filename) throws Exception {
103: undeploy(filename);
104: }
105: }
106:
107: /**
108: * Constructor
109: *
110: * @throws Exception
111: */
112: public ContextDeployer() throws Exception {
113: // set up the default scan location to be $jetty.home/webapps
114: String home = System.getProperty("jetty.home");
115: if (home == null)
116: home = ".";
117: Log.debug("jetty.home=" + home);
118: setConfigurationDir(Resource.newResource(home).addPath(
119: "webapps"));
120: Log.debug("hot deploy dir="
121: + _configurationDir.getFile().getCanonicalPath());
122: _scanner = new Scanner();
123: }
124:
125: /* ------------------------------------------------------------ */
126: /**
127: * @return the ContextHandlerColletion to which to deploy the contexts
128: */
129: public ContextHandlerCollection getContexts() {
130: return _contexts;
131: }
132:
133: /* ------------------------------------------------------------ */
134: /**
135: * Associate with a {@link ContextHandlerCollection}.
136: *
137: * @param contexts
138: * the ContextHandlerColletion to which to deploy the contexts
139: */
140: public void setContexts(ContextHandlerCollection contexts) {
141: if (isStarted() || isStarting())
142: throw new IllegalStateException(
143: "Cannot set Contexts after deployer start");
144: _contexts = contexts;
145: }
146:
147: /* ------------------------------------------------------------ */
148: /**
149: * @param seconds
150: * The period in second between scans for changed configuration
151: * files. A zero or negative interval disables hot deployment
152: */
153: public void setScanInterval(int seconds) {
154: if (isStarted() || isStarting())
155: throw new IllegalStateException(
156: "Cannot change scan interval after deployer start");
157: _scanInterval = seconds;
158: }
159:
160: /* ------------------------------------------------------------ */
161: public int getScanInterval() {
162: return _scanInterval;
163: }
164:
165: /* ------------------------------------------------------------ */
166: /**
167: * @param dir
168: * @throws Exception
169: */
170: public void setConfigurationDir(String dir) throws Exception {
171: setConfigurationDir(Resource.newResource(dir));
172: }
173:
174: /* ------------------------------------------------------------ */
175: /**
176: * @param file
177: * @throws Exception
178: */
179: public void setConfigurationDir(File file) throws Exception {
180: setConfigurationDir(Resource.newResource(file.toURL()));
181: }
182:
183: /* ------------------------------------------------------------ */
184: /**
185: * @param resource
186: */
187: public void setConfigurationDir(Resource resource) {
188: if (isStarted() || isStarting())
189: throw new IllegalStateException(
190: "Cannot change hot deploy dir after deployer start");
191: _configurationDir = resource;
192: }
193:
194: /* ------------------------------------------------------------ */
195: /**
196: * @param directory
197: */
198: public void setDirectory(String directory) throws Exception {
199: setConfigurationDir(directory);
200: }
201:
202: /* ------------------------------------------------------------ */
203: /**
204: * @return
205: */
206: public String getDirectory() {
207: return getConfigurationDir().getName();
208: }
209:
210: /* ------------------------------------------------------------ */
211: /**
212: * @return
213: */
214: public Resource getConfigurationDir() {
215: return _configurationDir;
216: }
217:
218: /* ------------------------------------------------------------ */
219: /**
220: * @param configMgr
221: */
222: public void setConfigurationManager(ConfigurationManager configMgr) {
223: _configMgr = configMgr;
224: }
225:
226: /* ------------------------------------------------------------ */
227: /**
228: * @return
229: */
230: public ConfigurationManager getConfigurationManager() {
231: return _configMgr;
232: }
233:
234: /* ------------------------------------------------------------ */
235: private void deploy(String filename) throws Exception {
236: ContextHandler context = createContext(filename);
237: Log.info("Deploy " + filename + " -> " + context);
238: _contexts.addHandler(context);
239: _currentDeployments.put(filename, context);
240: if (_contexts.isStarted())
241: context.start();
242: }
243:
244: /* ------------------------------------------------------------ */
245: private void undeploy(String filename) throws Exception {
246: ContextHandler context = (ContextHandler) _currentDeployments
247: .get(filename);
248: Log.info("Undeploy " + filename + " -> " + context);
249: if (context == null)
250: return;
251: context.stop();
252: _contexts.removeHandler(context);
253: _currentDeployments.remove(filename);
254: }
255:
256: /* ------------------------------------------------------------ */
257: private void redeploy(String filename) throws Exception {
258: undeploy(filename);
259: deploy(filename);
260: }
261:
262: /* ------------------------------------------------------------ */
263: /**
264: * Start the hot deployer looking for webapps to deploy/undeploy
265: *
266: * @see org.mortbay.component.AbstractLifeCycle#doStart()
267: */
268: protected void doStart() throws Exception {
269: if (_configurationDir == null)
270: throw new IllegalStateException(
271: "No configuraition dir specified");
272:
273: if (_contexts == null)
274: throw new IllegalStateException(
275: "No context handler collection specified for deployer");
276:
277: _scanner.setScanDir(_configurationDir.getFile());
278: _scanner.setScanInterval(getScanInterval());
279: // Accept changes only in files that could be the equivalent of
280: // jetty-web.xml files.
281: // That is, files that configure a single webapp.
282: _scanner.setFilenameFilter(new FilenameFilter() {
283: public boolean accept(File dir, String name) {
284: try {
285: if (name.endsWith(".xml")
286: && dir.equals(getConfigurationDir()
287: .getFile()))
288: return true;
289: return false;
290: } catch (IOException e) {
291: Log.warn(e);
292: return false;
293: }
294: }
295: });
296: _scannerListener = new ScannerListener();
297: _scanner.addListener(_scannerListener);
298: _scanner.scan();
299: _scanner.start();
300: _contexts.getServer().getContainer().addBean(_scanner);
301: }
302:
303: /* ------------------------------------------------------------ */
304: /**
305: * Stop the hot deployer.
306: *
307: * @see org.mortbay.component.AbstractLifeCycle#doStop()
308: */
309: protected void doStop() throws Exception {
310: _scanner.removeListener(_scannerListener);
311: _scanner.stop();
312: }
313:
314: /* ------------------------------------------------------------ */
315: /**
316: * Create a WebAppContext for the webapp being hot deployed, then apply the
317: * xml config file to it to configure it.
318: *
319: * @param filename
320: * the config file found in the hot deploy directory
321: * @return
322: * @throws Exception
323: */
324: private ContextHandler createContext(String filename)
325: throws Exception {
326: // The config file can call any method on WebAppContext to configure
327: // the webapp being deployed.
328: File hotDeployXmlFile = new File(filename);
329: if (!hotDeployXmlFile.exists())
330: return null;
331:
332: XmlConfiguration xmlConfiguration = new XmlConfiguration(
333: hotDeployXmlFile.toURL());
334: if (_configMgr != null)
335: xmlConfiguration.setProperties(_configMgr.getProperties());
336: ContextHandler context = (ContextHandler) xmlConfiguration
337: .configure();
338: return context;
339: }
340:
341: }
|