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.deployment.scanner;
023:
024: import java.io.UnsupportedEncodingException;
025: import java.net.MalformedURLException;
026: import java.net.URL;
027: import java.util.ArrayList;
028: import java.util.Collections;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Set;
034: import java.util.StringTokenizer;
035:
036: import org.jboss.deployment.IncompleteDeploymentException;
037: import org.jboss.deployment.NetBootFile;
038: import org.jboss.deployment.NetBootHelper;
039: import org.jboss.util.NullArgumentException;
040:
041: /**
042: * Implement a scanner for HTTP server with un- re- deploy features. To enable these
043: * features, a "lister" is used on the server: it allows to list some arbitrary
044: * directory and return an XML string containing the detail of the directory.
045: *
046: * The Filtering feature is not yet implemented (because it relies on the File class
047: * which cannot be used in our case, or should be tricked)
048: *
049: * The class extends URLDeploymentScanner but it doesn't really extends its code
050: * instead, it will delegate to it everything related to:
051: * - absolute http url that identify a single file, such as http://server/bla.jar
052: * WARNING: such explicit JARs are NOT re- un- deployable!
053: * - file: urls (must be identified as a file: URL in the MBean definition)
054: *
055: * Remote expanded directory are not supported.
056: *
057: * The scanner would be able to work with multiple different Lister (one for
058: * each URL for example): the processing and data structure are already there.
059: * Nevertheless, there is currently no way to indicate this explicitly (in the MBean
060: * definition for example). We would have to build a new naming scheme such as
061: * LISTER_URL#DOWNLOAD_URL#REMOTE_FOLDER_NAME
062: *
063: * @see org.jboss.deployment.scanner.URLDeploymentScanner
064: * @see org.jboss.deployment.NetBootHelper
065: *
066: * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
067: * @version $Revision: 57205 $
068: *
069: * <p><b>Revisions:</b>
070: *
071: * <p><b>6th of november 2002 Sacha Labourey:</b>
072: * <ul>
073: * <li> First implementation </li>
074: * </ul>
075: */
076:
077: /**
078: *
079: * @jmx:mbean extends="org.jboss.deployment.scanner.URLDeploymentScannerMBean"
080: *
081: */
082: public class HttpURLDeploymentScanner extends URLDeploymentScanner
083: implements HttpURLDeploymentScannerMBean {
084:
085: // Constants -----------------------------------------------------
086:
087: // Attributes ----------------------------------------------------
088:
089: protected String defaultHttpDirectoryListerUrl = null;
090: protected String httpDirectoryDownload = null;
091:
092: protected HttpLister defaultHttpLister = null;
093:
094: protected HashMap scannedHttpUrls = new HashMap(); // lister to array of HttpDeploymentFolder map
095:
096: // Static --------------------------------------------------------
097:
098: // Constructors --------------------------------------------------
099:
100: public HttpURLDeploymentScanner() {
101: super ();
102: }
103:
104: // Public --------------------------------------------------------
105:
106: /**
107: * Default URL to be used when listing files on a remote HTTP folder
108: * If none is provided, the one found in jboss.netboot.listing.url is used
109: * If the URL is X, the resulting URL that is used to list the content of folder
110: * "foo" will be "Xdir=foo": the provided URL must support this naming convention
111: *
112: * @jmx:managed-attribute
113: */
114: public String getDefaultHttpDirectoryListerUrl() {
115: if (defaultHttpDirectoryListerUrl == null)
116: defaultHttpDirectoryListerUrl = NetBootHelper
117: .getDefaultListUrl();
118:
119: return this .defaultHttpDirectoryListerUrl;
120: }
121:
122: /**
123: * @jmx:managed-attribute
124: */
125: public void setDefaultHttpDirectoryListerUrl(String url) {
126: this .defaultHttpDirectoryListerUrl = url;
127: }
128:
129: /**
130: * Default URL to be used when downloading files from a remote HTTP folder
131: * If none is provided, the one found in jboss.server.home.url is used
132: *
133: * @jmx:managed-attribute
134: */
135: public String getDefaultHttpDirectoryDownloadUrl() {
136: if (httpDirectoryDownload == null)
137: httpDirectoryDownload = NetBootHelper
138: .getDefaultDownloadUrl();
139:
140: return this .httpDirectoryDownload;
141: }
142:
143: /**
144: * @jmx:managed-attribute
145: */
146: public void setDefaultHttpDirectoryDownloadUrl(String url) {
147: this .httpDirectoryDownload = url;
148: }
149:
150: /**
151: * @jmx:managed-attribute
152: */
153: public void setURLList(final List list) {
154: // shouldn't be called. TODO setURLs is enough for now
155: }
156:
157: /**
158: * @jmx:managed-attribute
159: */
160: public void setURLs(final String listspec)
161: throws MalformedURLException {
162: if (listspec == null)
163: throw new NullArgumentException("listspec");
164:
165: boolean debug = log.isDebugEnabled();
166:
167: List fileList = new LinkedList();
168:
169: StringTokenizer stok = new StringTokenizer(listspec, ",");
170: while (stok.hasMoreTokens()) {
171: String urlspec = stok.nextToken().trim();
172:
173: if (debug) {
174: log.debug("Adding URL from spec: " + urlspec);
175: }
176:
177: // here we split between file based URL (or absolute JARs) that will
178: // be directly managed by our
179: // ancestor, and HTTP based URL that WE will manage
180: //
181: if (urlspec.startsWith("file:")
182: || urlspec.startsWith("http:")) {
183: URL url = makeURL(urlspec);
184: if (debug)
185: log.debug("File URL: " + url);
186: fileList.add(url);
187: } else {
188: URL url = makeURL(urlspec);
189: if (debug)
190: log.debug("HTTP URL: " + url);
191:
192: addHttpDeployment(urlspec, this
193: .getDefaultHttpDirectoryLister());
194: }
195: }
196:
197: // we call our father: he will still manage URLs that we don't
198: // => processing is split!
199: //
200: super .setURLList(fileList);
201: }
202:
203: public synchronized void scan() throws Exception {
204:
205: // call our father first: we split the process
206: //
207: super .scan();
208:
209: boolean trace = log.isTraceEnabled();
210:
211: // Scan for new deployements
212: if (trace)
213: log.trace("Scanning for new http deployments");
214:
215: // we deploy, for each lister, every deploy name
216: synchronized (scannedHttpUrls) {
217: // we may have several Listers...
218: //
219: Iterator listers = this .getAllDeploymentListers()
220: .iterator();
221: while (listers.hasNext()) {
222: HttpLister lister = (HttpLister) listers.next();
223:
224: // ...Each Lister may have a set of associated folder to list...
225: //
226: Iterator deployments = this
227: .getHttpDeploymentsForLister(lister).iterator();
228: while (deployments.hasNext()) {
229: // ... And each folder possibly has a set of deployed files
230: //
231: HttpDeploymentFolder deploymentFolder = (HttpDeploymentFolder) deployments
232: .next();
233: scanRemoteDirectory(deploymentFolder);
234: }
235: }
236: }
237:
238: // Now that all new files have been deployed, we
239: // scan for removed or changed deployments
240: // we do it lister by lister to avoid to have an http read for each deployment
241: // (instead we have one http read for each lister)
242: //
243: if (trace)
244: log
245: .trace("Scanning existing deployments for removal or modification");
246:
247: List removed = new LinkedList();
248: List modified = new LinkedList();
249:
250: Iterator listers = this .getAllDeploymentListers().iterator();
251: while (listers.hasNext()) {
252: HttpLister lister = (HttpLister) listers.next();
253:
254: Iterator deployments = this .getHttpDeploymentsForLister(
255: lister).iterator();
256: while (deployments.hasNext()) {
257: HttpDeploymentFolder deploymentFolder = (HttpDeploymentFolder) deployments
258: .next();
259:
260: // get remote view for this lister/deployment folder couple
261: //
262: NetBootFile[] remoteFiles = NetBootHelper
263: .listFilesFromDirectory(deploymentFolder
264: .getCompleteListingUrl());
265: ;
266:
267: Iterator deployedFiles = deploymentFolder
268: .getDeployedFiles().iterator();
269: while (deployedFiles.hasNext()) {
270: DeployedRemoteURL deployed = (DeployedRemoteURL) deployedFiles
271: .next();
272:
273: NetBootFile alreadyDeployed = findFileWithName(
274: deployed.getFile().getName(), remoteFiles);
275:
276: if (alreadyDeployed == null) {
277: removed.add(deployed);
278: } else if (alreadyDeployed.LastModified() > deployed
279: .getFile().LastModified()) {
280: deployed.updateFile(alreadyDeployed); // important! Required to update size and timestamp
281: modified.add(deployed);
282: }
283:
284: }
285:
286: }
287: }
288:
289: for (Iterator iter = removed.iterator(); iter.hasNext();) {
290: DeployedRemoteURL du = (DeployedRemoteURL) iter.next();
291: undeploy(du);
292: }
293:
294: for (Iterator iter = modified.iterator(); iter.hasNext();) {
295: DeployedRemoteURL du = (DeployedRemoteURL) iter.next();
296: undeploy(du);
297: deploy(du);
298: }
299:
300: // Validate that there are still incomplete deployments
301: if (lastIncompleteDeploymentException != null) {
302: try {
303: Object[] args = {};
304: String[] sig = {};
305: Object o = getServer().invoke(getDeployer(),
306: "checkIncompleteDeployments", args, sig);
307: } catch (Exception e) {
308: log.error(e);
309: }
310: }
311: }
312:
313: protected void scanRemoteDirectory(HttpDeploymentFolder httpFolder)
314: throws Exception {
315: boolean trace = log.isTraceEnabled();
316:
317: if (trace)
318: log.trace("Scanning directory: "
319: + httpFolder.getRelativeFolder());
320:
321: NetBootFile[] content = null;
322: try {
323: content = NetBootHelper.listFilesFromDirectory(httpFolder
324: .getCompleteListingUrl());
325: } catch (Exception e) {
326: log.trace(e);
327: return;
328: }
329:
330: /*
331: * TODO LATER
332: File[] files = filter == null ? file.listFiles() : file.listFiles(filter);
333: if (files == null)
334: {
335: throw new Exception("Null files returned from directory listing");
336: }
337: */
338:
339: // list of urls to deploy
340: List list = new LinkedList();
341: HashMap linkNameToObjects = new HashMap();
342:
343: for (int i = 0; i < content.length; i++) {
344: if (trace)
345: log.trace("Checking deployment file: " + content[i]);
346:
347: // Is it a new file
348: //
349: NetBootFile found = findFileWithName(content[i].getName(),
350: httpFolder.getDeployedFiles());
351: if (found == null) {
352: URL target = httpFolder.getUrlForFile(content[i]);
353: list.add(target);
354: linkNameToObjects.put(target, content[i]);
355: }
356: }
357:
358: //
359: // HACK, sort the elements so dependencies have a better chance of working
360: //
361: if (sorter != null) {
362: updateSorter();
363: Collections.sort(list, sorter);
364: }
365:
366: // deploy each url
367: Iterator iter = list.iterator();
368: while (iter.hasNext()) {
369: URL url = (URL) iter.next();
370: NetBootFile dep = (NetBootFile) linkNameToObjects.get(url);
371:
372: deploy(new DeployedRemoteURL(httpFolder, dep));
373: iter.remove();
374: if (sorter != null && iter.hasNext() && updateSorter()) {
375: Collections.sort(list, sorter);
376: iter = list.iterator();
377: }
378: }
379: }
380:
381: // Z implementation ----------------------------------------------
382:
383: // Y overrides ---------------------------------------------------
384:
385: // Package protected ---------------------------------------------
386:
387: // Protected -----------------------------------------------------
388:
389: protected void undeploy(DeployedRemoteURL deployedUrl) {
390: URL url = null;
391: try {
392: url = deployedUrl.getFolder().getUrlForFile(
393: deployedUrl.getFile());
394:
395: if (log.isTraceEnabled())
396: log.trace("Undeploying: " + url);
397:
398: deployer.undeploy(url);
399:
400: deployedUrl.getFolder().removeDeployedFile(deployedUrl);
401: } catch (Exception e) {
402: log.error("Failed to undeploy: " + url, e);
403: }
404: }
405:
406: protected void deploy(DeployedRemoteURL deployedUrl)
407: throws MalformedURLException {
408:
409: URL url = deployedUrl.getFolder().getUrlForFile(
410: deployedUrl.getFile());
411:
412: if (url == null)
413: return;
414:
415: if (log.isTraceEnabled())
416: log.trace("Deploying: " + url);
417:
418: try {
419: deployer.deploy(url);
420: } catch (IncompleteDeploymentException e) {
421: lastIncompleteDeploymentException = e;
422: } catch (Exception e) {
423: log.error("Failed to deploy: " + url, e);
424: }
425:
426: deployedUrl.getFolder().addDeployedFile(deployedUrl);
427: }
428:
429: // Find for file presence in a set of files
430: //
431:
432: protected NetBootFile findFileWithName(String name,
433: NetBootFile[] files) {
434: for (int i = 0; i < files.length; i++) {
435: if (files[i].getName().equals(name))
436: return files[i];
437: }
438: return null;
439: }
440:
441: protected NetBootFile findFileWithName(String name,
442: List deployedRemoteURL) {
443: NetBootFile[] tmp = new NetBootFile[deployedRemoteURL.size()];
444: Iterator iter = deployedRemoteURL.iterator();
445: int i = 0;
446: while (iter.hasNext()) {
447: DeployedRemoteURL url = (DeployedRemoteURL) iter.next();
448: tmp[i] = url.getFile();
449: i++;
450: }
451: return findFileWithName(name, tmp);
452: }
453:
454: // Manage Lister and associated Directories that must be watched
455: //
456:
457: protected synchronized void addHttpDeployment(String relativeName,
458: HttpLister lister) {
459: ArrayList deps = (ArrayList) scannedHttpUrls.get(lister);
460: if (deps == null) {
461: deps = new ArrayList();
462: scannedHttpUrls.put(lister, deps);
463: }
464: deps.add(new HttpDeploymentFolder(relativeName, lister));
465: }
466:
467: protected List getHttpDeploymentsForLister(HttpLister lister) {
468: ArrayList deps = (ArrayList) scannedHttpUrls.get(lister);
469: if (deps == null) {
470: deps = new ArrayList();
471: }
472: return deps;
473: }
474:
475: protected Set getAllDeploymentListers() {
476: return this .scannedHttpUrls.keySet();
477: }
478:
479: /**
480: * Default Lister object when no other lister is specified in the URLs
481: */
482: protected HttpLister getDefaultHttpDirectoryLister() {
483: if (defaultHttpLister == null)
484: defaultHttpLister = new HttpLister(
485: getDefaultHttpDirectoryDownloadUrl(),
486: getDefaultHttpDirectoryListerUrl());
487:
488: return this .defaultHttpLister;
489: }
490:
491: // Private -------------------------------------------------------
492:
493: // Inner classes -------------------------------------------------
494:
495: protected class HttpLister {
496: public String downloadUrl = null;
497: public String httpListerUrl = null;
498:
499: public HttpLister(String download, String list) {
500: downloadUrl = download;
501: httpListerUrl = list;
502: }
503:
504: public String getDownloadUrl() {
505: return this .downloadUrl;
506: }
507:
508: public String getHttpListerUrl() {
509: return this .httpListerUrl;
510: }
511:
512: public int hashCode() {
513: return this .httpListerUrl.hashCode();
514: }
515:
516: public boolean equals(Object obj) {
517: if (obj instanceof HttpLister)
518: return ((HttpLister) obj).httpListerUrl
519: .equals(this .httpListerUrl);
520: else
521: return false;
522: }
523:
524: }
525:
526: protected class HttpDeploymentFolder {
527: public String folder = null;
528: public HttpLister myLister = null;
529: public ArrayList deployedFiles = new ArrayList();
530:
531: public HttpDeploymentFolder(String folder, HttpLister accessor) {
532: this .folder = folder;
533: this .myLister = accessor;
534: }
535:
536: public String getRelativeFolder() {
537: return this .folder;
538: }
539:
540: public HttpLister getAssociatedLister() {
541: return this .myLister;
542: }
543:
544: public void addDeployedFile(DeployedRemoteURL file) {
545: deployedFiles.add(file);
546: }
547:
548: public void removeDeployedFile(DeployedRemoteURL file) {
549: deployedFiles.remove(file);
550: }
551:
552: public List getDeployedFiles() {
553: return this .deployedFiles;
554: }
555:
556: public String getCompleteListingUrl()
557: throws UnsupportedEncodingException {
558: return NetBootHelper.buildListUrlForFolder(this .myLister
559: .getHttpListerUrl(), this .folder);
560: }
561:
562: public URL getUrlForFile(NetBootFile file)
563: throws MalformedURLException {
564: return new URL(NetBootHelper.buildDownloadUrlForFile(
565: this .myLister.getDownloadUrl(), this .folder, file
566: .getName()));
567: }
568:
569: }
570:
571: protected class DeployedRemoteURL {
572: HttpDeploymentFolder folder = null;
573: NetBootFile file = null;
574:
575: public DeployedRemoteURL(HttpDeploymentFolder folder,
576: NetBootFile file) {
577: this .folder = folder;
578: this .file = file;
579: }
580:
581: public HttpDeploymentFolder getFolder() {
582: return this .folder;
583: }
584:
585: public NetBootFile getFile() {
586: return this .file;
587: }
588:
589: public void updateFile(NetBootFile newer) {
590: this.file = newer;
591: }
592: }
593:
594: }
|