All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.octopus.extract.BundleExtractor Maven / Gradle / Ivy

/***************************************************************************/
/*                                                                         */
/*                      ADOBE CONFIDENTIAL                                 */
/*                      _ _ _ _ _ _ _ _ _ _                                */
/*                                                                         */
/*  Copyright 2011, Adobe Systems Incorporated                             */
/*  All Rights Reserved.                                                   */
/*                                                                         */
/*  NOTICE: All information contained herein is, and remains the property  */
/*  of Adobe Systems Incorporated and its suppliers, if any. The           */
/*  intellectual and technical concepts contained herein are proprietary   */
/*  to Adobe Systems Incorporated and its suppliers and may be covered by  */
/*  U.S. and Foreign Patents, patents in process, and are protected by     */
/*  trade secret or copyright law.  Dissemination of this information or   */
/*  reproduction of this material is strictly forbidden unless prior       */
/*  written permission is obtained from Adobe Systems Incorporated.        */
/*                                                                         */
/***************************************************************************/

package com.adobe.octopus.extract;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
import org.osgi.service.packageadmin.PackageAdmin;

/**
 * @author  tnaroska
 * @version $Revision$
 * @since   1.0
 */
public final class BundleExtractor
{
	// --------------------------------------------------------------------------- Private Constants

	/** Root folder used for extracted resources in the bundle data store */
	private static final String ROOT = ".octopus";

	/** file that stores version information about this extractor's bundle and any of its 
	 * attached fragments */
	private static final String VERSION_FILE = ROOT + "/nativeCommExtractedVersions";
	
	// --------------------------------------------------------------------------- Private Variables

	/** Target bundle to operate on */
	private final Bundle bundle;
	
	/** Target bundle context to operate on */
	private final File baseDir;

	// -------------------------------------------------------------------------- Public Constructor

	/**
	 * Creates a BundleExtractor instance for the specified bundle.
	 *
	 * @param bundle - target bundle of this instance
	 */
	public BundleExtractor(Bundle bundle)
	{
		if (bundle == null)
		{
			throw new IllegalArgumentException("bundle == null");
		}

		this.bundle = bundle;

		BundleContext context = getBundleContext();

		baseDir = context.getDataFile(ROOT);
		if (baseDir == null)
		{
			throw new IllegalArgumentException("the OSGi platform has no file system support");
		}
		baseDir.mkdir();
		
		// Needed to retrieve fragment information
		PackageAdmin packageAdmin = (PackageAdmin)context.getService(context.getServiceReference(PackageAdmin.class.getName()));
	    
	    // Check if the versions have changed
	    try
		{
			if( resourcesNeedUpdate(packageAdmin) )
			{
				//If they have, delete any content of the working directory
				deleteDirectory( baseDir );
				baseDir.mkdir();
				//Create a new version file
				updateVersionFile(packageAdmin);
			}
		}
		catch (IOException e)
		{
			throw new IllegalStateException( e );
		}
	}

	/**
	 * Creates a BundleExtractor instance for the parent bundle of the specified class.
	 *
	 * @param clazz - a class object of the target bundle
	 */
	public BundleExtractor(Class clazz)
	{
		this(FrameworkUtil.getBundle(clazz));
	}

	// ------------------------------------------------------------------------------ Public Methods

	/**
	 * Extracts a resource from the target bundle to the file system. If path denotes a folder
	 * all contents of the folder is extracted recursively.
	 *
	 * @param path - path to the bundle resource (always relative to the bundle root)
	 * @return file object pointing to the extracted file/folder
	 * @throws IOException - if path cannot be found in the bundle or extraction fails
	 */
	public File extractResource(String path) throws IOException
	{
		File resolvedFile = getEntry(path);
		return resolvedFile;
	}

	/**
	 * Extract the specified file from this bundle and mark it as executable (Linux only).
	 * 
	 * @param path - path to the executable bundle resource (always relative to the bundle root)
	 * @return file object pointing to the extracted executable file
	 * @throws IOException - if path cannot be found in the bundle or extraction fails
	 */
	public File extractExecutable(String path) throws IOException
	{
		File resolvedFile = getEntry(path);

		// chmod +x on unix-like systems
		if (File.separatorChar == '/')
		{
			markExecutable(path, resolvedFile);
		}
		return resolvedFile;
	}

