001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.varia.deployment;
023:
024: import java.io.File;
025: import java.io.FileInputStream;
026: import java.io.FileOutputStream;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.OutputStream;
030:
031: import java.net.URL;
032: import java.net.MalformedURLException;
033:
034: import java.util.ArrayList;
035: import java.util.Collection;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.ListIterator;
039: import java.util.Enumeration;
040: import java.util.HashMap;
041: import java.util.Map;
042: import java.util.jar.JarFile;
043: import java.util.jar.JarEntry;
044:
045: import javax.management.MBeanServer;
046: import javax.management.MalformedObjectNameException;
047: import javax.management.ObjectName;
048:
049: import org.jboss.deployment.DeploymentException;
050: import org.jboss.deployment.DeploymentInfo;
051: import org.jboss.deployment.MainDeployerMBean;
052: import org.jboss.deployment.SubDeployer;
053: import org.jboss.deployment.SubDeployerSupport;
054:
055: import org.jboss.system.ServiceControllerMBean;
056: import org.jboss.system.server.ServerConfig;
057: import org.jboss.system.server.ServerConfigLocator;
058:
059: import org.jboss.util.Counter;
060: import org.jboss.util.file.Files;
061: import org.jboss.util.file.JarUtils;
062: import org.jboss.mx.util.MBeanProxyExt;
063:
064: import org.jboss.varia.deployment.convertor.Convertor;
065:
066: /**
067: * This is the deployer for other vendor's applications
068: * with dynamic migration of vendor-specific DDs to
069: * JBoss specific DDs.
070: *
071: * @see org.jboss.varia.deployment.convertor.Convertor
072: *
073: * @author <a href="mailto:andreas@jboss.org">Andreas Schaefer</a>
074: * @version $Revision: 57210 $
075: *
076: * @jmx.mbean
077: * name="jboss.system:service=ServiceDeployer"
078: * extends="org.jboss.deployment.SubDeployerMBean"
079: */
080: public class FoeDeployer extends SubDeployerSupport implements
081: SubDeployer, FoeDeployerMBean {
082: // Attributes ----------------------------------------------------
083: /** A proxy to the ServiceControllerDeployer. */
084: private ServiceControllerMBean serviceController;
085:
086: /** The deployers scratch directory. */
087: private File scratchDirectory;
088:
089: /** Contains the list of available converters */
090: private List converterList = new ArrayList();
091:
092: /** an increment for tmp files */
093: private final Counter id = Counter.makeSynchronized(new Counter(0));
094:
095: /** map of exploaded deployment destionation in scratch directoy by DeploymentInfo */
096: private ThreadLocal destinationByDI = new ThreadLocal() {
097: protected Object initialValue() {
098: return new HashMap();
099: }
100: };
101:
102: // SubDeployerSupport overrides ----------------------------------
103: /**
104: * Returns true if the there is a converter available to convert
105: * the deployment unit.
106: *
107: * @jmx.managed-operation
108: */
109: public boolean accepts(DeploymentInfo di) {
110: // delegate accepts to convertors
111: Iterator i = converterList.iterator();
112: while (i.hasNext()) {
113: Convertor converter = (Convertor) i.next();
114: if (converter.accepts(di.url)) {
115: return true;
116: }
117: }
118: return false;
119: }
120:
121: /**
122: * Returns true if the there is a converter available to convert
123: * the deployment unit.
124: */
125: public boolean accepts(URL url) {
126: // delegate accepts to convertors
127: Iterator i = converterList.iterator();
128: while (i.hasNext()) {
129: Convertor converter = (Convertor) i.next();
130: if (converter.accepts(url)) {
131: return true;
132: }
133: }
134: return false;
135: }
136:
137: /**
138: * At the init phase the deployment unit and its subdeployment units are unpacked.
139: * @jmx.managed-operation
140: */
141: public void init(DeploymentInfo di) throws DeploymentException {
142: // Determine the destination for unpacking and save it in the ThreadLocal
143: Map destinations = (Map) destinationByDI.get();
144: File destination = (File) destinations.get(di.parent);
145: if (destination == null) {
146: // Loop until for new destination
147: while (destination == null || destination.exists())
148: destination = new File(scratchDirectory, id.increment()
149: + "." + di.shortName);
150: } else {
151: destination = new File(destination, di.shortName);
152: }
153: destinations.put(di, destination);
154: destinationByDI.set(destinations);
155:
156: try {
157: log.debug("unpacking to " + destination);
158: inflateJar(di.localUrl, destination);
159: } catch (Exception e) {
160: throw new DeploymentException("Unpacking failed: ", e);
161: }
162:
163: // invoke super class' initialization
164: super .init(di);
165: }
166:
167: /**
168: * At the create phase, the conversion and packing is done.
169: * @jmx.managed-operation
170: */
171: public void create(DeploymentInfo di) throws DeploymentException {
172: try {
173: // fetch the destionation of unpacked deployment from ThreadLocal
174: Map destinations = (Map) destinationByDI.get();
175: File inflateDest = (File) destinations.get(di);
176:
177: // Look for the converter that accepts vendor specific deployment descriptors
178: // and let it convert them
179: Iterator i = converterList.iterator();
180: while (i.hasNext()) {
181: Convertor converter = (Convertor) i.next();
182: if (converter.accepts(di.url)) {
183: // Convert them to JBoss specific DDs
184: converter.convert(di, inflateDest);
185: // Now conversion is done and we can leave
186: break;
187: }
188: }
189:
190: // deflate
191: File deflateDest = (File) destinations.get(di.parent);
192: if (deflateDest == null)
193: deflateDest = scratchDirectory;
194: String validName = null;
195: if (di.shortName.endsWith(".wl"))
196: validName = di.shortName.substring(0, di.shortName
197: .length() - 3);
198: else
199: validName = di.shortName.substring(0, di.shortName
200: .length() - 4)
201: + "jar";
202: File convertedUnit = new File(deflateDest, validName);
203: log.debug("deflating to " + convertedUnit);
204: deflateJar(convertedUnit, inflateDest);
205:
206: // remove unpacked deployment unit
207: Files.delete(inflateDest);
208:
209: // copy the converted app back to the deployment directory
210: if (di.parent == null)
211: copyFile(convertedUnit, new File(di.url.getFile())
212: .getParentFile());
213: } catch (Exception e) {
214: log.error("Conversion error: ", e);
215: }
216: }
217:
218: /**
219: * This method stops this deployment because it is not of any
220: * use anymore (conversion is done)
221: * @jmx.managed-operation
222: */
223: public void start(DeploymentInfo di) throws DeploymentException {
224: stop(di);
225: destroy(di);
226: }
227:
228: /**
229: * @jmx.managed-operation
230: */
231: public void stop(DeploymentInfo di) {
232: log.debug("undeploying application: " + di.url);
233: }
234:
235: /**
236: * @jmx.managed-operation
237: */
238: public void destroy(DeploymentInfo di) {
239: List services = di.mbeans;
240: int lastService = services.size();
241: for (ListIterator i = services.listIterator(lastService); i
242: .hasPrevious();) {
243: ObjectName name = (ObjectName) i.previous();
244: log.debug("destroying mbean " + name);
245: try {
246: serviceController.destroy(name);
247: } catch (Exception e) {
248: log.error("Could not destroy mbean: " + name, e);
249: }
250: }
251:
252: for (ListIterator i = services.listIterator(lastService); i
253: .hasPrevious();) {
254: ObjectName name = (ObjectName) i.previous();
255: log.debug("removing mbean " + name);
256: try {
257: serviceController.remove(name);
258: } catch (Exception e) {
259: log.error("Could not remove mbean: " + name, e);
260: }
261: }
262: }
263:
264: /**
265: * This method is called in SubDeployerSupport.processNestedDeployments()
266: * The method is overriden to deploy the deployments acceptable by FoeDeployer only.
267: */
268: protected void addDeployableJar(DeploymentInfo di, JarFile jarFile)
269: throws DeploymentException {
270: String urlPrefix = "jar:" + di.localUrl.toString() + "!/";
271: for (Enumeration e = jarFile.entries(); e.hasMoreElements();) {
272: JarEntry entry = (JarEntry) e.nextElement();
273: String name = entry.getName();
274: try {
275: URL url = new URL(urlPrefix + name);
276: if (isDeployable(name, url)) {
277: // Obtain a jar url for the nested jar
278: // Append the ".wl" suffix to prevent other than FoeDeployer deployers'
279: // attempts to deploy the deployment unit
280: URL nestedURL = JarUtils.extractNestedJar(url,
281: this .tempDeployDir);
282: File file = new File(nestedURL.getFile());
283: File wlFile = new File(nestedURL.getFile() + ".wl");
284: file.renameTo(wlFile);
285:
286: if (accepts(wlFile.toURL())) {
287: deployUrl(di, wlFile.toURL(), name + ".wl");
288: } else {
289: // if the deployment isn't accepted rename it back
290: wlFile.renameTo(new File(nestedURL.getFile()));
291: }
292: }
293: } catch (MalformedURLException mue) {
294: log.warn("Jar entry invalid; ignoring: " + name, mue);
295: } catch (IOException ex) {
296: log.warn("Failed to extract nested jar; ignoring: "
297: + name, ex);
298: }
299: }
300: }
301:
302: /**
303: * The startService method
304: * - gets the mbeanProxies for MainDeployer and ServiceController;
305: * - creates scratch directory for foe work.
306: *
307: * @exception Exception if an error occurs
308: */
309: protected void startService() throws Exception {
310: mainDeployer = (MainDeployerMBean) MBeanProxyExt.create(
311: MainDeployerMBean.class, MainDeployerMBean.OBJECT_NAME,
312: server);
313:
314: // get the controller proxy
315: serviceController = (ServiceControllerMBean) MBeanProxyExt
316: .create(ServiceControllerMBean.class,
317: ServiceControllerMBean.OBJECT_NAME, server);
318:
319: ServerConfig config = ServerConfigLocator.locate();
320:
321: // build the scratch directory
322: File tempDirectory = config.getServerTempDir();
323: scratchDirectory = new File(tempDirectory, "foe");
324: if (!scratchDirectory.exists())
325: scratchDirectory.mkdirs();
326:
327: // Note: this should go the last.
328: // scratch directory must be created before this call
329: super .startService();
330: }
331:
332: /**
333: * Returns the ObjectName
334: */
335: protected ObjectName getObjectName(MBeanServer server,
336: ObjectName name) throws MalformedObjectNameException {
337: return name == null ? OBJECT_NAME : name;
338: }
339:
340: // FoeDeployerMBean implementation -------------------------------
341: /**
342: * Add a new conveter to the list. If the same converter is
343: * added, this new one won't be added, meaning everything stays the same.
344: * This method is normally called by a Converter to be
345: * called by this deployer to convert.
346: *
347: * @param converter New Converter to be added
348: *
349: * @jmx.managed-operation
350: */
351: public void addConvertor(Convertor converter) {
352: converterList.add(converter);
353:
354: // try to deploy waiting deployment units
355: // note: there is no need to synchronize, because MainDeployer
356: // returns a copy of waiting deployments
357: Collection waitingDeployments = mainDeployer
358: .listWaitingForDeployer();
359: if ((waitingDeployments != null)
360: && (waitingDeployments.size() > 0)) {
361: for (Iterator iter = waitingDeployments.iterator(); iter
362: .hasNext();) {
363: DeploymentInfo di = (DeploymentInfo) iter.next();
364:
365: // check whether the converter accepts the deployment
366: if (!converter.accepts(di.url))
367: continue;
368:
369: log.debug("trying to deploy with new converter: "
370: + di.shortName);
371: try {
372: mainDeployer.redeploy(di);
373: } catch (DeploymentException e) {
374: log
375: .error(
376: "DeploymentException while trying to deploy a package with new converter",
377: e);
378: }
379: }
380: }
381: }
382:
383: /**
384: * Removes a conveter from the list of converters. If the
385: * converter does not exist nothing happens.
386: * This method is normally called by a Converter to be removed
387: * from the list if not serving anymore.
388: *
389: * @param converter Conveter to be removed from the list
390: *
391: * @jmx.managed-operation
392: */
393: public void removeConvertor(Convertor converter) {
394: converterList.remove(converter);
395: }
396:
397: // Private --------------------------------------------------------
398: /**
399: * The <code>inflateJar</code> copies the jar entries
400: * from the jar url jarUrl to the directory destDir.
401: *
402: * @param fileURL URL pointing to the file to be inflated
403: * @param destinationDirectory Directory to which the content shall be inflated to
404: *
405: * @exception DeploymentException if an error occurs
406: * @exception IOException if an error occurs
407: */
408: protected void inflateJar(URL fileURL, File destinationDirectory)
409: throws DeploymentException, IOException {
410: File destFile = new File(fileURL.getFile());
411: InputStream input = new FileInputStream(fileURL.getFile());
412: JarUtils.unjar(input, destinationDirectory);
413: // input is closed in unjar();
414: }
415:
416: /**
417: * Deflate a given directory into a JAR file
418: *
419: * @param jarFile The JAR file to be created
420: * @param root Root directory of the files to be included (this directory
421: * will not be included in the path of the JAR content)
422: **/
423: private void deflateJar(File jarFile, File root) throws Exception {
424: OutputStream output = new FileOutputStream(jarFile);
425: JarUtils.jar(output, root.listFiles(), null, null, null);
426: output.close();
427: }
428:
429: /**
430: * Copies the given File to a new destination with the same name
431: *
432: * @param source The source file to be copied
433: * @param destinationDirectory File pointing to the destination directory
434: **/
435: private void copyFile(File source, File destinationDirectory)
436: throws Exception {
437: File target = new File(destinationDirectory, source.getName());
438: // Move may fail if target is used (because it is deployed)
439: // Use Files.copy instead
440: Files.copy(source, target);
441: }
442: }
|