001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.jbi.framework;
018    
019    import java.io.*;
020    import java.net.MalformedURLException;
021    import java.net.URI;
022    import java.net.URISyntaxException;
023    import java.net.URL;
024    import java.util.ArrayList;
025    import java.util.Date;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    import java.util.Timer;
032    import java.util.TimerTask;
033    import java.util.concurrent.ConcurrentHashMap;
034    import java.util.concurrent.atomic.AtomicBoolean;
035    import java.util.zip.ZipFile;
036    
037    import javax.jbi.JBIException;
038    import javax.jbi.management.DeploymentException;
039    import javax.management.JMException;
040    import javax.management.MBeanAttributeInfo;
041    
042    import org.apache.commons.logging.Log;
043    import org.apache.commons.logging.LogFactory;
044    import org.apache.servicemix.jbi.container.EnvironmentContext;
045    import org.apache.servicemix.jbi.container.JBIContainer;
046    import org.apache.servicemix.jbi.deployment.Component;
047    import org.apache.servicemix.jbi.deployment.Descriptor;
048    import org.apache.servicemix.jbi.deployment.DescriptorFactory;
049    import org.apache.servicemix.jbi.deployment.ServiceAssembly;
050    import org.apache.servicemix.jbi.event.DeploymentEvent;
051    import org.apache.servicemix.jbi.event.DeploymentListener;
052    import org.apache.servicemix.jbi.management.AttributeInfoHelper;
053    import org.apache.servicemix.jbi.management.BaseSystemService;
054    import org.apache.servicemix.jbi.util.FileUtil;
055    import org.apache.servicemix.jbi.util.XmlPersistenceSupport;
056    
057    /**
058     * Monitors install and deploy directories to auto install/deploy archives
059     * 
060     * @version $Revision: 695373 $
061     */
062    public class AutoDeploymentService extends BaseSystemService implements AutoDeploymentServiceMBean {
063    
064        private static final Log LOG = LogFactory.getLog(AutoDeploymentService.class);
065            
066        private static String filePrefix = "file:///";
067        private EnvironmentContext environmentContext;
068        private DeploymentService deploymentService;
069        private InstallationService installationService;
070        private boolean monitorInstallationDirectory = true;
071        private boolean monitorDeploymentDirectory = true;
072        private int monitorInterval = 10;
073        private String extensions = ".zip,.jar";
074        private AtomicBoolean started = new AtomicBoolean(false);
075        private Timer statsTimer;
076        private TimerTask timerTask;
077        private Map<File, ArchiveEntry> pendingComponents = new ConcurrentHashMap<File, ArchiveEntry>();
078        private Map<File, ArchiveEntry> pendingSAs = new ConcurrentHashMap<File, ArchiveEntry>();
079        private Map<String, ArchiveEntry> installFileMap;
080        private Map<String, ArchiveEntry> deployFileMap;
081    
082        /**
083         * @return the extensions
084         */
085        public String getExtensions() {
086            return extensions;
087        }
088    
089        /**
090         * @param extensions
091         *            the extensions to set
092         */
093        public void setExtensions(String extensions) {
094            this.extensions = extensions;
095        }
096    
097        /**
098         * @return a description of this
099         */
100        public String getDescription() {
101            return "automatically installs and deploys JBI Archives";
102        }
103    
104        /**
105         * @return Returns the monitorInstallationDirectory.
106         */
107        public boolean isMonitorInstallationDirectory() {
108            return monitorInstallationDirectory;
109        }
110    
111        /**
112         * @param monitorInstallationDirectory
113         *            The monitorInstallationDirectory to set.
114         */
115        public void setMonitorInstallationDirectory(boolean monitorInstallationDirectory) {
116            this.monitorInstallationDirectory = monitorInstallationDirectory;
117        }
118    
119        /**
120         * @return Returns the monitorDeploymentDirectory.
121         */
122        public boolean isMonitorDeploymentDirectory() {
123            return monitorDeploymentDirectory;
124        }
125    
126        /**
127         * @param monitorDeploymentDirectory
128         *            The monitorDeploymentDirectory to set.
129         */
130        public void setMonitorDeploymentDirectory(boolean monitorDeploymentDirectory) {
131            this.monitorDeploymentDirectory = monitorDeploymentDirectory;
132        }
133    
134        /**
135         * @return Returns the monitorInterval (number in secs)
136         */
137        public int getMonitorInterval() {
138            return monitorInterval;
139        }
140    
141        /**
142         * @param monitorInterval
143         *            The monitorInterval to set (in secs)
144         */
145        public void setMonitorInterval(int monitorInterval) {
146            this.monitorInterval = monitorInterval;
147        }
148    
149        public void start() throws javax.jbi.JBIException {
150            super.start();
151            if (started.compareAndSet(false, true)) {
152                scheduleDirectoryTimer();
153            }
154        }
155    
156        /**
157         * Stop the item. This suspends current messaging activities.
158         * 
159         * @exception javax.jbi.JBIException
160         *                if the item fails to stop.
161         */
162        public void stop() throws javax.jbi.JBIException {
163            if (started.compareAndSet(true, false)) {
164                super.stop();
165                if (timerTask != null) {
166                    timerTask.cancel();
167                }
168            }
169        }
170    
171        /**
172         * Initialize the Service
173         * 
174         * @param container
175         * @throws JBIException
176         */
177        public void init(JBIContainer container) throws JBIException {
178            super.init(container);
179            this.environmentContext = container.getEnvironmentContext();
180            this.installationService = container.getInstallationService();
181            this.deploymentService = container.getDeploymentService();
182            // clean-up tmp directory
183            if (environmentContext.getTmpDir() != null) {
184                FileUtil.deleteFile(environmentContext.getTmpDir());
185            }
186            initializeFileMaps();
187        }
188    
189        protected Class<AutoDeploymentServiceMBean> getServiceMBean() {
190            return AutoDeploymentServiceMBean.class;
191        }
192    
193        /**
194         * load an archive from an external location
195         * 
196         * @param location
197         * @param autoStart
198         * @throws DeploymentException
199         */
200        public ArchiveEntry updateExternalArchive(String location, boolean autoStart) throws DeploymentException {
201            ArchiveEntry entry = new ArchiveEntry();
202            entry.location = location;
203            entry.lastModified = new Date();
204            updateArchive(location, entry, autoStart);
205            return entry;
206        }
207    
208        /**
209         * Update an archive
210         * 
211         * @param location
212         * @param autoStart
213         * @throws DeploymentException
214         */
215        public void updateArchive(String location, ArchiveEntry entry, boolean autoStart) throws DeploymentException {
216            // Call listeners
217            try {
218                DeploymentListener[] listeners = (DeploymentListener[]) container.getListeners(DeploymentListener.class);
219                DeploymentEvent event = new DeploymentEvent(new File(location), DeploymentEvent.FILE_CHANGED);
220                for (int i = 0; i < listeners.length; i++) {
221                    if (listeners[i].fileChanged(event)) {
222                        return;
223                    }
224                }
225            } catch (IOException e) {
226                throw failure("deploy", "Error when deploying: " + location, e);
227            }
228            // Standard processing
229            File tmpDir = null;
230            try {
231                tmpDir = AutoDeploymentService.unpackLocation(environmentContext.getTmpDir(), location);
232            } catch (Exception e) {
233                throw failure("deploy", "Unable to unpack archive: " + location, e);
234            }
235            // unpackLocation returns null if no jbi descriptor is found
236            if (tmpDir == null) {
237                throw failure("deploy", "Unable to find jbi descriptor: " + location);
238            }
239            Descriptor root = null;
240            try {
241                root = DescriptorFactory.buildDescriptor(tmpDir);
242            } catch (Exception e) {
243                throw failure("deploy", "Unable to build jbi descriptor: " + location, e);
244            }
245            if (root == null) {
246                throw failure("deploy", "Unable to find jbi descriptor: " + location);
247            }
248            if (root != null) {
249                try {
250                    container.getBroker().suspend();
251                    if (root.getComponent() != null) {
252                        updateComponent(entry, autoStart, tmpDir, root);
253                    } else if (root.getSharedLibrary() != null) {
254                        updateSharedLibrary(entry, tmpDir, root);
255                    } else if (root.getServiceAssembly() != null) {
256                        updateServiceAssembly(entry, autoStart, tmpDir, root);
257                    }
258                } finally {
259                    container.getBroker().resume();
260                }
261            }
262        }
263    
264        protected void updateComponent(ArchiveEntry entry, boolean autoStart, File tmpDir, Descriptor root) throws DeploymentException {
265            Component comp = root.getComponent();
266            String componentName = comp.getIdentification().getName();
267            entry.type = "component";
268            entry.name = componentName;
269            try {
270                if (container.getRegistry().getComponent(componentName) != null) {
271                    installationService.loadInstaller(componentName);
272                    installationService.unloadInstaller(componentName, true);
273                }
274                // See if shared libraries are installed
275                entry.dependencies = getSharedLibraryNames(comp);
276                if (LOG.isDebugEnabled()) {
277                    LOG.debug("Component dependencies: " + entry.dependencies);
278                }
279                String missings = null;
280                boolean canInstall = true;
281                for (String libraryName : entry.dependencies) {
282                    if (container.getRegistry().getSharedLibrary(libraryName) == null) {
283                        canInstall = false;
284                        if (missings != null) {
285                            missings += ", " + libraryName;
286                        } else {
287                            missings = libraryName;
288                        }
289                    }
290                }
291                if (canInstall) {
292                    installationService.install(tmpDir, null, root, autoStart);
293                    checkPendingSAs();
294                } else {
295                    entry.pending = true;
296                    LOG.warn("Shared libraries " + missings + " are not installed yet: the component" + componentName
297                                    + " installation is suspended and will be resumed once the listed shared libraries are installed");
298                    pendingComponents.put(tmpDir, entry);
299                }
300            } catch (Exception e) {
301                String errStr = "Failed to update Component: " + componentName;
302                LOG.error(errStr, e);
303                throw new DeploymentException(errStr, e);
304            }
305        }
306    
307        protected void updateSharedLibrary(ArchiveEntry entry, File tmpDir, Descriptor root) throws DeploymentException {
308            String libraryName = root.getSharedLibrary().getIdentification().getName();
309            entry.type = "library";
310            entry.name = libraryName;
311            try {
312                if (container.getRegistry().getSharedLibrary(libraryName) != null) {
313                    container.getRegistry().unregisterSharedLibrary(libraryName);
314                    environmentContext.removeSharedLibraryDirectory(libraryName);
315                }
316                installationService.doInstallSharedLibrary(tmpDir, root.getSharedLibrary());
317                checkPendingComponents();
318            } catch (Exception e) {
319                String errStr = "Failed to update SharedLibrary: " + libraryName;
320                LOG.error(errStr, e);
321                throw new DeploymentException(errStr, e);
322            }
323        }
324    
325        protected void updateServiceAssembly(ArchiveEntry entry, boolean autoStart, File tmpDir, Descriptor root) throws DeploymentException {
326            ServiceAssembly sa = root.getServiceAssembly();
327            String name = sa.getIdentification().getName();
328            entry.type = "assembly";
329            entry.name = name;
330            try {
331                if (deploymentService.isSaDeployed(name)) {
332                    deploymentService.shutDown(name);
333                    deploymentService.undeploy(name);
334                }
335                // see if components are installed
336                entry.dependencies = getComponentNames(sa);
337                if (LOG.isDebugEnabled()) {
338                    LOG.debug("SA dependencies: " + entry.dependencies);
339                }
340                String missings = null;
341                boolean canDeploy = true;
342                for (String componentName : entry.dependencies) {
343                    if (container.getComponent(componentName) == null) {
344                        canDeploy = false;
345                        if (missings != null) {
346                            missings += ", " + componentName;
347                        } else {
348                            missings = componentName;
349                        }
350                    }
351                }
352                if (canDeploy) {
353                    deploymentService.deployServiceAssembly(tmpDir, sa);
354                    if (autoStart) {
355                        deploymentService.start(name);
356                    }
357                } else {
358                    // TODO: check that the assembly is not already
359                    // pending
360                    entry.pending = true;
361                    LOG.warn("Components " + missings + " are not installed yet: the service assembly " + name
362                                    + " deployment is suspended and will be resumed once the listed components are installed");
363                    pendingSAs.put(tmpDir, entry);
364                }
365            } catch (Exception e) {
366                String errStr = "Failed to update Service Assembly: " + name;
367                LOG.error(errStr, e);
368                throw new DeploymentException(errStr, e);
369            }
370        }
371    
372        protected DeploymentException failure(String task, String info) {
373            return failure(task, info, null, null);
374        }
375    
376        protected DeploymentException failure(String task, String info, Exception e) {
377            return failure(task, info, e, null);
378        }
379    
380        protected DeploymentException failure(String task, String info, Exception e, List componentResults) {
381            ManagementSupport.Message msg = new ManagementSupport.Message();
382            msg.setTask(task);
383            msg.setResult("FAILED");
384            msg.setType("ERROR");
385            msg.setException(e);
386            msg.setMessage(info);
387            return new DeploymentException(ManagementSupport.createFrameworkMessage(msg, componentResults));
388        }
389    
390        protected Set<String> getComponentNames(ServiceAssembly sa) {
391            Set<String> names = new HashSet<String>();
392            if (sa.getServiceUnits() != null && sa.getServiceUnits().length > 0) {
393                for (int i = 0; i < sa.getServiceUnits().length; i++) {
394                    names.add(sa.getServiceUnits()[i].getTarget().getComponentName());
395                }
396            }
397            return names;
398        }
399    
400        protected Set<String> getSharedLibraryNames(Component comp) {
401            Set<String> names = new HashSet<String>();
402            if (comp.getSharedLibraries() != null && comp.getSharedLibraries().length > 0) {
403                for (int i = 0; i < comp.getSharedLibraries().length; i++) {
404                    names.add(comp.getSharedLibraries()[i].getName());
405                }
406            }
407            return names;
408        }
409    
410        /**
411         * Remove an archive location
412         * 
413         * @param location
414         * @throws DeploymentException
415         */
416        public void removeArchive(ArchiveEntry entry) throws DeploymentException {
417            // Call listeners
418            try {
419                DeploymentListener[] listeners = (DeploymentListener[]) container.getListeners(DeploymentListener.class);
420                DeploymentEvent event = new DeploymentEvent(new File(entry.location), DeploymentEvent.FILE_REMOVED);
421                for (int i = 0; i < listeners.length; i++) {
422                    if (listeners[i].fileRemoved(event)) {
423                        return;
424                    }
425                }
426            } catch (IOException e) {
427                throw failure("deploy", "Error when deploying: " + entry.location, e);
428            }
429            // Standard processing
430            LOG.info("Attempting to remove archive at: " + entry.location);
431            try {
432                container.getBroker().suspend();
433                if ("component".equals(entry.type)) {
434                    LOG.info("Uninstalling component: " + entry.name);
435                    // Ensure installer is loaded
436                    installationService.loadInstaller(entry.name);
437                    // Uninstall and delete component
438                    installationService.unloadInstaller(entry.name, true);
439                }
440                if ("library".equals(entry.type)) {
441                    LOG.info("Removing shared library: " + entry.name);
442                    installationService.uninstallSharedLibrary(entry.name);
443                }
444                if ("assembly".equals(entry.type)) {
445                    LOG.info("Undeploying service assembly " + entry.name);
446                    try {
447                        if (deploymentService.isSaDeployed(entry.name)) {
448                            deploymentService.shutDown(entry.name);
449                            deploymentService.undeploy(entry.name);
450                        }
451                    } catch (Exception e) {
452                        String errStr = "Failed to update service assembly: " + entry.name;
453                        LOG.error(errStr, e);
454                        throw new DeploymentException(errStr, e);
455                    }
456                }
457            } finally {
458                container.getBroker().resume();
459            }
460        }
461    
462        /**
463         * Called when a component has been installed to see if pending service
464         * assemblies have all component installed.
465         */
466        private void checkPendingSAs() {
467            Set<File> deployedSas = new HashSet<File>();
468            for (Map.Entry<File, ArchiveEntry> me : pendingSAs.entrySet()) {
469                ArchiveEntry entry = me.getValue();
470                boolean canDeploy = true;
471                for (String componentName : entry.dependencies) {
472                    if (container.getComponent(componentName) == null) {
473                        canDeploy = false;
474                        break;
475                    }
476                }
477                if (canDeploy) {
478                    File tmp = (File) me.getKey();
479                    deployedSas.add(tmp);
480                    try {
481                        Descriptor root = DescriptorFactory.buildDescriptor(tmp);
482                        deploymentService.deployServiceAssembly(tmp, root.getServiceAssembly());
483                        deploymentService.start(root.getServiceAssembly().getIdentification().getName());
484                    } catch (Exception e) {
485                        String errStr = "Failed to update Service Assembly: " + tmp.getName();
486                        LOG.error(errStr, e);
487                    }
488                }
489            }
490            if (!deployedSas.isEmpty()) {
491                // Remove SA from pending SAs
492                for (File f : deployedSas) {
493                    ArchiveEntry entry = pendingSAs.remove(f);
494                    entry.pending = false;
495                }
496                // Store new state
497                persistState(environmentContext.getDeploymentDir(), deployFileMap);
498                persistState(environmentContext.getInstallationDir(), installFileMap);
499            }
500        }
501    
502        private void checkPendingComponents() {
503            Set<File> installedComponents = new HashSet<File>();
504            for (Map.Entry<File, ArchiveEntry> me : pendingComponents.entrySet()) {
505                ArchiveEntry entry = me.getValue();
506                boolean canInstall = true;
507                for (String libraryName : entry.dependencies) {
508                    if (container.getRegistry().getSharedLibrary(libraryName) == null) {
509                        canInstall = false;
510                        break;
511                    }
512                }
513                if (canInstall) {
514                    File tmp = me.getKey();
515                    installedComponents.add(tmp);
516                    try {
517                        Descriptor root = DescriptorFactory.buildDescriptor(tmp);
518                        installationService.install(tmp, null, root, true);
519                    } catch (Exception e) {
520                        String errStr = "Failed to update Component: " + tmp.getName();
521                        LOG.error(errStr, e);
522                    }
523                }
524            }
525            if (!installedComponents.isEmpty()) {
526                // Remove SA from pending SAs
527                for (File f : installedComponents) {
528                    ArchiveEntry entry = pendingComponents.remove(f);
529                    entry.pending = false;
530                }
531                // Store new state
532                persistState(environmentContext.getDeploymentDir(), deployFileMap);
533                persistState(environmentContext.getInstallationDir(), installFileMap);
534                // Check for pending SAs
535                checkPendingSAs();
536            }
537        }
538    
539        /**
540         * Get an array of MBeanAttributeInfo
541         * 
542         * @return array of AttributeInfos
543         * @throws JMException
544         */
545        public MBeanAttributeInfo[] getAttributeInfos() throws JMException {
546            AttributeInfoHelper helper = new AttributeInfoHelper();
547            helper.addAttribute(getObjectToManage(), "monitorInstallationDirectory", "Periodically monitor the Installation directory");
548            helper.addAttribute(getObjectToManage(), "monitorInterval", "Interval (secs) before monitoring");
549            return AttributeInfoHelper.join(super.getAttributeInfos(), helper.getAttributeInfos());
550        }
551    
552        /**
553         * Unpack a location into a temp file directory. If the location does not
554         * contain a jbi descritor, no unpacking occurs.
555         * 
556         * @param location
557         * @return tmp directory (if location contains a jbi descriptor)
558         * @throws DeploymentException
559         */
560        protected static File unpackLocation(File tmpRoot, String location) throws DeploymentException {
561            File tmpDir = null;
562            File file = null;
563            try {   
564                if (location.startsWith(filePrefix)) {
565                    String os = System.getProperty("os.name");
566                    if (os.startsWith("Windows")) {
567                        
568                        location = location.replace('\\', '/');
569                        location = location.replaceAll(" ", "%20");
570                    }
571                    URI uri = new URI(location);
572                    file = new File(uri);
573                } else {
574                    file = new File(location);
575                }
576                if (file.isDirectory()) {
577                    if (LOG.isDebugEnabled()) {
578                        LOG.debug("Deploying an exploded jar/zip, we will create a temporary jar for it.");
579                    }
580                    // If we have a directory then we should move it over
581                    File newFile = new File(tmpRoot.getAbsolutePath() + "/exploded.jar");
582                    newFile.delete();
583                    FileUtil.zipDir(file.getAbsolutePath(), newFile.getAbsolutePath());
584                    file = newFile;
585                    if (LOG.isDebugEnabled()) {
586                        LOG.debug("Deployment will now work from " + file.getAbsolutePath());
587                    }
588                }
589                if (!file.exists()) {
590                    // assume it's a URL
591                    try {
592                        URL url = new URL(location);
593                        String fileName = url.getFile();
594                        if (fileName == null) {
595                            throw new DeploymentException("Location: " + location + " is not an archive");
596                        }
597                        file = FileUtil.unpackArchive(url, tmpRoot);
598                    } catch (MalformedURLException e) {
599                        throw new DeploymentException(e);
600                    }
601                }
602                if (FileUtil.archiveContainsEntry(file, DescriptorFactory.DESCRIPTOR_FILE)) {
603                    tmpDir = FileUtil.createUniqueDirectory(tmpRoot, file.getName());
604                    FileUtil.unpackArchive(file, tmpDir);
605                    if (LOG.isDebugEnabled()) {
606                        LOG.debug("Unpacked archive " + location + " to " + tmpDir);
607                    }
608                }
609            } catch (IOException e) {
610                throw new DeploymentException(e);
611            } catch (URISyntaxException ex) {
612                throw new DeploymentException(ex);
613            }
614            return tmpDir;
615        }
616    
617        private void scheduleDirectoryTimer() {
618            if (!container.isEmbedded() && (isMonitorInstallationDirectory() || isMonitorDeploymentDirectory())) {
619                if (statsTimer == null) {
620                    statsTimer = new Timer(true);
621                }
622                if (timerTask != null) {
623                    timerTask.cancel();
624                }
625                timerTask = new TimerTask() {
626                    public void run() {
627                        if (!isStarted()) {
628                            return;
629                        }
630                        if (isMonitorInstallationDirectory()) {
631                            monitorDirectory(environmentContext.getInstallationDir(), installFileMap);
632                        }
633                        if (isMonitorDeploymentDirectory()) {
634                            monitorDirectory(environmentContext.getDeploymentDir(), deployFileMap);
635                        }
636                    }
637                };
638                long interval = monitorInterval * 1000;
639                statsTimer.scheduleAtFixedRate(timerTask, 0, interval);
640            }
641        }
642    
643        private void monitorDirectory(final File root, final Map<String, ArchiveEntry> fileMap) {
644            /*
645             * if (log.isTraceEnabled()) { if (root != null) log.trace("Monitoring
646             * directory " + root.getAbsolutePath() + " for new or modified
647             * archives"); else log.trace("No directory to monitor for new or
648             * modified archives for " + ((fileMap==installFileMap) ? "Installation" :
649             * "Deployment") + "."); }
650             */
651            List<String> tmpList = new ArrayList<String>();
652            if (root != null && root.exists() && root.isDirectory()) {
653                File[] files = root.listFiles();
654                if (files != null) {
655                    for (int i = 0; i < files.length; i++) {
656                        final File file = files[i];
657                        tmpList.add(file.getName());
658                        if (isAllowedExtension(file.getName()) && isAvailable(file)) {
659                            ArchiveEntry lastEntry = fileMap.get(file.getName());
660                            if (lastEntry == null || file.lastModified() > lastEntry.lastModified.getTime()) {
661                                try {
662                                    final ArchiveEntry entry = new ArchiveEntry();
663                                    entry.location = file.getName();
664                                    entry.lastModified = new Date(file.lastModified());
665                                    fileMap.put(file.getName(), entry);
666                                    LOG.info("Directory: " + root.getName() + ": Archive changed: processing " + file.getName() + " ...");
667                                    updateArchive(file.getAbsolutePath(), entry, true);
668                                    LOG.info("Directory: " + root.getName() + ": Finished installation of archive:  " + file.getName());
669                                } catch (Exception e) {
670                                    LOG.warn("Directory: " + root.getName() + ": Automatic install of " + file + " failed", e);
671                                } finally {
672                                    persistState(root, fileMap);
673                                }
674                            }
675                        }
676                    }
677                }
678                // now remove any locations no longer here
679                Map<String, ArchiveEntry> map = new HashMap<String, ArchiveEntry>(fileMap);
680                for (String location : map.keySet()) {
681                    if (!tmpList.contains(location)) {
682                        ArchiveEntry entry = fileMap.remove(location);
683                        try {
684                            LOG.info("Location " + location + " no longer exists - removing ...");
685                            removeArchive(entry);
686                        } catch (DeploymentException e) {
687                            LOG.error("Failed to removeArchive: " + location, e);
688                        }
689                    }
690                }
691                if (!map.equals(fileMap)) {
692                    persistState(root, fileMap);
693                }
694            }
695        }
696    
697        private boolean isAvailable(File file) {
698            // First check to see if the file is still growing
699            long targetLength = file.length();
700            try {
701                Thread.sleep(100);
702            } catch (InterruptedException e) {
703                //Do nothing
704            }
705            long target2Length = file.length();
706    
707            if (targetLength != target2Length) {
708                LOG.warn("File is still being copied, deployment deferred to next cycle: " + file.getName());
709                return false;
710            }
711    
712            // If file size is consistent, do a foolproof check of the zip file
713            try {
714                ZipFile zip = new ZipFile(file);
715                zip.size();
716                zip.close();
717            } catch (IOException e) {
718                LOG.warn("Unable to open deployment file, deployment deferred to next cycle: " + file.getName());
719                return false;
720            }
721    
722            return true;
723        }
724    
725        private boolean isAllowedExtension(String file) {
726            String[] ext = this.extensions.split(",");
727            for (int i = 0; i < ext.length; i++) {
728                if (file.endsWith(ext[i])) {
729                    return true;
730                }
731            }
732            return false;
733        }
734    
735        private void persistState(File root, Map<String, ArchiveEntry> map) {
736            try {
737                File file = new File(environmentContext.getJbiRootDir(), root.getName() + ".xml");
738                XmlPersistenceSupport.write(file, map);
739            } catch (IOException e) {
740                LOG.error("Failed to persist file state to: " + root, e);
741            }
742        }
743    
744        @SuppressWarnings("unchecked")
745        private Map<String, ArchiveEntry> readState(File root) {
746            Map<String, ArchiveEntry> result = new HashMap<String, ArchiveEntry>();
747            try {
748                File file = new File(environmentContext.getJbiRootDir(), root.getName() + ".xml");
749                if (file.exists()) {
750                    result = (Map<String, ArchiveEntry>) XmlPersistenceSupport.read(file);
751                } else {
752                    LOG.debug("State file doesn't exist: " + file.getPath());
753                }
754            } catch (Exception e) {
755                LOG.error("Failed to read file state from: " + root, e);
756            }
757            return result;
758        }
759    
760        private void initializeFileMaps() {
761            if (isMonitorInstallationDirectory() && !container.isEmbedded()) {
762                try {
763                    installFileMap = readState(environmentContext.getInstallationDir());
764                    removePendingEntries(installFileMap);
765                } catch (Exception e) {
766                    LOG.error("Failed to read installed state", e);
767                }
768            }
769            if (isMonitorDeploymentDirectory() && !container.isEmbedded()) {
770                try {
771                    deployFileMap = readState(environmentContext.getDeploymentDir());
772                    removePendingEntries(deployFileMap);
773                } catch (Exception e) {
774                    LOG.error("Failed to read deployed state", e);
775                }
776            }
777        }
778    
779        private void removePendingEntries(Map<String, ArchiveEntry> map) {
780            Set<String> pendings = new HashSet<String>();
781            for (Map.Entry<String, ArchiveEntry> e : map.entrySet()) {
782                if (e.getValue().pending) {
783                    pendings.add(e.getKey());
784                }
785            }
786            for (String s : pendings) {
787                map.remove(s);
788            }
789        }
790    
791        public static class ArchiveEntry {
792            
793            private String location;
794            private Date lastModified;
795            private String type;
796            private String name;
797            private boolean pending;
798            private transient Set<String> dependencies;
799    
800            public String getLocation() {
801                return location;
802            }
803    
804            public void setLocation(String location) {
805                this.location = location;
806            }
807    
808            public Date getLastModified() {
809                return lastModified;
810            }
811    
812            public void setLastModified(Date lastModified) {
813                this.lastModified = lastModified;
814            }
815    
816            public String getType() {
817                return type;
818            }
819    
820            public void setType(String type) {
821                this.type = type;
822            }
823    
824            public String getName() {
825                return name;
826            }
827    
828            public void setName(String name) {
829                this.name = name;
830            }
831    
832            public boolean isPending() {
833                return pending;
834            }
835    
836            public void setPending(boolean pending) {
837                this.pending = pending;
838            }
839    
840            public Set<String> getDependencies() {
841                return dependencies;
842            }
843    
844            public void setDependencies(Set<String> dependencies) {
845                this.dependencies = dependencies;
846            }
847        }
848    }