| |
| package org.eclipse.virgo.nano.deployer.internal; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.jar.JarFile; |
| |
| import org.eclipse.virgo.nano.deployer.api.core.DeploymentIdentity; |
| import org.eclipse.virgo.medic.eventlog.EventLogger; |
| import org.eclipse.virgo.nano.deployer.NanoDeployerLogEvents; |
| import org.eclipse.virgo.nano.deployer.SimpleDeployer; |
| import org.eclipse.virgo.nano.deployer.StandardDeploymentIdentity; |
| import org.eclipse.virgo.nano.deployer.util.BundleInfosUpdater; |
| import org.eclipse.virgo.util.io.FileCopyUtils; |
| import org.eclipse.virgo.util.io.IOUtils; |
| import org.eclipse.virgo.util.osgi.manifest.BundleManifest; |
| import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory; |
| import org.eclipse.virgo.util.osgi.manifest.FragmentHost; |
| import org.eclipse.virgo.util.osgi.manifest.parse.BundleManifestParseException; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleException; |
| import org.osgi.service.packageadmin.PackageAdmin; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class BundleDeployer implements SimpleDeployer { |
| |
| private static final String JAR = "jar"; |
| |
| private static final boolean STATUS_ERROR = false; |
| |
| private static final boolean STATUS_OK = true; |
| |
| private static final char SLASH = '/'; |
| |
| private static final char BACKSLASH = '\\'; |
| |
| private static final String FILE_PROTOCOL = "file"; |
| |
| private static final String UNKNOWN = "unknown"; |
| |
| private static final String INSTALL_BY_REFERENCE_PREFIX = "reference:file:"; |
| |
| private static final String FRAGMEN_HOST_HEADER = "Fragment-Host"; |
| |
| private final EventLogger eventLogger; |
| |
| private final long largeFileCopyTimeout = 30000; |
| |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| private final BundleContext bundleContext; |
| |
| private final BundleInfosUpdater bundleInfosUpdater; |
| |
| private final PackageAdmin packageAdmin; |
| |
| private final File workBundleInstallLocation; |
| |
| public BundleDeployer(BundleContext bundleContext, PackageAdmin packageAdmin, EventLogger eventLogger) { |
| this.eventLogger = eventLogger; |
| this.bundleContext = bundleContext; |
| this.packageAdmin = packageAdmin; |
| String kernelHome = System.getProperty("org.eclipse.virgo.kernel.home"); |
| File kernelHomeFile = new File(kernelHome); |
| File bundlesInfoFile = new File(kernelHomeFile, "configuration/org.eclipse.equinox.simpleconfigurator/bundles.info"); |
| this.bundleInfosUpdater = new BundleInfosUpdater(bundlesInfoFile, kernelHomeFile); |
| String thisBundleName = this.bundleContext.getBundle().getSymbolicName(); |
| String staging = "staging"; |
| this.workBundleInstallLocation = new File(kernelHomeFile, "work" + File.separator + thisBundleName + File.separator + staging); |
| } |
| |
| private Boolean createInstallationFolder(){ |
| if (!this.workBundleInstallLocation.exists()) { |
| if (!this.workBundleInstallLocation.mkdirs()) { |
| this.logger.error("Failed to create staging directory '" + this.workBundleInstallLocation.getAbsolutePath() |
| + "' for bundle deployment."); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean validateUri(URI uri){ |
| if (!canWrite(uri)) { |
| this.logger.error("Cannot open the file " + uri + " for writing. The configured timeout is " + this.largeFileCopyTimeout + "."); |
| return false; |
| } |
| return true; |
| } |
| |
| private Boolean isFragment(FragmentHost hostHolder){ |
| return (hostHolder != null && hostHolder.getBundleSymbolicName() != null); |
| } |
| |
| private Boolean isFragment(Bundle bundle){ |
| Enumeration<String> keys = bundle.getHeaders().keys(); |
| while (keys.hasMoreElements()){ |
| if (keys.nextElement().equalsIgnoreCase(FRAGMEN_HOST_HEADER) ){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void refreshHosts(FragmentHost hostHolder, Bundle fragment){ |
| try { |
| Bundle[] hosts = this.packageAdmin.getBundles(hostHolder.getBundleSymbolicName(), null); |
| if (hosts != null) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHING_HOST, fragment.getSymbolicName(), fragment.getVersion()); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHING_HOST, fragment.getSymbolicName(), fragment.getVersion()); |
| this.packageAdmin.refreshPackages(hosts); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHED_HOST, fragment.getSymbolicName(), fragment.getVersion()); |
| } |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESH_HOST_ERROR, e, fragment.getSymbolicName(), fragment.getVersion()); |
| } |
| try { |
| if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) { |
| registerToBundlesInfo(fragment, true); |
| } |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_PERSIST_ERROR, e, fragment.getSymbolicName(), fragment.getVersion()); |
| } |
| } |
| |
| private void updateBundleInfo(Bundle bundle, Boolean isFragment){ |
| try { |
| if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) { |
| registerToBundlesInfo(bundle, true); |
| } |
| } catch (Exception ex) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_PERSIST_ERROR, ex, bundle.getSymbolicName(), bundle.getVersion()); |
| } |
| } |
| |
| @Override |
| public boolean install(URI uri) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING, new File(uri).toString()); |
| try{ |
| if (!validateUri(uri) || !(createInstallationFolder())) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, uri); |
| return STATUS_ERROR; |
| } |
| // copy bundle to work folder |
| File stagedFile = new File(workBundleInstallLocation, extractJarFileNameFromString(uri.toString())); |
| FileCopyUtils.copy(new File(uri), stagedFile); |
| |
| // install the bundle |
| final Bundle installed = this.bundleContext.installBundle(createInstallLocation(stagedFile)); |
| final FragmentHost hostHolder = getFragmentHostFromDeployedBundleIfExsiting(stagedFile); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLED, installed.getSymbolicName(), installed.getVersion()); |
| |
| //if fragment, refresh hosts and update bundles.info |
| if (isFragment(hostHolder)){ |
| refreshHosts(hostHolder, installed); |
| updateBundleInfo(installed, true); |
| } |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, e, uri); |
| return STATUS_ERROR; |
| } |
| return STATUS_OK; |
| } |
| |
| |
| @Override |
| public boolean start(URI uri) { |
| Bundle installedBundle = getInstalledBundle(uri); |
| if (installedBundle != null){ |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING, installedBundle.getSymbolicName(), installedBundle.getVersion()); |
| try { |
| if (!isFragment(installedBundle)){ |
| installedBundle.start(); |
| updateBundleInfo(installedBundle, false); |
| }else{ |
| this.logger.warn("The installed bundle for the given url ["+ uri +"] is a fragment bundle. Start operation for this url failed. "); |
| return STATUS_ERROR; |
| } |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING_ERROR, e, installedBundle.getSymbolicName(), installedBundle.getVersion()); |
| return STATUS_ERROR; |
| } |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTED, installedBundle.getSymbolicName(), installedBundle.getVersion()); |
| } |
| return STATUS_OK; |
| } |
| |
| @Override |
| public boolean deploy(URI path) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING, new File(path).toString()); |
| final File deployedFile = new File(path); |
| |
| if (!canWrite(path)) { |
| this.logger.error("Cannot open the file " + path + " for writing. The configured timeout is " + this.largeFileCopyTimeout + "."); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, path); |
| return STATUS_ERROR; |
| } |
| final Bundle installed; |
| final FragmentHost hostHolder; |
| try { |
| // copy bundle to work |
| if (!this.workBundleInstallLocation.exists()) { |
| if (!this.workBundleInstallLocation.mkdirs()) { |
| this.logger.error("Failed to create staging directory '" + this.workBundleInstallLocation.getAbsolutePath() |
| + "' for bundle deployment."); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, path); |
| return STATUS_ERROR; |
| } |
| } |
| File stagedFile = new File(workBundleInstallLocation, extractJarFileNameFromString(path.toString())); |
| FileCopyUtils.copy(deployedFile, stagedFile); |
| // install the bundle |
| installed = this.bundleContext.installBundle(createInstallLocation(stagedFile)); |
| hostHolder = getFragmentHostFromDeployedBundleIfExsiting(stagedFile); |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, e, path); |
| return STATUS_ERROR; |
| } |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLED, installed.getSymbolicName(), installed.getVersion()); |
| |
| if (hostHolder != null && hostHolder.getBundleSymbolicName() != null) { |
| try { |
| Bundle[] hosts = this.packageAdmin.getBundles(hostHolder.getBundleSymbolicName(), null); |
| if (hosts != null) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHING_HOST, installed.getSymbolicName(), installed.getVersion()); |
| this.packageAdmin.refreshPackages(hosts); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHED_HOST, installed.getSymbolicName(), installed.getVersion()); |
| } |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESH_HOST_ERROR, e, installed.getSymbolicName(), installed.getVersion()); |
| } |
| } else { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING, installed.getSymbolicName(), installed.getVersion()); |
| try { |
| installed.start(); |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING_ERROR, e, installed.getSymbolicName(), installed.getVersion()); |
| return STATUS_ERROR; |
| } |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTED, installed.getSymbolicName(), installed.getVersion()); |
| } |
| try { |
| if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) { |
| registerToBundlesInfo(installed, hostHolder != null && hostHolder.getBundleSymbolicName() != null); |
| } |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_PERSIST_ERROR, e, installed.getSymbolicName(), installed.getVersion()); |
| } |
| |
| return STATUS_OK; |
| } |
| |
| private FragmentHost getFragmentHostFromDeployedBundleIfExsiting(File stagedFile) { |
| try { |
| JarFile bundleJar = new JarFile(stagedFile); |
| BundleManifest manifest = BundleManifestFactory.createBundleManifest(new InputStreamReader( |
| bundleJar.getInputStream(bundleJar.getEntry(JarFile.MANIFEST_NAME)))); |
| return manifest.getFragmentHost(); |
| } catch (IOException ioe) { |
| this.logger.error("Failed to extract the fragment host header from file '"+ stagedFile.getAbsolutePath() +"'.", ioe); |
| return null; |
| } catch (BundleManifestParseException bmpe) { |
| return null; |
| } |
| } |
| |
| @Override |
| public boolean update(URI path) { |
| final File updatedFile = new File(path); |
| final File matchingStagedFile = new File(this.workBundleInstallLocation, extractJarFileNameFromString(path.toString())); |
| |
| if (!canWrite(path)) { |
| this.logger.error("Cannot open the file [" + path + "] for writing. Timeout is [" + this.largeFileCopyTimeout + "]."); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATING_ERROR, path); |
| return STATUS_ERROR; |
| } |
| |
| final Bundle bundle = this.bundleContext.getBundle(createInstallLocation(matchingStagedFile)); |
| if (bundle != null) { |
| try { |
| // copy the updated bundle over the old one |
| FileCopyUtils.copy(updatedFile, matchingStagedFile); |
| |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATING, bundle.getSymbolicName(), bundle.getVersion()); |
| bundle.update(); |
| if (this.packageAdmin != null) { |
| this.packageAdmin.refreshPackages(new Bundle[] { bundle }); |
| this.logger.info("Update of file with path '" + path + "' is successful."); |
| } |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATED, bundle.getSymbolicName(), bundle.getVersion()); |
| } catch (Exception e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATE_ERROR, e, bundle.getSymbolicName(), bundle.getVersion()); |
| } |
| } else { |
| deploy(path); |
| } |
| return STATUS_OK; |
| } |
| |
| @Override |
| public boolean undeploy(Bundle bundle) { |
| if (bundle != null) { |
| File stagingFileToDelete = new File(bundle.getLocation().substring(BundleDeployer.INSTALL_BY_REFERENCE_PREFIX.length())); |
| final FragmentHost hostHolder = getFragmentHostFromDeployedBundleIfExsiting(stagingFileToDelete); |
| try { |
| if (this.logger.isInfoEnabled()) { |
| this.logger.info("Removing bundle '" + bundle.getSymbolicName() + "' version '" + bundle.getVersion() + "' from bundles.info."); |
| } |
| if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) { |
| unregisterToBundlesInfo(bundle, hostHolder != null && hostHolder.getBundleSymbolicName() != null); |
| this.logger.info("Successfully removed bundle '" + bundle.getSymbolicName() + "' version '" + bundle.getVersion() |
| + "' from bundles.info."); |
| } else { |
| this.logger.error("BundleInfosUpdater not available. Failed to remove bundle '" + bundle.getSymbolicName() + "' version '" |
| + bundle.getVersion() + "' from bundles.info."); |
| } |
| } catch (Exception e) { |
| this.logger.warn("Failed to unregister bundle '" + bundle.getSymbolicName() + "' version '" + bundle.getVersion() + "'", e); |
| } |
| try { |
| if (hostHolder == null) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STOPPING, bundle.getSymbolicName(), bundle.getVersion()); |
| bundle.stop(); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_STOPPED, bundle.getSymbolicName(), bundle.getVersion()); |
| } |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UNINSTALLING, bundle.getSymbolicName(), bundle.getVersion()); |
| bundle.uninstall(); |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UNINSTALLED, bundle.getSymbolicName(), bundle.getVersion()); |
| |
| if (!stagingFileToDelete.delete()) { |
| this.logger.warn("Could not delete staging file '" + stagingFileToDelete.getAbsolutePath() + "'"); |
| } |
| } catch (BundleException e) { |
| this.eventLogger.log(NanoDeployerLogEvents.NANO_UNDEPLOY_ERROR, e, bundle.getSymbolicName(), bundle.getVersion()); |
| return STATUS_ERROR; |
| } |
| } |
| return STATUS_OK; |
| } |
| |
| @Override |
| public boolean canServeFileType(String fileType) { |
| return JAR.equals(fileType); |
| } |
| |
| private Bundle getInstalledBundle(URI uri){ |
| File matchingStagingBundle = new File(this.workBundleInstallLocation, extractJarFileNameFromString(uri.toString())); |
| return this.bundleContext.getBundle(createInstallLocation(matchingStagingBundle)); |
| } |
| |
| @Override |
| public boolean isDeployed(URI path) { |
| File matchingStagingBundle = new File(this.workBundleInstallLocation, extractJarFileNameFromString(path.toString())); |
| if (this.bundleContext.getBundle(createInstallLocation(matchingStagingBundle)) == null) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public DeploymentIdentity getDeploymentIdentity(URI path) { |
| File matchingStagingBundle = new File(this.workBundleInstallLocation, extractJarFileNameFromString(path.toString())); |
| Bundle bundle = this.bundleContext.getBundle(createInstallLocation(matchingStagingBundle)); |
| if (bundle == null) { |
| return null; |
| } |
| return new StandardDeploymentIdentity(JAR, bundle.getSymbolicName(), bundle.getVersion().toString()); |
| } |
| |
| private boolean canWrite(URI path) { |
| int tries = -1; |
| boolean isWritable = false; |
| // Some big files are copied very slowly, but the event is received |
| // immediately. |
| // So we will wait 0.5 x 240 i.e. 2 minutes |
| final long timeout = this.largeFileCopyTimeout / 500; |
| while (tries < timeout) { |
| FileInputStream fis = null; |
| try { |
| fis = new FileInputStream(new File(path)); |
| isWritable = true; |
| break; |
| } catch (FileNotFoundException e) { |
| if (this.logger.isInfoEnabled()) { |
| this.logger.info("File is still locked.", e); |
| } |
| } finally { |
| IOUtils.closeQuietly(fis); |
| } |
| try { |
| Thread.sleep(500); |
| } catch (InterruptedException e) { |
| this.logger.error("InterruptedException occurred.", e); |
| } |
| tries++; |
| } |
| return isWritable; |
| } |
| |
| private final void registerToBundlesInfo(Bundle bundle, boolean isFragment) throws URISyntaxException, IOException, BundleException { |
| String location = bundle.getLocation().replace(BACKSLASH, SLASH); |
| location = location.replaceAll(" ", "%20"); |
| String scheme = new URI(location).getScheme(); |
| if (scheme != null && !scheme.equals(FILE_PROTOCOL)) { |
| location = new URI(location).getRawSchemeSpecificPart(); |
| } |
| String symbolicName = bundle.getSymbolicName(); |
| this.bundleInfosUpdater.addBundleToBundlesInfo(symbolicName == null ? UNKNOWN : symbolicName, new URI(location), |
| bundle.getVersion().toString(), SimpleDeployer.HOT_DEPLOYED_ARTIFACTS_START_LEVEL, !isFragment); |
| this.bundleInfosUpdater.updateBundleInfosRepository(); |
| } |
| |
| private final void unregisterToBundlesInfo(Bundle bundle, boolean isFragment) throws IOException, BundleException, URISyntaxException { |
| String location = bundle.getLocation().replace(BACKSLASH, SLASH); |
| location = location.replaceAll(" ", "%20"); |
| String scheme = new URI(location).getScheme(); |
| if (scheme != null && !scheme.equals(FILE_PROTOCOL)) { |
| location = new URI(location).getRawSchemeSpecificPart(); |
| } |
| String symbolicName = bundle.getSymbolicName(); |
| this.bundleInfosUpdater.removeBundleFromBundlesInfo(symbolicName == null ? UNKNOWN : symbolicName, new URI(location), |
| bundle.getVersion().toString(), SimpleDeployer.HOT_DEPLOYED_ARTIFACTS_START_LEVEL, !isFragment); |
| this.bundleInfosUpdater.updateBundleInfosRepository(); |
| } |
| |
| private String createInstallLocation(final File jarFile) { |
| return INSTALL_BY_REFERENCE_PREFIX + jarFile.getAbsolutePath(); |
| } |
| |
| private String extractJarFileNameFromString(String path) { |
| final String warName = path.substring(path.lastIndexOf(SLASH) + 1); |
| return warName; |
| } |
| |
| @Override |
| public boolean isDeployFileValid(File file) { |
| JarFile jarFile = null; |
| try { |
| jarFile = new JarFile(file); |
| } catch (IOException e) { |
| this.logger.error("The deployed file '" + file.getAbsolutePath() + "' is an invalid zip file."); |
| return false; |
| } finally { |
| try { |
| if (jarFile != null) { |
| jarFile.close(); |
| } |
| } catch (IOException e) { |
| // do nothing |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public List<String> getAcceptedFileTypes() { |
| List<String> types = new ArrayList<String>(); |
| types.add(JAR); |
| return types; |
| } |
| |
| |
| } |