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.File;
020    import java.io.IOException;
021    import java.util.Iterator;
022    import java.util.Map;
023    import java.util.Properties;
024    import java.util.concurrent.ConcurrentHashMap;
025    
026    import javax.jbi.JBIException;
027    import javax.jbi.management.DeploymentException;
028    import javax.jbi.management.InstallationServiceMBean;
029    import javax.jbi.management.InstallerMBean;
030    import javax.management.Attribute;
031    import javax.management.JMException;
032    import javax.management.MBeanOperationInfo;
033    import javax.management.MBeanServer;
034    import javax.management.ObjectName;
035    
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.apache.servicemix.jbi.container.ComponentEnvironment;
039    import org.apache.servicemix.jbi.container.EnvironmentContext;
040    import org.apache.servicemix.jbi.container.JBIContainer;
041    import org.apache.servicemix.jbi.deployment.Component;
042    import org.apache.servicemix.jbi.deployment.Descriptor;
043    import org.apache.servicemix.jbi.deployment.DescriptorFactory;
044    import org.apache.servicemix.jbi.deployment.SharedLibrary;
045    import org.apache.servicemix.jbi.management.BaseSystemService;
046    import org.apache.servicemix.jbi.management.ManagementContext;
047    import org.apache.servicemix.jbi.management.OperationInfoHelper;
048    import org.apache.servicemix.jbi.management.ParameterHelper;
049    import org.apache.servicemix.jbi.util.FileUtil;
050    import org.apache.servicemix.jbi.util.FileVersionUtil;
051    
052    /**
053     * Installation Service - installs/uninstalls archives
054     * 
055     * @version $Revision: 564900 $
056     */
057    public class InstallationService extends BaseSystemService implements InstallationServiceMBean {
058    
059        private static final Log LOG = LogFactory.getLog(InstallationService.class);
060    
061        private EnvironmentContext environmentContext;
062    
063        private ManagementContext managementContext;
064    
065        private Map<String, InstallerMBeanImpl> installers = new ConcurrentHashMap<String, InstallerMBeanImpl>();
066    
067        private Map<String, InstallerMBeanImpl> nonLoadedInstallers = new ConcurrentHashMap<String, InstallerMBeanImpl>();
068    
069        /**
070         * Get Description
071         * 
072         * @return description of this item
073         */
074        public String getDescription() {
075            return "installs/uninstalls Components";
076        }
077    
078        /**
079         * Load the installer for a new component from a component installation
080         * package.
081         * 
082         * @param installJarURL -
083         *            URL locating a jar file containing a JBI Installable
084         *            Component.
085         * @return - the JMX ObjectName of the InstallerMBean loaded from
086         *         installJarURL.
087         */
088        public synchronized ObjectName loadNewInstaller(String installJarURL) {
089            try {
090                ObjectName result = null;
091                if (LOG.isDebugEnabled()) {
092                    LOG.debug("Loading new installer from " + installJarURL);
093                }
094                File tmpDir = AutoDeploymentService.unpackLocation(environmentContext.getTmpDir(), installJarURL);
095                if (tmpDir != null) {
096                    Descriptor root = DescriptorFactory.buildDescriptor(tmpDir);
097                    if (root != null && root.getComponent() != null) {
098                        String componentName = root.getComponent().getIdentification().getName();
099                        if (!installers.containsKey(componentName)) {
100                            InstallerMBeanImpl installer = doInstallArchive(tmpDir, root);
101                            if (installer != null) {
102                                result = installer.getObjectName();
103                                installers.put(componentName, installer);
104                            }
105                        } else {
106                            throw new RuntimeException("An installer already exists for " + componentName);
107                        }
108                    } else {
109                        throw new RuntimeException("Could not find Component from: " + installJarURL);
110                    }
111                } else {
112                    throw new RuntimeException("location: " + installJarURL + " isn't valid");
113                }
114                return result;
115            } catch (Throwable t) {
116                LOG.error("Deployment failed", t);
117                if (t instanceof Error) {
118                    throw (Error) t;
119                }
120                if (t instanceof RuntimeException) {
121                    throw (RuntimeException) t;
122                } else {
123                    throw new RuntimeException("Deployment failed: " + t.getMessage());
124                }
125            }
126        }
127    
128        /**
129         * Load the InstallerMBean for a previously installed component.
130         * 
131         * @param aComponentName -
132         *            the component name identifying the installer to load.
133         * @return - the JMX ObjectName of the InstallerMBean loaded from an
134         *         existing installation context.
135         */
136        public ObjectName loadInstaller(String aComponentName) {
137            InstallerMBeanImpl installer = installers.get(aComponentName);
138            if (installer == null) {
139                installer = nonLoadedInstallers.get(aComponentName);
140                if (installer != null) {
141                    try {
142                        // create an MBean for the installer
143                        ObjectName objectName = managementContext.createCustomComponentMBeanName("Installer", aComponentName);
144                        installer.setObjectName(objectName);
145                        managementContext.registerMBean(objectName, installer, InstallerMBean.class,
146                                        "standard installation controls for a Component");
147                    } catch (Exception e) {
148                        throw new RuntimeException("Could not load installer", e);
149                    }
150                    return installer.getObjectName();
151                }
152            }
153            return null;
154        }
155    
156        private InstallerMBeanImpl createInstaller(String componentName) throws IOException, DeploymentException {
157            File installationDir = environmentContext.getComponentInstallationDir(componentName);
158            Descriptor root = DescriptorFactory.buildDescriptor(installationDir);
159            Component descriptor = root.getComponent();
160    
161            InstallationContextImpl installationContext = new InstallationContextImpl(descriptor);
162            installationContext.setInstall(false);
163            installationContext.setInstallRoot(installationDir);
164            // now build the ComponentContext
165            File componentRoot = environmentContext.getComponentRootDir(componentName);
166            ComponentContextImpl context = buildComponentContext(componentRoot, installationDir, componentName);
167            installationContext.setContext(context);
168            return new InstallerMBeanImpl(container, installationContext);
169        }
170    
171        /**
172         * Unload a JBI Installable Component installer.
173         * 
174         * @param componentName -
175         *            the component name identifying the installer to unload.
176         * @param isToBeDeleted -
177         *            true if the component is to be deleted as well.
178         * @return - true if the operation was successful, otherwise false.
179         */
180        public boolean unloadInstaller(String componentName, boolean isToBeDeleted) {
181            boolean result = false;
182            try {
183                container.getBroker().suspend();
184                InstallerMBeanImpl installer = installers.remove(componentName);
185                result = installer != null;
186                if (result) {
187                    container.getManagementContext().unregisterMBean(installer);
188                    if (isToBeDeleted) {
189                        installer.uninstall();
190                    } else {
191                        nonLoadedInstallers.put(componentName, installer);
192                    }
193                }
194            } catch (JBIException e) {
195                String errStr = "Problem shutting down Component: " + componentName;
196                LOG.error(errStr, e);
197            } finally {
198                container.getBroker().resume();
199            }
200            return result;
201        }
202    
203        /**
204         * Install a shared library jar.
205         * 
206         * @param aSharedLibURI -
207         *            URI locating a jar file containing a shared library.
208         * @return - the name of the shared library loaded from aSharedLibURI.
209         */
210        public String installSharedLibrary(String aSharedLibURI) {
211            String result = "";
212            try {
213                File tmpDir = AutoDeploymentService.unpackLocation(environmentContext.getTmpDir(), aSharedLibURI);
214                if (tmpDir != null) {
215                    Descriptor root = DescriptorFactory.buildDescriptor(tmpDir);
216                    if (root == null) {
217                        throw new DeploymentException("Could not find JBI descriptor");
218                    }
219                    SharedLibrary sl = root.getSharedLibrary();
220                    if (sl != null) {
221                        result = doInstallSharedLibrary(tmpDir, sl);
222                    } else {
223                        throw new DeploymentException("JBI descriptor is not a SharedLibrary descriptor");
224                    }
225                } else {
226                    throw new DeploymentException("Could not find JBI descriptor");
227                }
228            } catch (DeploymentException e) {
229                LOG.error("Deployment failed", e);
230            }
231            return result;
232        }
233    
234        /**
235         * Uninstall a shared library.
236         * 
237         * @param aSharedLibName -
238         *            the name of the shared library to uninstall.
239         * @return - true iff the uninstall was successful.
240         */
241        public boolean uninstallSharedLibrary(String aSharedLibName) {
242            // TODO: should check existence of shared library
243            // and that it is not currently in use
244            container.getRegistry().unregisterSharedLibrary(aSharedLibName);
245            environmentContext.removeSharedLibraryDirectory(aSharedLibName);
246            return true;
247        }
248    
249        /**
250         * Initialize the Service
251         * 
252         * @param container
253         * @throws JBIException
254         * @throws DeploymentException
255         */
256        public void init(JBIContainer container) throws JBIException {
257            super.init(container);
258            this.environmentContext = container.getEnvironmentContext();
259            this.managementContext = container.getManagementContext();
260            buildState();
261        }
262    
263        protected Class getServiceMBean() {
264            return InstallationServiceMBean.class;
265        }
266    
267        /**
268         * Install an archive
269         * 
270         * @param location
271         * @param props
272         * @param autoStart
273         * @throws DeploymentException
274         */
275        public void install(String location, Properties props, boolean autoStart) throws DeploymentException {
276            File tmpDir = AutoDeploymentService.unpackLocation(environmentContext.getTmpDir(), location);
277            if (tmpDir != null) {
278                Descriptor root = DescriptorFactory.buildDescriptor(tmpDir);
279                if (root != null) {
280                    if (root.getComponent() == null) {
281                        throw new DeploymentException("JBI descriptor is not a component descriptor");
282                    }
283                    install(tmpDir, props, root, autoStart);
284                } else {
285                    throw new DeploymentException("Could not find JBI descriptor");
286                }
287            } else {
288                throw new DeploymentException("Could not find JBI descriptor");
289            }
290        }
291    
292        /**
293         * Install an archive
294         * 
295         * @param tmpDir
296         * @param root
297         * @param autoStart
298         * @throws DeploymentException
299         */
300        protected void install(File tmpDir, Properties props, Descriptor root, boolean autoStart) throws DeploymentException {
301            if (root.getComponent() != null) {
302                String componentName = root.getComponent().getIdentification().getName();
303                if (installers.containsKey(componentName)) {
304                    throw new DeploymentException("Component " + componentName + " is already installed");
305                }
306                InstallerMBeanImpl installer = doInstallArchive(tmpDir, root);
307                if (installer != null) {
308                    try {
309                        if (props != null && props.size() > 0) {
310                            ObjectName on = installer.getInstallerConfigurationMBean();
311                            if (on == null) {
312                                LOG.warn("Could not find installation configuration MBean. Installation properties will be ignored.");
313                            } else {
314                                MBeanServer mbs = managementContext.getMBeanServer();
315                                for (Iterator it = props.keySet().iterator(); it.hasNext();) {
316                                    String key = (String) it.next();
317                                    String val = props.getProperty(key);
318                                    try {
319                                        mbs.setAttribute(on, new Attribute(key, val));
320                                    } catch (JMException e) {
321                                        throw new DeploymentException("Could not set installation property: (" + key + " = " + val, e);
322                                    }
323                                }
324                            }
325                        }
326                        installer.install();
327                    } catch (JBIException e) {
328                        throw new DeploymentException(e);
329                    }
330                    if (autoStart) {
331                        try {
332                            ComponentMBeanImpl lcc = container.getComponent(componentName);
333                            if (lcc != null) {
334                                lcc.start();
335                            } else {
336                                LOG.warn("No ComponentConnector found for Component " + componentName);
337                            }
338                        } catch (JBIException e) {
339                            String errStr = "Failed to start Component: " + componentName;
340                            LOG.error(errStr, e);
341                            throw new DeploymentException(e);
342                        }
343                    }
344                    installers.put(componentName, installer);
345                }
346            }
347        }
348    
349        /**
350         * Get an array of MBeanOperationInfo
351         * 
352         * @return array of OperationInfos
353         * @throws JMException
354         */
355        public MBeanOperationInfo[] getOperationInfos() throws JMException {
356            OperationInfoHelper helper = new OperationInfoHelper();
357            ParameterHelper ph = helper.addOperation(getObjectToManage(), "loadNewInstaller", 1, "load a new Installer ");
358            ph.setDescription(0, "installJarURL", "URL locating the install Jar");
359            ph = helper.addOperation(getObjectToManage(), "loadInstaller", 1, "load installer for a previously installed component");
360            ph.setDescription(0, "componentName", "Name of the Component");
361            ph = helper.addOperation(getObjectToManage(), "unloadInstaller", 2, "unload an installer");
362            ph.setDescription(0, "componentName", "Name of the Component");
363            ph.setDescription(1, "isToBeDeleted", "true if component is to be deleted");
364            ph = helper.addOperation(getObjectToManage(), "installSharedLibrary", 1, "Install a shared library jar");
365            ph.setDescription(0, "sharedLibURI", "URI for the jar to be installed");
366            ph = helper.addOperation(getObjectToManage(), "uninstallSharedLibrary", 1, "Uninstall a shared library jar");
367            ph.setDescription(0, "sharedLibName", "name of the shared library");
368            ph = helper.addOperation(getObjectToManage(), "install", 1, "install and deplot an archive");
369            ph.setDescription(0, "location", "location of archive");
370            ph = helper.addOperation(getObjectToManage(), "install", 2, "install and deplot an archive");
371            ph.setDescription(0, "location", "location of archive");
372            ph.setDescription(1, "autostart", "automatically start the Component");
373            return OperationInfoHelper.join(super.getOperationInfos(), helper.getOperationInfos());
374        }
375    
376        protected InstallerMBeanImpl doInstallArchive(File tmpDirectory, Descriptor descriptor) throws DeploymentException {
377            InstallerMBeanImpl installer = null;
378            Component component = descriptor.getComponent();
379            if (component != null) {
380                installer = doInstallComponent(tmpDirectory, component);
381            }
382            return installer;
383        }
384    
385        protected String doInstallSharedLibrary(File tmpDirectory, SharedLibrary descriptor) throws DeploymentException {
386            String result = null;
387            if (descriptor != null) {
388                File installationDir = null;
389                try {
390                    result = descriptor.getIdentification().getName();
391                    File rootDir = environmentContext.createSharedLibraryDirectory(result);
392                    installationDir = FileVersionUtil.getNewVersionDirectory(rootDir);
393                    if (!tmpDirectory.renameTo(installationDir)) {
394                        throw new DeploymentException("Unable to rename " + tmpDirectory + " to " + installationDir);
395                    }
396                    if (LOG.isDebugEnabled()) {
397                        LOG.debug("Moved " + tmpDirectory + " to " + installationDir);
398                    }
399                    container.getRegistry().registerSharedLibrary(descriptor, installationDir);
400                } catch (Exception e) {
401                    LOG.error("Deployment of Shared Library failed", e);
402                    // remove any files created for installation
403                    FileUtil.deleteFile(installationDir);
404                    throw new DeploymentException(e);
405                } finally {
406                    FileUtil.deleteFile(tmpDirectory);
407                }
408            }
409            return result;
410        }
411    
412        protected InstallerMBeanImpl doInstallComponent(File tmpDirectory, Component descriptor) throws DeploymentException {
413            // move archive to Component directory
414            InstallerMBeanImpl result = null;
415            String name = descriptor.getIdentification().getName();
416            try {
417                File oldInstallationDir = environmentContext.getComponentInstallationDir(name);
418                // try and delete the old version ? - maybe should leave around ??
419                if (!FileUtil.deleteFile(oldInstallationDir)) {
420                    LOG.warn("Failed to delete old installation directory: " + oldInstallationDir.getPath());
421                }
422                File componentRoot = environmentContext.createComponentRootDir(name);
423                // this will get the new one
424                File installationDir = environmentContext.getNewComponentInstallationDir(name);
425                tmpDirectory.renameTo(installationDir);
426                if (LOG.isDebugEnabled()) {
427                    LOG.debug("Moved " + tmpDirectory + " to " + installationDir);
428                }
429                result = initializeInstaller(installationDir, componentRoot, descriptor);
430                return result;
431            } catch (IOException e) {
432                throw new DeploymentException(e);
433            }
434        }
435    
436        private InstallerMBeanImpl initializeInstaller(File installationDir, File componentRoot, 
437                                                       Component descriptor) throws DeploymentException {
438            InstallerMBeanImpl result = null;
439            try {
440                String name = descriptor.getIdentification().getName();
441                InstallationContextImpl installationContext = new InstallationContextImpl(descriptor);
442                installationContext.setInstall(true);
443                installationContext.setInstallRoot(installationDir);
444                // now build the ComponentContext
445                ComponentContextImpl context = buildComponentContext(componentRoot, installationDir, name);
446                installationContext.setContext(context);
447                result = new InstallerMBeanImpl(container, installationContext);
448                // create an MBean for the installer
449                ObjectName objectName = managementContext.createCustomComponentMBeanName("Installer", name);
450                result.setObjectName(objectName);
451                managementContext.registerMBean(objectName, result, InstallerMBean.class, "standard installation controls for a Component");
452            } catch (Throwable e) {
453                LOG.error("Deployment of Component failed", e);
454                // remove any files created for installation
455                environmentContext.removeComponentRootDirectory(descriptor.getIdentification().getName());
456                throw new DeploymentException(e);
457            }
458            return result;
459        }
460    
461        protected void buildState() {
462            buildSharedLibs();
463            buildComponents();
464        }
465    
466        /**
467         * returns true if a shared library is already installed
468         * 
469         * @param name
470         * @return true/false
471         */
472        protected boolean containsSharedLibrary(String name) {
473            return container.getRegistry().getSharedLibrary(name) != null;
474        }
475    
476        protected void buildSharedLibs() {
477            // walk through shared libaries and add then to the ClassLoaderService
478            File top = environmentContext.getSharedLibDir();
479            if (top != null && top.exists() && top.isDirectory()) {
480                // directory structure is sharedlibraries/<lib name>/version_x/stuff
481                // ...
482                File[] files = top.listFiles();
483                if (files != null) {
484                    for (int i = 0; i < files.length; i++) {
485                        if (!files[i].isDirectory()) {
486                            continue;
487                        }
488                        File dir = FileVersionUtil.getLatestVersionDirectory(files[i]);
489                        if (dir == null) {
490                            continue;
491                        }
492                        Descriptor root = DescriptorFactory.buildDescriptor(dir);
493                        if (root == null) {
494                            continue;
495                        }
496                        SharedLibrary sl = root.getSharedLibrary();
497                        if (sl == null) {
498                            continue;
499                        }
500                        try {
501                            container.getRegistry().registerSharedLibrary(sl, dir);
502                        } catch (Exception e) {
503                            LOG.error("Failed to initialize sharted library", e);
504                        }
505                    }
506                }
507            }
508        }
509    
510        protected void buildComponents() {
511            // walk through components and add then to the ClassLoaderService
512            File top = environmentContext.getComponentsDir();
513            if (top != null && top.exists() && top.isDirectory()) {
514                // directory structure is components/<component name>/installation
515                // ...
516                File[] files = top.listFiles();
517                if (files != null) {
518                    for (int i = 0; i < files.length; i++) {
519                        if (!files[i].isDirectory()) {
520                            continue;
521                        }
522                        final File directory = files[i];
523                        try {
524                            buildComponent(directory);
525                        } catch (DeploymentException e) {
526                            LOG.error("Could not build Component: " + directory.getName(), e);
527                            LOG.warn("Deleting Component directory: " + directory);
528                            FileUtil.deleteFile(directory);
529                        }
530                    }
531                }
532            }
533        }
534    
535        protected void buildComponent(File componentDirectory) throws DeploymentException {
536            try {
537                String componentName = componentDirectory.getName();
538                ComponentEnvironment env = container.getEnvironmentContext().getComponentEnvironment(componentName);
539                if (!env.getStateFile().exists()) {
540                    // An installer has been created but the component has not been
541                    // installed
542                    // So remove it
543                    FileUtil.deleteFile(componentDirectory);
544                } else {
545                    InstallerMBeanImpl installer = createInstaller(componentName);
546                    installer.activateComponent();
547                    nonLoadedInstallers.put(componentName, installer);
548                }
549            } catch (Throwable e) {
550                LOG.error("Failed to deploy component: " + componentDirectory.getName(), e);
551                throw new DeploymentException(e);
552            }
553        }
554    
555        protected ComponentContextImpl buildComponentContext(File componentRoot, File installRoot, String name) throws IOException {
556            ComponentNameSpace cns = new ComponentNameSpace(container.getName(), name);
557            ComponentContextImpl context = new ComponentContextImpl(container, cns);
558            ComponentEnvironment env = new ComponentEnvironment();
559            FileUtil.buildDirectory(componentRoot);
560            File privateWorkspace = environmentContext.createWorkspaceDirectory(name);
561            env.setWorkspaceRoot(privateWorkspace);
562            env.setComponentRoot(componentRoot);
563            env.setInstallRoot(installRoot);
564            context.setEnvironment(env);
565            return context;
566        }
567    
568    }