| /******************************************************************************* |
| * Copyright (c) 2005, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Rob Harrop - SpringSource Inc. (bug 247520 and 253942) |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.internal.baseadaptor; |
| |
| import java.io.*; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.net.*; |
| import java.util.*; |
| import org.eclipse.core.runtime.adaptor.EclipseStarter; |
| import org.eclipse.core.runtime.adaptor.LocationManager; |
| import org.eclipse.core.runtime.internal.adaptor.EclipseAdaptorMsg; |
| import org.eclipse.osgi.baseadaptor.BaseAdaptor; |
| import org.eclipse.osgi.baseadaptor.BaseData; |
| import org.eclipse.osgi.baseadaptor.bundlefile.*; |
| import org.eclipse.osgi.baseadaptor.hooks.*; |
| import org.eclipse.osgi.framework.adaptor.*; |
| import org.eclipse.osgi.framework.debug.Debug; |
| import org.eclipse.osgi.framework.debug.FrameworkDebugOptions; |
| import org.eclipse.osgi.framework.internal.core.*; |
| import org.eclipse.osgi.framework.internal.core.Constants; |
| import org.eclipse.osgi.framework.log.FrameworkLogEntry; |
| import org.eclipse.osgi.framework.util.KeyedHashSet; |
| import org.eclipse.osgi.service.datalocation.Location; |
| import org.eclipse.osgi.service.resolver.*; |
| import org.eclipse.osgi.storagemanager.ManagedOutputStream; |
| import org.eclipse.osgi.storagemanager.StorageManager; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.*; |
| |
| public class BaseStorage implements SynchronousBundleListener { |
| private static final String RUNTIME_ADAPTOR = FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + "/eclipseadaptor"; //$NON-NLS-1$ |
| private static final String OPTION_PLATFORM_ADMIN = RUNTIME_ADAPTOR + "/debug/platformadmin"; //$NON-NLS-1$ |
| private static final String OPTION_PLATFORM_ADMIN_RESOLVER = RUNTIME_ADAPTOR + "/debug/platformadmin/resolver"; //$NON-NLS-1$ |
| private static final String OPTION_MONITOR_PLATFORM_ADMIN = RUNTIME_ADAPTOR + "/resolver/timing"; //$NON-NLS-1$ |
| private static final String OPTION_RESOLVER_READER = RUNTIME_ADAPTOR + "/resolver/reader/timing"; //$NON-NLS-1$ |
| private static final String PROP_FRAMEWORK_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$ |
| private static final String PROP_BUNDLE_STORE = "osgi.bundlestore"; //$NON-NLS-1$ |
| // The name of the bundle data directory |
| static final String DATA_DIR_NAME = "data"; //$NON-NLS-1$ |
| static final String LIB_TEMP = "libtemp"; //$NON-NLS-1$ |
| // System property used to determine whether State saver needs to be enabled |
| private static final String PROP_ENABLE_STATE_SAVER = "eclipse.enableStateSaver"; //$NON-NLS-1$ |
| static final String BUNDLEFILE_NAME = "bundlefile"; //$NON-NLS-1$ |
| // System property used to clean the osgi configuration area |
| private static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$ |
| |
| /** The current bundle data version */ |
| public static final byte BUNDLEDATA_VERSION = 18; |
| /** |
| * flag to indicate a framework extension is being intialized |
| */ |
| public static final byte EXTENSION_INITIALIZE = 0x01; |
| /** |
| * flag to indicate a framework extension is being installed |
| */ |
| public static final byte EXTENSION_INSTALLED = 0x02; |
| /** |
| * flag to indicate a framework extension is being uninstalled |
| */ |
| public static final byte EXTENSION_UNINSTALLED = 0x04; |
| /** |
| * flag to indicate a framework extension is being updated |
| */ |
| public static final byte EXTENSION_UPDATED = 0x08; |
| |
| /** |
| * the file name for the delete flag. If this file exists in one a directory |
| * under the bundle store area then it will be removed during the |
| * compact operation. |
| */ |
| public static final String DELETE_FLAG = ".delete"; //$NON-NLS-1$ |
| private static final String PERM_DATA_FILE = ".permdata"; //$NON-NLS-1$ |
| private static final byte PERMDATA_VERSION = 1; |
| |
| private final MRUBundleFileList mruList = new MRUBundleFileList(); |
| |
| private BaseAdaptor adaptor; |
| // assume a file: installURL |
| private String installPath; |
| private StorageManager storageManager; |
| private StateManager stateManager; |
| // no need to synchronize on storageHooks because the elements are statically set in initialize |
| private KeyedHashSet storageHooks = new KeyedHashSet(5, false); |
| private BundleContext context; |
| private SynchronousBundleListener extensionListener; |
| |
| /** |
| * The add URL method used to support framework extensions |
| */ |
| private final Method addFwkURLMethod; |
| private final Method addExtURLMethod; |
| |
| /** |
| * The list of configured framework extensions |
| */ |
| private String[] configuredExtensions; |
| |
| private long timeStamp = 0; |
| private int initialBundleStartLevel = 1; |
| |
| private final Object nextIdMonitor = new Object(); |
| private volatile long nextId = 1; |
| /** |
| * directory containing installed bundles |
| */ |
| private File bundleStoreRoot; |
| |
| private BasePermissionStorage permissionStorage; |
| private StateSaver stateSaver; |
| private boolean invalidState; |
| private boolean storageManagerClosed; |
| |
| BaseStorage() { |
| // make constructor package private |
| // initialize the addXYZURLMethods to support framework extensions |
| addFwkURLMethod = findAddURLMethod(getFwkClassLoader(), "addURL"); //$NON-NLS-1$ |
| addExtURLMethod = findAddURLMethod(getExtClassLoader(), "addURL"); //$NON-NLS-1$ |
| } |
| |
| public void initialize(BaseAdaptor adaptor) throws IOException { |
| this.adaptor = adaptor; |
| setDebugOptions(); |
| if (Boolean.valueOf(FrameworkProperties.getProperty(BaseStorage.PROP_CLEAN)).booleanValue()) |
| cleanOSGiCache(); |
| |
| // we need to set the install path as soon as possible so we can determine |
| // the absolute location of install relative URLs |
| Location installLoc = LocationManager.getInstallLocation(); |
| if (installLoc != null) { |
| URL installURL = installLoc.getURL(); |
| // assume install URL is file: based |
| installPath = installURL.getPath(); |
| } |
| boolean readOnlyConfiguration = LocationManager.getConfigurationLocation().isReadOnly(); |
| storageManager = initFileManager(LocationManager.getOSGiConfigurationDir(), readOnlyConfiguration ? "none" : null, readOnlyConfiguration); //$NON-NLS-1$ |
| storageManagerClosed = false; |
| // initialize the storageHooks |
| StorageHook[] hooks = adaptor.getHookRegistry().getStorageHooks(); |
| for (int i = 0; i < hooks.length; i++) |
| storageHooks.add(hooks[i]); |
| } |
| |
| private static Method findAddURLMethod(ClassLoader cl, String name) { |
| if (cl == null) |
| return null; |
| return findMethod(cl.getClass(), name, new Class[] {URL.class}); |
| } |
| |
| // recursively searches a class and it's superclasses for a (potentially inaccessable) method |
| private static Method findMethod(Class clazz, String name, Class[] args) { |
| if (clazz == null) |
| return null; // ends the recursion when getSuperClass returns null |
| try { |
| Method result = clazz.getDeclaredMethod(name, args); |
| result.setAccessible(true); |
| return result; |
| } catch (NoSuchMethodException e) { |
| // do nothing look in super class below |
| } catch (SecurityException e) { |
| // if we do not have the permissions then we will not find the method |
| } |
| return findMethod(clazz.getSuperclass(), name, args); |
| } |
| |
| private static void callAddURLMethod(ClassLoader cl, Method meth, URL arg) throws InvocationTargetException { |
| try { |
| meth.invoke(cl, new Object[] {arg}); |
| } catch (Throwable t) { |
| throw new InvocationTargetException(t); |
| } |
| } |
| |
| private ClassLoader getFwkClassLoader() { |
| return this.getClass().getClassLoader(); |
| } |
| |
| private ClassLoader getExtClassLoader() { |
| ClassLoader cl = ClassLoader.getSystemClassLoader(); |
| ClassLoader extcl = cl.getParent(); |
| while ((extcl != null) && (extcl.getParent() != null)) { |
| extcl = extcl.getParent(); |
| } |
| return extcl; |
| } |
| |
| private static void setDebugOptions() { |
| FrameworkDebugOptions options = FrameworkDebugOptions.getDefault(); |
| // may be null if debugging is not enabled |
| if (options == null) |
| return; |
| StateManager.DEBUG = options != null; |
| StateManager.DEBUG_READER = options.getBooleanOption(OPTION_RESOLVER_READER, false); |
| StateManager.MONITOR_PLATFORM_ADMIN = options.getBooleanOption(OPTION_MONITOR_PLATFORM_ADMIN, false); |
| StateManager.DEBUG_PLATFORM_ADMIN = options.getBooleanOption(OPTION_PLATFORM_ADMIN, false); |
| StateManager.DEBUG_PLATFORM_ADMIN_RESOLVER = options.getBooleanOption(OPTION_PLATFORM_ADMIN_RESOLVER, false); |
| } |
| |
| protected StorageManager initFileManager(File baseDir, String lockMode, boolean readOnly) throws IOException { |
| StorageManager sManager = new StorageManager(baseDir, lockMode, readOnly); |
| try { |
| sManager.open(!readOnly); |
| } catch (IOException ex) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading framework metadata: " + ex.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(ex); |
| } |
| String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, ex.getMessage()); |
| FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, ex, null); |
| adaptor.getFrameworkLog().log(logEntry); |
| FrameworkProperties.setProperty(EclipseStarter.PROP_EXITCODE, "15"); //$NON-NLS-1$ |
| String errorDialog = "<title>" + AdaptorMsg.ADAPTOR_STORAGE_INIT_FAILED_TITLE + "</title>" + NLS.bind(AdaptorMsg.ADAPTOR_STORAGE_INIT_FAILED_MSG, baseDir) + "\n" + ex.getMessage(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| FrameworkProperties.setProperty(EclipseStarter.PROP_EXITDATA, errorDialog); |
| throw ex; |
| } |
| return sManager; |
| } |
| |
| public boolean isReadOnly() { |
| return storageManager.isReadOnly(); |
| } |
| |
| public void compact() throws IOException { |
| if (!isReadOnly()) |
| compact(getBundleStoreRoot()); |
| } |
| |
| private void compact(File directory) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("compact(" + directory.getPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ |
| String list[] = directory.list(); |
| if (list == null) |
| return; |
| |
| int len = list.length; |
| for (int i = 0; i < len; i++) { |
| if (BaseStorage.DATA_DIR_NAME.equals(list[i])) |
| continue; /* do not examine the bundles data dir. */ |
| File target = new File(directory, list[i]); |
| // if the file is a directory |
| if (!target.isDirectory()) |
| continue; |
| File delete = new File(target, BaseStorage.DELETE_FLAG); |
| // and the directory is marked for delete |
| if (delete.exists()) { |
| // if rm fails to delete the directory and .delete was removed |
| if (!AdaptorUtil.rm(target) && !delete.exists()) { |
| try { |
| // recreate .delete |
| FileOutputStream out = new FileOutputStream(delete); |
| out.close(); |
| } catch (IOException e) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("Unable to write " + delete.getPath() + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| } else { |
| compact(target); /* descend into directory */ |
| } |
| } |
| } |
| |
| public long getFreeSpace() throws IOException { |
| // cannot implement this without native code! |
| return -1; |
| } |
| |
| public File getDataFile(BaseData data, String path) { |
| BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY); |
| if (storageHook == null) |
| return null; |
| return storageHook.getDataFile(path); |
| } |
| |
| BaseAdaptor getAdaptor() { |
| return adaptor; |
| } |
| |
| public void installNativeCode(BaseData data, String[] nativepaths) throws BundleException { |
| if (nativepaths.length > 0) { |
| BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY); |
| if (storageHook != null) |
| storageHook.installNativePaths(nativepaths); |
| } |
| } |
| |
| public Dictionary loadManifest(BaseData data) throws BundleException { |
| return loadManifest(data, false); |
| } |
| |
| public Dictionary loadManifest(BaseData bundleData, boolean firstTime) throws BundleException { |
| Dictionary result = null; |
| StorageHook[] dataStorageHooks = bundleData.getStorageHooks(); |
| for (int i = 0; i < dataStorageHooks.length && result == null; i++) |
| result = dataStorageHooks[i].getManifest(firstTime); |
| if (result == null) |
| result = AdaptorUtil.loadManifestFrom(bundleData); |
| if (result == null) |
| throw new BundleException(NLS.bind(AdaptorMsg.MANIFEST_NOT_FOUND_EXCEPTION, Constants.OSGI_BUNDLE_MANIFEST, bundleData.getLocation()), BundleException.MANIFEST_ERROR); |
| return result; |
| } |
| |
| public File getExtractFile(BaseData data, String path) { |
| BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY); |
| if (storageHook == null) |
| return null; |
| // first check the child generation dir |
| File childGenDir = storageHook.getGenerationDir(); |
| if (childGenDir != null) { |
| File childPath = new File(childGenDir, path); |
| if (childPath.exists()) |
| return childPath; |
| } |
| // now check the parent |
| File parentGenDir = storageHook.getParentGenerationDir(); |
| if (parentGenDir != null) { |
| // there is a parent generation check if the file exists |
| File parentPath = new File(parentGenDir, path); |
| if (parentPath.exists()) |
| // only use the parent generation file if it exists; do not extract there |
| return parentPath; |
| } |
| // did not exist in both locations; create a file for extraction. |
| File bundleGenerationDir = storageHook.createGenerationDir(); |
| /* if the generation dir exists, then we have place to cache */ |
| if (bundleGenerationDir != null && bundleGenerationDir.exists()) |
| return new File(bundleGenerationDir, path); |
| return null; |
| } |
| |
| public BaseData[] getInstalledBundles() { |
| try { |
| return readBundleDatas(); |
| } catch (Throwable t) { |
| // be safe here and throw out the results and start over |
| // otherwise this would result in a failed launch |
| FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, "Error loading bundle datas. Recalculating cache.", 0, t, null); //$NON-NLS-1$ |
| adaptor.getFrameworkLog().log(logEntry); |
| return null; |
| } |
| } |
| |
| private BaseData[] readBundleDatas() { |
| InputStream bundleDataStream = findStorageStream(LocationManager.BUNDLE_DATA_FILE); |
| if (bundleDataStream == null) |
| return null; |
| try { |
| DataInputStream in = new DataInputStream(new BufferedInputStream(bundleDataStream)); |
| try { |
| byte version = in.readByte(); |
| if (version != BUNDLEDATA_VERSION) |
| return null; |
| timeStamp = in.readLong(); |
| initialBundleStartLevel = in.readInt(); |
| nextId = in.readLong(); |
| |
| int numStorageHooks = in.readInt(); |
| StorageHook[] storageHooks = adaptor.getHookRegistry().getStorageHooks(); |
| if (numStorageHooks != storageHooks.length) |
| return null; // must have the same number of storagehooks to properly read the data |
| for (int i = 0; i < numStorageHooks; i++) { |
| Object storageKey = storageHooks[i].getKey(); |
| int storageVersion = storageHooks[i].getStorageVersion(); |
| if (!storageKey.equals(in.readUTF()) || storageVersion != in.readInt()) |
| return null; // some storage hooks have changed must throw the data away. |
| } |
| |
| int bundleCount = in.readInt(); |
| ArrayList result = new ArrayList(bundleCount); |
| long id = -1; |
| boolean bundleDiscarded = false; |
| for (int i = 0; i < bundleCount; i++) { |
| boolean error = false; |
| BaseData data = null; |
| try { |
| id = in.readLong(); |
| if (id != 0) { |
| data = loadBaseData(id, in); |
| data.getBundleFile(); |
| StorageHook[] dataStorageHooks = data.getStorageHooks(); |
| for (int j = 0; j < dataStorageHooks.length; j++) |
| dataStorageHooks[j].validate(); |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("BundleData created: " + data); //$NON-NLS-1$ |
| processExtension(data, EXTENSION_INITIALIZE); |
| result.add(data); |
| } |
| } catch (IllegalArgumentException e) { |
| // may be from data.getBundleFile() |
| bundleDiscarded = true; |
| error = true; |
| } catch (BundleException e) { |
| // should never happen |
| bundleDiscarded = true; |
| error = true; |
| } catch (IOException e) { |
| bundleDiscarded = true; |
| error = true; |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading framework metadata: " + e.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(e); |
| } |
| } |
| if (error && data != null) { |
| BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY); |
| storageHook.delete(true, BaseStorageHook.DEL_BUNDLE_STORE); |
| } |
| } |
| if (bundleDiscarded) |
| FrameworkProperties.setProperty(EclipseStarter.PROP_REFRESH_BUNDLES, "true"); //$NON-NLS-1$ |
| return (BaseData[]) result.toArray(new BaseData[result.size()]); |
| } finally { |
| in.close(); |
| } |
| } catch (IOException e) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading framework metadata: " + e.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(e); |
| } |
| } |
| return null; |
| } |
| |
| private void saveAllData(boolean shutdown) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("Saving framework data ..."); //$NON-NLS-1$ |
| if (storageManagerClosed) |
| try { |
| storageManager.open(!LocationManager.getConfigurationLocation().isReadOnly()); |
| storageManagerClosed = false; |
| } catch (IOException e) { |
| String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, e.getMessage()); |
| FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null); |
| adaptor.getFrameworkLog().log(logEntry); |
| } |
| saveBundleDatas(); |
| saveStateData(shutdown); |
| savePermissionStorage(); |
| if (shutdown) |
| stateManager.stopDataManager(); |
| } |
| |
| private BasePermissionStorage readPermissionData() { |
| BasePermissionStorage result = new BasePermissionStorage(this); |
| InputStream permDataStream = findStorageStream(PERM_DATA_FILE); |
| if (permDataStream == null) |
| return result; |
| try { |
| DataInputStream in = new DataInputStream(new BufferedInputStream(permDataStream)); |
| try { |
| if (PERMDATA_VERSION != in.readByte()) |
| return result; |
| // read the default permissions first |
| int numPerms = in.readInt(); |
| if (numPerms > 0) { |
| String[] perms = new String[numPerms]; |
| for (int i = 0; i < numPerms; i++) |
| perms[i] = in.readUTF(); |
| result.setPermissionData(null, perms); |
| } |
| int numLocs = in.readInt(); |
| if (numLocs > 0) |
| for (int i = 0; i < numLocs; i++) { |
| String loc = in.readUTF(); |
| numPerms = in.readInt(); |
| String[] perms = new String[numPerms]; |
| for (int j = 0; j < numPerms; j++) |
| perms[j] = in.readUTF(); |
| result.setPermissionData(loc, perms); |
| } |
| int numCondPerms = in.readInt(); |
| if (numCondPerms > 0) { |
| String[] condPerms = new String[numCondPerms]; |
| for (int i = 0; i < numCondPerms; i++) |
| condPerms[i] = in.readUTF(); |
| result.saveConditionalPermissionInfos(condPerms); |
| } |
| result.setDirty(false); |
| } finally { |
| in.close(); |
| } |
| } catch (IOException e) { |
| adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e)); |
| } |
| return result; |
| } |
| |
| private void savePermissionStorage() { |
| if (permissionStorage == null || isReadOnly() || !permissionStorage.isDirty()) |
| return; |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("About to save permission data ..."); //$NON-NLS-1$ |
| try { |
| ManagedOutputStream fmos = storageManager.getOutputStream(PERM_DATA_FILE); |
| DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fmos)); |
| boolean error = true; |
| try { |
| out.writeByte(PERMDATA_VERSION); |
| // always write the default permissions first |
| String[] defaultPerms = permissionStorage.getPermissionData(null); |
| out.writeInt(defaultPerms == null ? 0 : defaultPerms.length); |
| if (defaultPerms != null) |
| for (int i = 0; i < defaultPerms.length; i++) |
| out.writeUTF(defaultPerms[i]); |
| String[] locations = permissionStorage.getLocations(); |
| out.writeInt(locations == null ? 0 : locations.length); |
| if (locations != null) |
| for (int i = 0; i < locations.length; i++) { |
| out.writeUTF(locations[i]); |
| String[] perms = permissionStorage.getPermissionData(locations[i]); |
| out.writeInt(perms == null ? 0 : perms.length); |
| if (perms != null) |
| for (int j = 0; j < perms.length; j++) |
| out.writeUTF(perms[j]); |
| } |
| String[] condPerms = permissionStorage.getConditionalPermissionInfos(); |
| out.writeInt(condPerms == null ? 0 : condPerms.length); |
| if (condPerms != null) |
| for (int i = 0; i < condPerms.length; i++) |
| out.writeUTF(condPerms[i]); |
| out.close(); |
| permissionStorage.setDirty(false); |
| error = false; |
| } finally { |
| // if something happens, don't close a corrupt file |
| if (error) { |
| fmos.abort(); |
| try { |
| out.close(); |
| } catch (IOException e) {/*ignore*/ |
| } |
| } |
| } |
| } catch (IOException e) { |
| adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e)); |
| return; |
| } |
| } |
| |
| private void saveBundleDatas() { |
| // the cache and the state match |
| if (stateManager == null || isReadOnly() || (timeStamp == stateManager.getSystemState().getTimeStamp() && !stateManager.saveNeeded())) |
| return; |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("Saving bundle data ..."); //$NON-NLS-1$ |
| try { |
| ManagedOutputStream fmos = storageManager.getOutputStream(LocationManager.BUNDLE_DATA_FILE); |
| DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fmos)); |
| boolean error = true; |
| try { |
| out.writeByte(BUNDLEDATA_VERSION); |
| out.writeLong(stateManager.getSystemState().getTimeStamp()); |
| out.writeInt(initialBundleStartLevel); |
| out.writeLong(nextId); |
| |
| StorageHook[] storageHooks = adaptor.getHookRegistry().getStorageHooks(); |
| out.writeInt(storageHooks.length); |
| for (int i = 0; i < storageHooks.length; i++) { |
| out.writeUTF((String) storageHooks[i].getKey()); |
| out.writeInt(storageHooks[i].getStorageVersion()); |
| } |
| |
| Bundle[] bundles = context.getBundles(); |
| out.writeInt(bundles.length); |
| for (int i = 0; i < bundles.length; i++) { |
| long id = bundles[i].getBundleId(); |
| out.writeLong(id); |
| if (id != 0) { |
| BundleData data = ((org.eclipse.osgi.framework.internal.core.AbstractBundle) bundles[i]).getBundleData(); |
| saveBaseData((BaseData) data, out); |
| } |
| } |
| out.close(); |
| // update the 'timeStamp' after the changed Meta data is saved. |
| timeStamp = stateManager.getSystemState().getTimeStamp(); |
| error = false; |
| } finally { |
| // if something happens, don't close a corrupt file |
| if (error) { |
| fmos.abort(); |
| try { |
| out.close(); |
| } catch (IOException e) {/*ignore*/ |
| } |
| } |
| } |
| } catch (IOException e) { |
| adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e)); |
| return; |
| } |
| } |
| |
| private void saveStateData(boolean shutdown) { |
| if (shutdown && "true".equals(FrameworkProperties.getProperty("osgi.forcedRestart"))) //$NON-NLS-1$ //$NON-NLS-2$ |
| // increment the state timestamp if a forced restart happened. |
| stateManager.getSystemState().setTimeStamp(stateManager.getSystemState().getTimeStamp() + 1); |
| if (stateManager == null || isReadOnly() || !stateManager.saveNeeded()) |
| return; |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("Saving resolver state data ..."); //$NON-NLS-1$ |
| File stateTmpFile = null; |
| File lazyTmpFile = null; |
| try { |
| stateTmpFile = File.createTempFile(LocationManager.STATE_FILE, ".new", LocationManager.getOSGiConfigurationDir()); //$NON-NLS-1$ |
| lazyTmpFile = File.createTempFile(LocationManager.LAZY_FILE, ".new", LocationManager.getOSGiConfigurationDir()); //$NON-NLS-1$ |
| if (shutdown) |
| stateManager.shutdown(stateTmpFile, lazyTmpFile); |
| else |
| synchronized (stateManager) { |
| stateManager.update(stateTmpFile, lazyTmpFile); |
| } |
| storageManager.lookup(LocationManager.STATE_FILE, true); |
| storageManager.lookup(LocationManager.LAZY_FILE, true); |
| storageManager.update(new String[] {LocationManager.STATE_FILE, LocationManager.LAZY_FILE}, new String[] {stateTmpFile.getName(), lazyTmpFile.getName()}); |
| } catch (IOException e) { |
| adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e)); |
| } finally { |
| if (stateTmpFile != null && stateTmpFile.exists()) |
| stateTmpFile.delete(); |
| if (lazyTmpFile != null && lazyTmpFile.exists()) |
| lazyTmpFile.delete(); |
| } |
| } |
| |
| public PermissionStorage getPermissionStorage() throws IOException { |
| if (permissionStorage == null) |
| permissionStorage = readPermissionData(); |
| return permissionStorage; |
| } |
| |
| public int getInitialBundleStartLevel() { |
| return initialBundleStartLevel; |
| } |
| |
| public void setInitialBundleStartLevel(int value) { |
| this.initialBundleStartLevel = value; |
| requestSave(); |
| } |
| |
| public void save(BaseData data) throws IOException { |
| if (data.isDirty()) { |
| timeStamp--; // Change the value of the timeStamp, as a marker that something changed. |
| requestSave(); |
| data.setDirty(false); |
| } |
| } |
| |
| public BundleOperation installBundle(String location, URLConnection source) { |
| BaseData data = createBaseData(getNextBundleId(), location); |
| return new BundleInstall(data, source, this); |
| } |
| |
| public BundleOperation updateBundle(BaseData data, URLConnection source) { |
| return new BundleUpdate(data, source, this); |
| } |
| |
| public BundleOperation uninstallBundle(BaseData data) { |
| return new BundleUninstall(data, this); |
| } |
| |
| protected Object getBundleContent(BaseData bundledata) throws IOException { |
| BaseStorageHook storageHook = (BaseStorageHook) bundledata.getStorageHook(BaseStorageHook.KEY); |
| if (storageHook == null) |
| throw new IllegalStateException(); |
| return storageHook.isReference() ? new File(storageHook.getFileName()) : new File(storageHook.getGenerationDir(), storageHook.getFileName()); |
| } |
| |
| public BundleFile createBundleFile(Object content, BaseData data) throws IOException { |
| boolean base = false; |
| if (content == null) { |
| // this must be a request for the base bundlefile |
| base = true; |
| // get the content of this bundle |
| content = getBundleContent(data); |
| } |
| BundleFile result = null; |
| // Ask factories before doing the default behavior |
| BundleFileFactoryHook[] factories = adaptor.getHookRegistry().getBundleFileFactoryHooks(); |
| for (int i = 0; i < factories.length && result == null; i++) |
| result = factories[i].createBundleFile(content, data, base); |
| |
| // No factories configured or they declined to create the bundle file; do default |
| if (result == null && content instanceof File) { |
| File file = (File) content; |
| if (file.isDirectory()) |
| result = new DirBundleFile(file); |
| else |
| result = new ZipBundleFile(file, data, this.mruList); |
| } |
| |
| if (result == null && content instanceof String) { |
| // here we assume the content is a path offset into the base bundle file; create a NestedDirBundleFile |
| result = new NestedDirBundleFile(data.getBundleFile(), (String) content); |
| } |
| if (result == null) |
| // nothing we can do; must throw exception for the content |
| throw new IOException("Cannot create bundle file for content of type: " + content.getClass().getName()); //$NON-NLS-1$ |
| |
| // try creating a wrapper bundlefile out of it. |
| BundleFileWrapperFactoryHook[] wrapperFactories = adaptor.getHookRegistry().getBundleFileWrapperFactoryHooks(); |
| BundleFileWrapperChain wrapped = wrapperFactories.length == 0 ? null : new BundleFileWrapperChain(result, null); |
| for (int i = 0; i < wrapperFactories.length; i++) { |
| BundleFile wrapperBundle = wrapperFactories[i].wrapBundleFile(result, content, data, base); |
| if (wrapperBundle != null && wrapperBundle != result) |
| result = wrapped = new BundleFileWrapperChain(wrapperBundle, wrapped); |
| } |
| return result; |
| } |
| |
| public synchronized StateManager getStateManager() { |
| if (stateManager != null) |
| return stateManager; |
| stateManager = readStateData(); |
| checkSystemState(stateManager.getSystemState()); |
| return stateManager; |
| } |
| |
| private void checkSystemState(State state) { |
| BundleDescription[] bundles = state.getBundles(); |
| if (bundles == null) |
| return; |
| boolean removedBundle = false; |
| for (int i = 0; i < bundles.length; i++) { |
| if (context.getBundle(bundles[i].getBundleId()) == null) { |
| state.removeBundle(bundles[i]); |
| removedBundle = true; |
| } |
| } |
| if (removedBundle) |
| state.resolve(false); // do a full resolve |
| BundleDescription systemBundle = state.getBundle(0); |
| if (systemBundle == null || !systemBundle.isResolved()) { |
| ResolverError[] errors = systemBundle == null ? new ResolverError[0] : state.getResolverErrors(systemBundle); |
| StringBuffer sb = new StringBuffer(); |
| for (int i = 0; i < errors.length; i++) { |
| sb.append(errors[i].toString()); |
| if (i < errors.length - 1) |
| sb.append(", "); //$NON-NLS-1$ |
| } |
| // this would be a bug in the framework |
| throw new IllegalStateException(NLS.bind(AdaptorMsg.SYSTEMBUNDLE_NOTRESOLVED, sb.toString())); |
| } |
| } |
| |
| private StateManager readStateData() { |
| File[] stateFiles = findStorageFiles(new String[] {LocationManager.STATE_FILE, LocationManager.LAZY_FILE}); |
| File stateFile = stateFiles[0]; |
| File lazyFile = stateFiles[1]; |
| |
| stateManager = new StateManager(stateFile, lazyFile, context, timeStamp); |
| State systemState = null; |
| if (!invalidState) { |
| systemState = stateManager.readSystemState(); |
| if (systemState != null) |
| return stateManager; |
| } |
| systemState = stateManager.createSystemState(); |
| Bundle[] installedBundles = context.getBundles(); |
| if (installedBundles == null) |
| return stateManager; |
| StateObjectFactory factory = stateManager.getFactory(); |
| for (int i = 0; i < installedBundles.length; i++) { |
| AbstractBundle toAdd = (AbstractBundle) installedBundles[i]; |
| try { |
| // make sure we get the real manifest as if this is the first time. |
| Dictionary toAddManifest = loadManifest((BaseData) toAdd.getBundleData(), true); |
| BundleDescription newDescription = factory.createBundleDescription(systemState, toAddManifest, toAdd.getLocation(), toAdd.getBundleId()); |
| systemState.addBundle(newDescription); |
| } catch (BundleException be) { |
| // just ignore bundle datas with invalid manifests |
| } |
| } |
| // we do not set the cached timestamp here because we want a new one to be used from the new system state object (bug 132978) |
| // we need the state resolved |
| systemState.resolve(); |
| invalidState = false; |
| return stateManager; |
| } |
| |
| private File[] findStorageFiles(String[] fileNames) { |
| File[] storageFiles = new File[fileNames.length]; |
| try { |
| for (int i = 0; i < storageFiles.length; i++) |
| storageFiles[i] = storageManager.lookup(fileNames[i], false); |
| } catch (IOException ex) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(ex); |
| } |
| } |
| boolean success = true; |
| for (int i = 0; i < storageFiles.length; i++) |
| if (storageFiles[i] == null || !storageFiles[i].isFile()) { |
| success = false; |
| break; |
| } |
| if (success) |
| return storageFiles; |
| //if it does not exist, try to read it from the parent |
| Location parentConfiguration = null; |
| Location currentConfiguration = LocationManager.getConfigurationLocation(); |
| if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null) { |
| try { |
| File stateLocationDir = new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME); |
| StorageManager newFileManager = initFileManager(stateLocationDir, "none", true); //$NON-NLS-1$); |
| for (int i = 0; i < storageFiles.length; i++) |
| storageFiles[i] = newFileManager.lookup(fileNames[i], false); |
| newFileManager.close(); |
| } catch (IOException ex) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(ex); |
| } |
| } |
| } else { |
| try { |
| //it did not exist in either place, so create it in the original location |
| if (!isReadOnly()) { |
| for (int i = 0; i < storageFiles.length; i++) |
| storageFiles[i] = storageManager.lookup(fileNames[i], true); |
| } |
| } catch (IOException ex) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(ex); |
| } |
| } |
| } |
| return storageFiles; |
| } |
| |
| public void frameworkStart(BundleContext fwContext) throws BundleException { |
| this.context = fwContext; |
| // System property can be set to enable state saver or not. |
| if (Boolean.valueOf(FrameworkProperties.getProperty(BaseStorage.PROP_ENABLE_STATE_SAVER, "true")).booleanValue()) //$NON-NLS-1$ |
| stateSaver = new StateSaver(); |
| |
| } |
| |
| public void frameworkStop(BundleContext fwContext) throws BundleException { |
| if (stateSaver != null) |
| stateSaver.shutdown(); |
| saveAllData(true); |
| storageManager.close(); |
| storageManagerClosed = true; |
| if (extensionListener != null) |
| context.removeBundleListener(extensionListener); |
| mruList.shutdown(); |
| } |
| |
| public void frameworkStopping(BundleContext fwContext) { |
| // do nothing in storage |
| } |
| |
| public void addProperties(Properties properties) { |
| // set the extension support if we found the addURL method |
| if (addFwkURLMethod != null) |
| properties.put(Constants.SUPPORTS_FRAMEWORK_EXTENSION, "true"); //$NON-NLS-1$ |
| // store bundleStore back into adaptor properties for others to see |
| properties.put(BaseStorage.PROP_BUNDLE_STORE, getBundleStoreRoot().getAbsolutePath()); |
| } |
| |
| private InputStream findStorageStream(String fileName) { |
| InputStream storageStream = null; |
| try { |
| storageStream = storageManager.getInputStream(fileName); |
| } catch (IOException ex) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error reading framework metadata: " + ex.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(ex); |
| } |
| } |
| if (storageStream == null) { |
| Location currentConfiguration = LocationManager.getConfigurationLocation(); |
| Location parentConfiguration = null; |
| if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null) { |
| try { |
| File bundledataLocationDir = new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME); |
| StorageManager newStorageManager = initFileManager(bundledataLocationDir, "none", true); //$NON-NLS-1$ |
| storageStream = newStorageManager.getInputStream(fileName); |
| newStorageManager.close(); |
| } catch (MalformedURLException e1) { |
| // This will not happen since all the URLs are derived by us |
| // and we are GODS! |
| } catch (IOException e1) { |
| // That's ok we will regenerate the .bundleData |
| } |
| } |
| } |
| return storageStream; |
| } |
| |
| protected void saveBaseData(BaseData bundledata, DataOutputStream out) throws IOException { |
| StorageHook[] hooks = bundledata.getStorageHooks(); |
| out.writeInt(hooks.length); |
| for (int i = 0; i < hooks.length; i++) { |
| out.writeUTF((String) hooks[i].getKey()); |
| hooks[i].save(out); |
| } |
| } |
| |
| protected BaseData loadBaseData(long id, DataInputStream in) throws IOException { |
| BaseData result = new BaseData(id, adaptor); |
| int numHooks = in.readInt(); |
| StorageHook[] hooks = new StorageHook[numHooks]; |
| for (int i = 0; i < numHooks; i++) { |
| String hookKey = in.readUTF(); |
| StorageHook storageHook = (StorageHook) storageHooks.getByKey(hookKey); |
| if (storageHook == null) |
| throw new IOException(); |
| hooks[i] = storageHook.load(result, in); |
| } |
| result.setStorageHooks(hooks); |
| return result; |
| } |
| |
| protected BaseData createBaseData(long id, String location) { |
| BaseData result = new BaseData(id, adaptor); |
| result.setLocation(location); |
| return result; |
| } |
| |
| public String getInstallPath() { |
| return installPath; |
| } |
| |
| private void cleanOSGiCache() { |
| File osgiConfig = LocationManager.getOSGiConfigurationDir(); |
| if (!AdaptorUtil.rm(osgiConfig)) |
| adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, "The -clean (osgi.clean) option was not successful. Unable to clean the storage area: " + osgiConfig.getAbsolutePath(), 0, null, null)); //$NON-NLS-1$ |
| |
| } |
| |
| /** |
| * Processes an extension bundle |
| * @param bundleData the extension bundle data |
| * @param type the type of extension bundle |
| * @throws BundleException on any errors or if the extension bundle type is not supported |
| */ |
| protected void processExtension(BaseData bundleData, byte type) throws BundleException { |
| if ((bundleData.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) { |
| validateExtension(bundleData); |
| processFrameworkExtension(bundleData, type); |
| } else if ((bundleData.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0) { |
| validateExtension(bundleData); |
| processBootExtension(bundleData, type); |
| } else if ((bundleData.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0) { |
| validateExtension(bundleData); |
| processExtExtension(bundleData, type); |
| } |
| } |
| |
| /** |
| * Validates the extension bundle metadata |
| * @param bundleData the extension bundle data |
| * @throws BundleException if the extension bundle metadata is invalid |
| */ |
| private void validateExtension(BundleData bundleData) throws BundleException { |
| Dictionary extensionManifest = bundleData.getManifest(); |
| if (extensionManifest.get(Constants.IMPORT_PACKAGE) != null) |
| throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_IMPORT_ERROR, bundleData.getLocation()), BundleException.MANIFEST_ERROR); |
| if (extensionManifest.get(Constants.REQUIRE_BUNDLE) != null) |
| throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_REQUIRE_ERROR, bundleData.getLocation()), BundleException.MANIFEST_ERROR); |
| if (extensionManifest.get(Constants.BUNDLE_NATIVECODE) != null) |
| throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_NATIVECODE_ERROR, bundleData.getLocation()), BundleException.MANIFEST_ERROR); |
| } |
| |
| /** |
| * Processes a framework extension bundle |
| * @param bundleData the extension bundle data |
| * @param type the type of extension bundle |
| * @throws BundleException on errors or if framework extensions are not supported |
| */ |
| protected void processFrameworkExtension(BaseData bundleData, byte type) throws BundleException { |
| if (addFwkURLMethod == null) |
| throw new BundleException("Framework extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$ |
| addExtensionContent(bundleData, type, getFwkClassLoader(), addFwkURLMethod); |
| } |
| |
| protected void processExtExtension(BaseData bundleData, byte type) throws BundleException { |
| if (addExtURLMethod == null) |
| throw new BundleException("Extension classpath extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$ |
| addExtensionContent(bundleData, type, getExtClassLoader(), addExtURLMethod); |
| } |
| |
| private void addExtensionContent(BaseData bundleData, byte type, ClassLoader addToLoader, Method addToMethod) throws BundleException { |
| if ((type & (EXTENSION_UNINSTALLED | EXTENSION_UPDATED)) != 0) |
| // if uninstalled or updated then do nothing framework must be restarted. |
| return; |
| |
| // first make sure this BundleData is not on the pre-configured osgi.framework.extensions list |
| String[] extensions = getConfiguredExtensions(); |
| for (int i = 0; i < extensions.length; i++) |
| if (extensions[i].equals(bundleData.getSymbolicName())) |
| return; |
| if ((type & EXTENSION_INSTALLED) != 0) { |
| if (extensionListener == null) { |
| // add bundle listener to wait for extension to be resolved |
| extensionListener = this; |
| context.addBundleListener(extensionListener); |
| } |
| return; |
| } |
| File[] files = getExtensionFiles(bundleData); |
| if (files == null) |
| return; |
| |
| for (int i = 0; i < files.length; i++) { |
| if (files[i] == null) |
| continue; |
| try { |
| callAddURLMethod(addToLoader, addToMethod, AdaptorUtil.encodeFileURL(files[i])); |
| } catch (InvocationTargetException e) { |
| adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e); |
| } catch (MalformedURLException e) { |
| adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e); |
| } |
| } |
| |
| try { |
| addToLoader.loadClass("thisIsNotAClass"); // initialize the new urls //$NON-NLS-1$ |
| } catch (ClassNotFoundException e) { |
| // do nothing |
| } |
| } |
| |
| /** |
| * Returns a list of configured extensions |
| * @return a list of configured extensions |
| */ |
| protected String[] getConfiguredExtensions() { |
| if (configuredExtensions != null) |
| return configuredExtensions; |
| String prop = FrameworkProperties.getProperty(BaseStorage.PROP_FRAMEWORK_EXTENSIONS); |
| if (prop == null || prop.trim().length() == 0) |
| configuredExtensions = new String[0]; |
| else |
| configuredExtensions = ManifestElement.getArrayFromList(prop); |
| return configuredExtensions; |
| } |
| |
| /** |
| * Processes a boot extension bundle |
| * @param bundleData the extension bundle data |
| * @param type the type of extension bundle |
| * @throws BundleException on errors or if boot extensions are not supported |
| */ |
| protected void processBootExtension(BundleData bundleData, byte type) throws BundleException { |
| throw new BundleException("Boot classpath extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$ |
| } |
| |
| private void initBundleStoreRoot() { |
| File configurationLocation = LocationManager.getOSGiConfigurationDir(); |
| if (configurationLocation != null) |
| bundleStoreRoot = new File(configurationLocation, LocationManager.BUNDLES_DIR); |
| else |
| // last resort just default to "bundles" |
| bundleStoreRoot = new File(LocationManager.BUNDLES_DIR); |
| } |
| |
| public File getBundleStoreRoot() { |
| if (bundleStoreRoot == null) |
| initBundleStoreRoot(); |
| return bundleStoreRoot; |
| } |
| |
| /** |
| * Returns a list of classpath files for an extension bundle |
| * @param bundleData the bundle data for an extension bundle |
| * @return a list of classpath files for an extension bundle |
| */ |
| protected File[] getExtensionFiles(BaseData bundleData) { |
| File[] files = null; |
| try { |
| String[] paths = bundleData.getClassPath(); |
| if (DevClassPathHelper.inDevelopmentMode()) { |
| String[] devPaths = DevClassPathHelper.getDevClassPath(bundleData.getSymbolicName()); |
| String[] origPaths = paths; |
| |
| paths = new String[origPaths.length + devPaths.length]; |
| System.arraycopy(origPaths, 0, paths, 0, origPaths.length); |
| System.arraycopy(devPaths, 0, paths, origPaths.length, devPaths.length); |
| } |
| ArrayList results = new ArrayList(paths.length); |
| for (int i = 0; i < paths.length; i++) { |
| if (".".equals(paths[i])) //$NON-NLS-1$ |
| results.add(bundleData.getBundleFile().getBaseFile()); |
| else { |
| File result = bundleData.getBundleFile().getFile(paths[i], false); |
| if (result != null) |
| results.add(result); |
| } |
| } |
| return (File[]) results.toArray(new File[results.size()]); |
| } catch (BundleException e) { |
| adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e); |
| } |
| return files; |
| } |
| |
| void requestSave() { |
| // Only when the State saver is enabled will the stateSaver be started. |
| if (stateSaver == null) |
| return; |
| stateSaver.requestSave(); |
| } |
| |
| /** |
| * Updates the state mananager with an updated/installed/uninstalled bundle |
| * @param bundleData the modified bundle |
| * @param type the type of modification |
| * @throws BundleException |
| */ |
| public void updateState(BundleData bundleData, int type) throws BundleException { |
| if (stateManager == null) { |
| invalidState = true; |
| return; |
| } |
| State systemState = stateManager.getSystemState(); |
| BundleDescription oldDescription = null; |
| BundleDescription newDescription = null; |
| switch (type) { |
| case BundleEvent.UPDATED : |
| // fall through to INSTALLED |
| case BundleEvent.INSTALLED : |
| if (type == BundleEvent.UPDATED) |
| oldDescription = systemState.getBundle(bundleData.getBundleID()); |
| newDescription = stateManager.getFactory().createBundleDescription(systemState, bundleData.getManifest(), bundleData.getLocation(), bundleData.getBundleID()); |
| if (oldDescription == null) |
| systemState.addBundle(newDescription); |
| else |
| systemState.updateBundle(newDescription); |
| break; |
| case BundleEvent.UNINSTALLED : |
| systemState.removeBundle(bundleData.getBundleID()); |
| break; |
| } |
| |
| if (newDescription != null) |
| validateNativeCodePaths(newDescription, (BaseData) bundleData); |
| } |
| |
| private void validateNativeCodePaths(BundleDescription newDescription, BaseData data) { |
| NativeCodeSpecification nativeCode = newDescription.getNativeCodeSpecification(); |
| if (nativeCode == null) |
| return; |
| NativeCodeDescription nativeCodeDescs[] = nativeCode.getPossibleSuppliers(); |
| for (int i = 0; i < nativeCodeDescs.length; i++) { |
| BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY); |
| if (storageHook != null) |
| try { |
| storageHook.validateNativePaths(nativeCodeDescs[i].getNativePaths()); |
| } catch (BundleException e) { |
| stateManager.getSystemState().setNativePathsInvalid(nativeCodeDescs[i], true); |
| } |
| } |
| } |
| |
| private class StateSaver implements Runnable { |
| private final long delay_interval; |
| private final long max_total_delay_interval; |
| private boolean shutdown = false; |
| private long lastSaveTime = 0; |
| private Thread runningThread = null; |
| private Thread shutdownHook = null; |
| |
| StateSaver() { |
| String prop = FrameworkProperties.getProperty("eclipse.stateSaveDelayInterval"); //$NON-NLS-1$ |
| long delayValue = 30000; // 30 seconds. |
| long maxDelayValue = 1800000; // 30 minutes. |
| if (prop != null) { |
| try { |
| long val = Long.parseLong(prop); |
| if (val >= 1000 && val <= 1800000) { |
| delayValue = val; |
| maxDelayValue = val * 60; |
| } else if (val == 0) { |
| delayValue = 0; |
| maxDelayValue = 0; |
| } |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| delay_interval = delayValue; |
| max_total_delay_interval = maxDelayValue; |
| } |
| |
| public void run() { |
| State systemState = adaptor.getState(); |
| synchronized (systemState) { |
| long firstSaveTime = lastSaveTime; |
| long curSaveTime = 0; |
| long delayTime; |
| do { |
| do { |
| if ((System.currentTimeMillis() - firstSaveTime) > max_total_delay_interval) { |
| curSaveTime = lastSaveTime; |
| // Waiting time has been too long, so break to start saving State data to file. |
| break; |
| } |
| delayTime = Math.min(delay_interval, lastSaveTime - curSaveTime); |
| curSaveTime = lastSaveTime; |
| // wait for other save requests |
| try { |
| if (!shutdown) |
| systemState.wait(delayTime); |
| } catch (InterruptedException ie) { |
| // force break from do/while loops |
| curSaveTime = lastSaveTime; |
| break; |
| } |
| // Continue the loop if 'lastSaveTime' is increased again during waiting. |
| } while (!shutdown && curSaveTime < lastSaveTime); |
| // Save State and Meta data. |
| saveAllData(false); |
| // Continue the loop if Saver is asked again during saving State data to file. |
| } while (!shutdown && curSaveTime < lastSaveTime); |
| runningThread = null; // clear runningThread |
| try { |
| Runtime.getRuntime().removeShutdownHook(shutdownHook); |
| } catch (IllegalStateException e) { |
| // avoid exception if shutdown is in progress |
| } |
| shutdownHook = null; |
| } |
| } |
| |
| void shutdown() { |
| State systemState = adaptor.getState(); |
| Thread joinWith = null; |
| synchronized (systemState) { |
| shutdown = true; |
| joinWith = runningThread; |
| systemState.notifyAll(); // To wakeup sleeping thread. |
| } |
| try { |
| if (joinWith != null) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("About to join saving thread"); //$NON-NLS-1$ |
| // There should be no deadlock when 'shutdown' is true. |
| joinWith.join(); |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) |
| Debug.println("Joined with saving thread"); //$NON-NLS-1$ |
| } |
| } catch (InterruptedException ie) { |
| if (Debug.DEBUG && Debug.DEBUG_GENERAL) { |
| Debug.println("Error shutdowning StateSaver: " + ie.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(ie); |
| } |
| } |
| } |
| |
| void requestSave() { |
| final State systemState = adaptor.getState(); |
| synchronized (systemState) { |
| if (shutdown) |
| return; // do not start another thread if we have already shutdown |
| if (delay_interval == 0) // all saves are atomic |
| saveAllData(false); |
| lastSaveTime = System.currentTimeMillis(); |
| if (runningThread == null) { |
| shutdownHook = new Thread(new Runnable() { |
| public void run() { |
| // Synchronize with JVM shutdown hook, because |
| // saveAllData creates a temp file with delete on |
| // exit is true. The temp file will be removed in the |
| // shutdown hook. This prevents that the remove temp files |
| // in the shutdown hook is earlier handled then adding new |
| // temp file in saveAllData. |
| shutdown(); |
| } |
| }); |
| runningThread = new Thread(this, "State Saver"); //$NON-NLS-1$ |
| runningThread.start(); |
| Runtime.getRuntime().addShutdownHook(shutdownHook); |
| } |
| } |
| } |
| } |
| |
| public long getNextBundleId() { |
| synchronized (this.nextIdMonitor) { |
| return nextId++; |
| } |
| } |
| |
| public void bundleChanged(BundleEvent event) { |
| if (event.getType() != BundleEvent.RESOLVED) |
| return; |
| BaseData data = (BaseData) ((AbstractBundle) event.getBundle()).getBundleData(); |
| try { |
| if ((data.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) |
| processFrameworkExtension(data, EXTENSION_INITIALIZE); |
| else if ((data.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0) |
| processBootExtension(data, EXTENSION_INITIALIZE); |
| else if ((data.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0) |
| processExtExtension(data, EXTENSION_INITIALIZE); |
| } catch (BundleException e) { |
| // do nothing; |
| } |
| } |
| |
| public String copyToTempLibrary(BaseData data, String absolutePath) throws IOException { |
| File storageRoot = getBundleStoreRoot(); |
| File libTempDir = new File(storageRoot, LIB_TEMP); |
| // we assume the absolutePath is a File path |
| File realLib = new File(absolutePath); |
| String libName = realLib.getName(); |
| // find a temp dir for the bundle data and the library; |
| File bundleTempDir = null; |
| File libTempFile = null; |
| // We need a somewhat predictable temp dir for the libraries of a given bundle; |
| // This is not strictly necessary but it does help scenarios where one native library loads another native library without using java. |
| // On some OSes this causes issues because the second library is cannot be found. |
| // This has been worked around by the bundles loading the libraries in a particular order (and setting some LIB_PATH env). |
| // The one catch is that the libraries need to be in the same directory and they must use their original lib names. |
| // |
| // This bit of code attempts to do that by using the bundle ID as an ID for the temp dir along with an incrementing ID |
| // in cases where the temp dir may already exist. |
| Long bundleID = new Long(data.getBundleID()); |
| for (int i = 0; i < Integer.MAX_VALUE; i++) { |
| bundleTempDir = new File(libTempDir, bundleID.toString() + "_" + new Integer(i).toString()); //$NON-NLS-1$ |
| libTempFile = new File(bundleTempDir, libName); |
| if (bundleTempDir.exists()) { |
| if (libTempFile.exists()) |
| continue; // to to next temp file |
| break; |
| } |
| break; |
| } |
| if (!bundleTempDir.exists()) { |
| bundleTempDir.mkdirs(); |
| bundleTempDir.deleteOnExit(); |
| // This is just a safeguard incase the VM is terminated unexpectantly, it also looks like deleteOnExit cannot really work because |
| // the VM likely will still have a lock on the lib file at the time of VM exit. |
| File deleteFlag = new File(libTempDir, BaseStorage.DELETE_FLAG); |
| if (!deleteFlag.exists()) { |
| // need to create a delete flag to force removal the temp libraries |
| try { |
| FileOutputStream out = new FileOutputStream(deleteFlag); |
| out.close(); |
| } catch (IOException e) { |
| // do nothing; that would mean we did not make the temp dir successfully |
| } |
| } |
| } |
| // copy the library file |
| InputStream in = new FileInputStream(realLib); |
| AdaptorUtil.readFile(in, libTempFile); |
| // set permissions if needed |
| BundleFile.setPermissions(libTempFile); |
| libTempFile.deleteOnExit(); // this probably will not work because the VM will probably have the lib locked at exit |
| // return the temporary path |
| return libTempFile.getAbsolutePath(); |
| } |
| |
| } |