	// ----------------------------------------------------------------------------- Private Methods
	/**
	 * Recursively delete a directory
	 * @param path directory to delete
	 * @return see File.delete()
	 */
	private boolean deleteDirectory(File path) 
	{
		File[] files = path.listFiles();
		for(int i=0; i 1 )
	        	{
	        		resourcesNeedUpdate = true;
	        	}
            }
        	finally
            {
            	is.close();
            }
        } // If the version file does not exist, it means an update is needed

        return resourcesNeedUpdate;
	}
	
	
	/**
	 * Updates/creates the version file with the ID and version info from the 
	 * extractor's bundle and any attached fragment bundles. 
	 * It will create the file if it does not exist
	 * @throws IOException
	 */
	private void updateVersionFile(PackageAdmin packageAdmin) throws IOException
	{
    	Properties prop = new Properties();
        // store host bundle id and version
        prop.setProperty( String.valueOf( bundle.getBundleId() ), bundle.getVersion().toString() );
        
        // store fragments info, if any
        if( packageAdmin != null )
		{
			Bundle[] fragments = packageAdmin.getFragments( bundle );
	
			if(fragments != null)
			{
				for( Bundle fragment : fragments )
				{
					prop.setProperty( String.valueOf( fragment.getBundleId() ), fragment.getVersion().toString() );
				}
			}
		}
	        
        BundleContext context = getBundleContext();
        File versionFile = context.getDataFile( VERSION_FILE );
        
        OutputStream os = new FileOutputStream( versionFile );
        try
        {
	        prop.store(os, null);
        }
        finally
        {
        	os.close();
        }

	}
	
	/**
	 * Get the current BundleContext
	 * @return the bundle's current context
	 */
	private BundleContext getBundleContext()
	{
		BundleContext context = bundle.getBundleContext();
		if (context == null)
		{
			startBundle();
	
			context = bundle.getBundleContext();
			if (context == null)
			{
				throw new IllegalArgumentException("bundle '" + bundle +"' has no BundleContext." +
					"Either it is not active or a fragment bundle.");
			}
		}
		return context;
	}

	/**
	 * Find the specified path in the target bundle and resolve it to a java.io.File, unpacking
	 * bundle contents if necessary.
	 *
	 * @param path - bundle resource path (always relative to the bundle root)
	 * @return File object pointing to the extracted file.
	 * @throws IOException - if resource not found or cannot be resolved
	 */
	private File getEntry(String path) throws IOException
	{
		// first try recursive extraction
		Enumeration entries = bundle.findEntries(path, null, true);
		if (entries != null)
		{
			while (entries.hasMoreElements())
			{
				URL url = (URL) entries.nextElement();

				String entryPath = url.getPath();
				File target = new File(baseDir, entryPath);
				if (!entryPath.endsWith("/"))
				{
					copyOut(url, target);
				}
				else
				{
					target.mkdirs();
				}
			}

			File result = new File(baseDir, path);
			return result;
		}
		else
		{
			// fall back to single file extraction
			URL rootUrl = bundle.getResource(path);
			if (rootUrl == null)
			{
				throw new FileNotFoundException("'" + path + "' not found in Bundle " + bundle);
			}
			String rootPath = rootUrl.getPath();
			File result = new File(baseDir, rootPath);

			copyOut(rootUrl, result);
			return result;
		}
	}

	/**
	 * Copy out a bundle resource to the specified target location.
	 * If the resource already exists at the location it is not copied again.
	 * @param url - bundler resource URL
	 * @param target - target file location
	 * @throws IOException on error
	 */
	private void copyOut(final URL url, File target) throws IOException
	{
		if( ! target.exists() )
		{
			target.getParentFile().mkdirs();
	
			InputStream in = url.openStream();
			OutputStream out = new FileOutputStream(target);
	
			copy(in, out);
		}
	}

	/**
	 * Copy inputstream to outputstream.
	 * @param is - source
	 * @param os - destination
	 * @return bytes copied
	 * @throws IOException on error
	 */
	private static long copy(InputStream is, OutputStream os) throws IOException
	{
		long total = 0;
		try
		{
			int read = 0;
			byte[] bytes = new byte[64 * 1024];

			while ((read = is.read(bytes)) != -1)
			{
				os.write(bytes, 0, read);
				total += read;
			}
		}
		finally
		{
			safeClose(is);
			safeClose(os);
		}
		return total;
	}

	/**
	 * Mark the specified file as executable using Java File class.
	 * @param path - bundle resource path
	 * @param resolvedFile - actual file to modify
	 */
	private void markExecutable(String path, File resolvedFile) throws IOException
	{
		try
		{
			if (!resolvedFile.setExecutable(true, true))
			{
				throw new IOException("Failed to mark '" + path + "' of Bundle '" + bundle +
							"' as executable. (" + resolvedFile.getAbsolutePath() + ")");
			}
		}
		catch (NoSuchMethodError e)
		{
			// fallback to java 1.5 implementation
			markExecutable15(path, resolvedFile);
		}
	}

	/**
	 * Mark the specified file as executable spawning a chmod process.
	 * @param path - bundle resource path
	 * @param resolvedFile - actual file to modify
	 */
	private void markExecutable15(String path, File resolvedFile) throws IOException
	{
		try
		{
			Process p = Runtime.getRuntime().exec(new String[] {
					"chmod",
					"+x",
					resolvedFile.getAbsolutePath()
				});
			int pe = p.waitFor();
			if (pe != 0)
			{
				throw new IOException(String.format("chmod +x \"%s\" returned %i",
						resolvedFile.getAbsolutePath(), pe));
			}
		}
		catch (Exception ioe)
		{
			throw new IOException("Failed to mark '" + path + "' of Bundle '" + bundle +
					"' as executable. (" + resolvedFile.getAbsolutePath() + ")", ioe);
		}
	}

	private static void safeClose(Closeable c)
	{
		try
		{
			c.close();
		}
		catch (IOException e)
		{
			;
		}
	}

	/**
	 * Try to start the target bundle if it is not already active.
	 */
	private void startBundle()
	{
		try
		{
			if (bundle.getState() != Bundle.ACTIVE)
			{
				bundle.start(Bundle.START_TRANSIENT);
			}
		}
		catch (BundleException be)
		{
			throw new IllegalArgumentException("failed to start bundle '" + bundle +"'", be);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy