Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Kaegi2008-02-18 21:51:26 +0000
committerSimon Kaegi2008-02-18 21:51:26 +0000
commitfc508e5e5bfe9af2d702cbd8dddba0221bfd3cb7 (patch)
treef6f8b6509994c182728e0067ac6d4dbb81ede08a /bundles/org.eclipse.equinox.cm/src
parent47234b69b53dfbae8b8d81463d40d54a200c6405 (diff)
downloadrt.equinox.bundles-fc508e5e5bfe9af2d702cbd8dddba0221bfd3cb7.tar.gz
rt.equinox.bundles-fc508e5e5bfe9af2d702cbd8dddba0221bfd3cb7.tar.xz
rt.equinox.bundles-fc508e5e5bfe9af2d702cbd8dddba0221bfd3cb7.zip
Bug 218711 [cm] move code to graduated project in CVSv20080226
Diffstat (limited to 'bundles/org.eclipse.equinox.cm/src')
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/Activator.java80
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java108
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java75
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java151
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationEventAdapter.java99
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java286
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java202
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/EventDispatcher.java88
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/LogTracker.java169
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java188
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java179
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java108
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/SerializedTaskQueue.java61
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFile.java840
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileInputStream.java211
-rw-r--r--bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileOutputStream.java184
16 files changed, 3029 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/Activator.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/Activator.java
new file mode 100644
index 00000000..d09e892c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/Activator.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 Cognos Incorporated, IBM Corporation
+ * 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:
+ * Cognos Incorporated - initial API and implementation
+ * Chris Aniszczyk <zx@us.ibm.com> - bug 209294
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import org.osgi.framework.*;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * Activator start the ConfigurationAdminFactory but also handles passing in the Service
+ * Registration needed by Asynch threads. Asynch threads are controlled by ConfigurationAdminFactory
+ * start and stop. It requires some care to handle pending events as the service is registered before
+ * activating the threads. (see EventDispatcher)
+ */
+public class Activator implements BundleActivator {
+ private static final String EVENT_ADMIN_CLASS = "org.osgi.service.event.EventAdmin"; //$NON-NLS-1$
+ private LogTracker logTracker;
+ private ServiceRegistration registration;
+ private ConfigurationAdminFactory factory;
+ private ConfigurationEventAdapter eventAdapter;
+ private static BundleContext bundleContext;
+
+ private static synchronized void setBundleContext(BundleContext context) {
+ bundleContext = context;
+ }
+
+ public static synchronized String getProperty(String key) {
+ if (bundleContext != null)
+ return bundleContext.getProperty(key);
+
+ return null;
+ }
+
+ public void start(BundleContext context) throws Exception {
+ setBundleContext(context);
+ logTracker = new LogTracker(context, System.err);
+ logTracker.open();
+ if (checkEventAdmin()) {
+ eventAdapter = new ConfigurationEventAdapter(context);
+ eventAdapter.start();
+ }
+ factory = new ConfigurationAdminFactory(context, logTracker);
+ factory.start();
+ context.addBundleListener(factory);
+ registration = context.registerService(ConfigurationAdmin.class.getName(), factory, null);
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ registration.unregister();
+ registration = null;
+ context.removeBundleListener(factory);
+ factory.stop();
+ factory = null;
+ if (eventAdapter != null) {
+ eventAdapter.stop();
+ eventAdapter = null;
+ }
+ logTracker.close();
+ logTracker = null;
+ setBundleContext(null);
+ }
+
+ private static boolean checkEventAdmin() {
+ // cannot support scheduling without the event admin package
+ try {
+ Class.forName(EVENT_ADMIN_CLASS);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java
new file mode 100644
index 00000000..2bd87c90
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.security.Permission;
+import java.util.Dictionary;
+import org.osgi.framework.*;
+import org.osgi.service.cm.ConfigurationPermission;
+import org.osgi.service.log.LogService;
+
+/**
+ * ConfigurationAdminFactory provides a Configuration Admin ServiceFactory but more significantly
+ * launches the whole implementation.
+ */
+
+public class ConfigurationAdminFactory implements ServiceFactory, BundleListener {
+
+ private final Permission configurationPermission = new ConfigurationPermission("*", ConfigurationPermission.CONFIGURE); //$NON-NLS-1$
+ private final EventDispatcher eventDispatcher;
+ private final PluginManager pluginManager;
+ private final LogService log;
+ private final ManagedServiceTracker managedServiceTracker;
+ private final ManagedServiceFactoryTracker managedServiceFactoryTracker;
+ private final ConfigurationStore configurationStore;
+
+ public ConfigurationAdminFactory(BundleContext context, LogService log) {
+ this.log = log;
+ configurationStore = new ConfigurationStore(this, context);
+ eventDispatcher = new EventDispatcher(context, log);
+ pluginManager = new PluginManager(context);
+ managedServiceTracker = new ManagedServiceTracker(this, configurationStore, context);
+ managedServiceFactoryTracker = new ManagedServiceFactoryTracker(this, configurationStore, context);
+ }
+
+ void start() {
+ eventDispatcher.start();
+ pluginManager.start();
+ managedServiceTracker.open();
+ managedServiceFactoryTracker.open();
+ }
+
+ void stop() {
+ managedServiceTracker.close();
+ managedServiceFactoryTracker.close();
+ eventDispatcher.stop();
+ pluginManager.stop();
+ }
+
+ public Object getService(Bundle bundle, ServiceRegistration registration) {
+ ServiceReference reference = registration.getReference();
+ eventDispatcher.setServiceReference(reference);
+ return new ConfigurationAdminImpl(this, configurationStore, bundle);
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
+ // do nothing
+ }
+
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == BundleEvent.UNINSTALLED)
+ configurationStore.unbindConfigurations(event.getBundle());
+ }
+
+ public void checkConfigurationPermission() throws SecurityException {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(configurationPermission);
+ }
+
+ void log(int level, String message) {
+ log.log(level, message);
+ }
+
+ void log(int level, String message, Throwable exception) {
+ log.log(level, message, exception);
+ }
+
+ void dispatchEvent(int type, String factoryPid, String pid) {
+ eventDispatcher.dispatchEvent(type, factoryPid, pid);
+ }
+
+ void notifyConfigurationUpdated(ConfigurationImpl config, boolean isFactory) {
+ if (isFactory)
+ managedServiceFactoryTracker.notifyUpdated(config);
+ else
+ managedServiceTracker.notifyUpdated(config);
+ }
+
+ void notifyConfigurationDeleted(ConfigurationImpl config, boolean isFactory) {
+ if (isFactory)
+ managedServiceFactoryTracker.notifyDeleted(config);
+ else
+ managedServiceTracker.notifyDeleted(config);
+ }
+
+ void modifyConfiguration(ServiceReference reference, Dictionary properties) {
+ pluginManager.modifyConfiguration(reference, properties);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java
new file mode 100644
index 00000000..56ffebd7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.io.IOException;
+import org.osgi.framework.*;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * ConfigurationAdminImpl provides the ConfigurationAdmin service implementation
+ */
+class ConfigurationAdminImpl implements ConfigurationAdmin {
+
+ private final ConfigurationAdminFactory configurationAdminFactory;
+ private final Bundle bundle;
+ private final ConfigurationStore configurationStore;
+
+ public ConfigurationAdminImpl(ConfigurationAdminFactory configurationAdminFactory, ConfigurationStore configurationStore, Bundle bundle) {
+ this.configurationAdminFactory = configurationAdminFactory;
+ this.configurationStore = configurationStore;
+ this.bundle = bundle;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
+ checkPID(factoryPid);
+ return configurationStore.createFactoryConfiguration(factoryPid, bundle.getLocation());
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
+ checkPID(factoryPid);
+ this.configurationAdminFactory.checkConfigurationPermission();
+ return configurationStore.createFactoryConfiguration(factoryPid, location);
+ }
+
+ public Configuration getConfiguration(String pid) throws IOException {
+ checkPID(pid);
+ Configuration config = configurationStore.getConfiguration(pid, bundle.getLocation());
+ if (config.getBundleLocation() != null && !config.getBundleLocation().equals(bundle.getLocation()))
+ this.configurationAdminFactory.checkConfigurationPermission();
+ return config;
+ }
+
+ public Configuration getConfiguration(String pid, String location) throws IOException {
+ checkPID(pid);
+ this.configurationAdminFactory.checkConfigurationPermission();
+ return configurationStore.getConfiguration(pid, location);
+ }
+
+ public Configuration[] listConfigurations(String filterString) throws IOException, InvalidSyntaxException {
+ if (filterString == null)
+ filterString = "(" + Constants.SERVICE_PID + "=*)"; //$NON-NLS-1$ //$NON-NLS-2$
+
+ try {
+ this.configurationAdminFactory.checkConfigurationPermission();
+ } catch (SecurityException e) {
+ filterString = "(&(" + ConfigurationAdmin.SERVICE_BUNDLELOCATION + "=" + bundle.getLocation() + ")" + filterString + ")"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$
+ }
+ return configurationStore.listConfigurations(FrameworkUtil.createFilter(filterString));
+ }
+
+ private void checkPID(String pid) {
+ if (pid == null)
+ throw new IllegalArgumentException("PID cannot be null"); //$NON-NLS-1$
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java
new file mode 100644
index 00000000..51e8c4bd
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Vector;
+import java.util.Map.Entry;
+
+/**
+ * ConfigurationDictionary holds the actual configuration data and meets the various comparison
+ * requirements of the Configuration Admin Service specification.
+ */
+
+public class ConfigurationDictionary extends Dictionary implements Serializable {
+
+ private static final long serialVersionUID = -3583299578203095532L;
+ private static final Collection simples = Arrays.asList(new Class[] {String.class, Integer.class, Long.class, Float.class, Double.class, Byte.class, Short.class, Character.class, Boolean.class});
+ private static final Collection simpleArrays = Arrays.asList(new Class[] {String[].class, Integer[].class, Long[].class, Float[].class, Double[].class, Byte[].class, Short[].class, Character[].class, Boolean[].class});
+ private static final Collection primitiveArrays = Arrays.asList(new Class[] {long[].class, int[].class, short[].class, char[].class, byte[].class, double[].class, float[].class, boolean[].class});
+
+ static class CaseInsensitiveStringComparator implements Comparator, Serializable {
+ private static final long serialVersionUID = 6501536810492374044L;
+
+ public int compare(Object o1, Object o2) {
+ return ((String) o1).compareToIgnoreCase((String) o2);
+ }
+ }
+
+ protected final Map configurationProperties = Collections.synchronizedMap(new TreeMap(new CaseInsensitiveStringComparator()));
+
+ private static void validateValue(Object value) {
+ Class clazz = value.getClass();
+
+ // Is it in the set of simple types
+ if (simples.contains(clazz))
+ return;
+
+ // Is it an array of primitives or simples
+ if (simpleArrays.contains(clazz) || primitiveArrays.contains(clazz))
+ return;
+
+ // Is it a vector of simples
+ if (clazz == Vector.class) {
+ Vector valueVector = (Vector) value;
+ for (Iterator it = valueVector.iterator(); it.hasNext();) {
+ Class containedClazz = it.next().getClass();
+ if (!simples.contains(containedClazz)) {
+ throw new IllegalArgumentException(containedClazz.getName() + " in " + Vector.class.getName()); //$NON-NLS-1$
+ }
+ }
+ return;
+ }
+ throw new IllegalArgumentException(clazz.getName());
+ }
+
+ public Enumeration elements() {
+ return new Enumeration() {
+ final Iterator valuesIterator = configurationProperties.values().iterator();
+
+ public boolean hasMoreElements() {
+ return valuesIterator.hasNext();
+ }
+
+ public Object nextElement() {
+ return valuesIterator.next();
+ }
+ };
+ }
+
+ public Object get(Object key) {
+ if (key == null)
+ throw new NullPointerException();
+ return configurationProperties.get(key);
+ }
+
+ public boolean isEmpty() {
+ return configurationProperties.isEmpty();
+ }
+
+ public Enumeration keys() {
+ return new Enumeration() {
+ Iterator keysIterator = configurationProperties.keySet().iterator();
+
+ public boolean hasMoreElements() {
+ return keysIterator.hasNext();
+ }
+
+ public Object nextElement() {
+ return keysIterator.next();
+ }
+ };
+ }
+
+ public Object put(Object key, Object value) {
+ if (key == null || value == null)
+ throw new NullPointerException();
+
+ // Will throw an illegal argument exception if not a valid configuration property type
+ validateValue(value);
+
+ return configurationProperties.put(key, value);
+ }
+
+ public Object remove(Object key) {
+ if (key == null)
+ throw new NullPointerException();
+ return configurationProperties.remove(key);
+ }
+
+ public int size() {
+ return configurationProperties.size();
+ }
+
+ ConfigurationDictionary copy() {
+ ConfigurationDictionary result = new ConfigurationDictionary();
+ for (Iterator it = configurationProperties.entrySet().iterator(); it.hasNext();) {
+ Entry entry = (Entry) it.next();
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ if (value.getClass().isArray()) {
+ int arrayLength = Array.getLength(value);
+ Object copyOfArray = Array.newInstance(value.getClass().getComponentType(), arrayLength);
+ System.arraycopy(value, 0, copyOfArray, 0, arrayLength);
+ result.configurationProperties.put(key, copyOfArray);
+ } else if (value instanceof Vector)
+ result.configurationProperties.put(key, ((Vector) value).clone());
+ else
+ result.configurationProperties.put(key, value);
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationEventAdapter.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationEventAdapter.java
new file mode 100644
index 00000000..a4a43804
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationEventAdapter.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation.
+ * 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
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.util.Hashtable;
+import org.osgi.framework.*;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ConfigurationEventAdapter implements ConfigurationListener {
+ // constants for Event topic substring
+ public static final String TOPIC = "org/osgi/service/cm/ConfigurationEvent"; //$NON-NLS-1$
+ public static final char TOPIC_SEPARATOR = '/';
+ // constants for Event types
+ public static final String CM_UPDATED = "CM_UPDATED"; //$NON-NLS-1$
+ public static final String CM_DELETED = "CM_DELETED"; //$NON-NLS-1$
+ // constants for Event properties
+ public static final String CM_FACTORY_PID = "cm.factoryPid"; //$NON-NLS-1$
+ public static final String CM_PID = "cm.pid"; //$NON-NLS-1$
+ public static final String SERVICE = "service"; //$NON-NLS-1$
+ public static final String SERVICE_ID = "service.id"; //$NON-NLS-1$
+ public static final String SERVICE_OBJECTCLASS = "service.objectClass"; //$NON-NLS-1$
+ public static final String SERVICE_PID = "service.pid"; //$NON-NLS-1$
+
+ private final BundleContext context;
+ private ServiceRegistration configListenerRegistration;
+ private final ServiceTracker eventAdminTracker;
+
+ public ConfigurationEventAdapter(BundleContext context) {
+ this.context = context;
+ eventAdminTracker = new ServiceTracker(context, EventAdmin.class.getName(), null);
+ }
+
+ public void start() throws Exception {
+ eventAdminTracker.open();
+ configListenerRegistration = context.registerService(ConfigurationListener.class.getName(), this, null);
+ }
+
+ public void stop() throws Exception {
+ configListenerRegistration.unregister();
+ configListenerRegistration = null;
+ eventAdminTracker.close();
+ }
+
+ public void configurationEvent(ConfigurationEvent event) {
+ EventAdmin eventAdmin = (EventAdmin) eventAdminTracker.getService();
+ if (eventAdmin == null) {
+ return;
+ }
+ String typename = null;
+ switch (event.getType()) {
+ case ConfigurationEvent.CM_UPDATED :
+ typename = CM_UPDATED;
+ break;
+ case ConfigurationEvent.CM_DELETED :
+ typename = CM_DELETED;
+ break;
+ default : // do nothing
+ return;
+ }
+ String topic = TOPIC + TOPIC_SEPARATOR + typename;
+ ServiceReference ref = event.getReference();
+ if (ref == null) {
+ throw new RuntimeException("ServiceEvent.getServiceReference() is null"); //$NON-NLS-1$
+ }
+ Hashtable properties = new Hashtable();
+ properties.put(CM_PID, event.getPid());
+ if (event.getFactoryPid() != null) {
+ properties.put(CM_FACTORY_PID, event.getFactoryPid());
+ }
+ putServiceReferenceProperties(properties, ref);
+ Event convertedEvent = new Event(topic, properties);
+ eventAdmin.postEvent(convertedEvent);
+ }
+
+ public void putServiceReferenceProperties(Hashtable properties, ServiceReference ref) {
+ properties.put(SERVICE, ref);
+ properties.put(SERVICE_ID, ref.getProperty(org.osgi.framework.Constants.SERVICE_ID));
+ Object o = ref.getProperty(org.osgi.framework.Constants.SERVICE_PID);
+ if ((o != null) && (o instanceof String)) {
+ properties.put(SERVICE_PID, o);
+ }
+ Object o2 = ref.getProperty(org.osgi.framework.Constants.OBJECTCLASS);
+ if ((o2 != null) && (o2 instanceof String[])) {
+ properties.put(SERVICE_OBJECTCLASS, o2);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java
new file mode 100644
index 00000000..99e68ac6
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java
@@ -0,0 +1,286 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.*;
+
+/**
+ * ConfigurationImpl provides the Configuration implementation.
+ * The lock and unlock methods are used for synchronization. Operations outside of
+ * ConfigurationImpl that expect to have control of the lock should call checkLocked
+ */
+class ConfigurationImpl implements Configuration {
+
+ private final ConfigurationAdminFactory configurationAdminFactory;
+ private final ConfigurationStore configurationStore;
+ /** @GuardedBy this*/
+ private String bundleLocation;
+ private final String factoryPid;
+ private final String pid;
+ private ConfigurationDictionary dictionary;
+ /** @GuardedBy this*/
+ private boolean deleted = false;
+ /** @GuardedBy this*/
+ private Bundle boundBundle;
+ /** @GuardedBy this*/
+ private int lockedCount = 0;
+ /** @GuardedBy this*/
+ private Thread lockHolder = null;
+
+ public ConfigurationImpl(ConfigurationAdminFactory configurationAdminFactory, ConfigurationStore configurationStore, String factoryPid, String pid, String bundleLocation) {
+ this.configurationAdminFactory = configurationAdminFactory;
+ this.configurationStore = configurationStore;
+ this.factoryPid = factoryPid;
+ this.pid = pid;
+ this.bundleLocation = bundleLocation;
+ }
+
+ public ConfigurationImpl(ConfigurationAdminFactory configurationAdminFactory, ConfigurationStore configurationStore, Dictionary dictionary) {
+ this.configurationAdminFactory = configurationAdminFactory;
+ this.configurationStore = configurationStore;
+ pid = (String) dictionary.get(Constants.SERVICE_PID);
+ factoryPid = (String) dictionary.get(ConfigurationAdmin.SERVICE_FACTORYPID);
+ bundleLocation = (String) dictionary.get(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
+ updateDictionary(dictionary);
+ }
+
+ protected synchronized void lock() {
+ Thread current = Thread.currentThread();
+ if (lockHolder != current) {
+ boolean interrupted = false;
+ try {
+ while (lockedCount != 0)
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // although we don't handle an interrupt we should still
+ // save and restore the interrupt for others further up the stack
+ interrupted = true;
+ }
+ } finally {
+ if (interrupted)
+ current.interrupt(); // restore interrupted status
+ }
+ }
+ lockedCount++;
+ lockHolder = current;
+ }
+
+ protected synchronized void unlock() {
+ Thread current = Thread.currentThread();
+ if (lockHolder != current)
+ throw new IllegalStateException("Thread not lock owner"); //$NON-NLS-1$
+
+ lockedCount--;
+ if (lockedCount == 0) {
+ lockHolder = null;
+ notify();
+ }
+ }
+
+ protected synchronized void checkLocked() {
+ Thread current = Thread.currentThread();
+ if (lockHolder != current)
+ throw new IllegalStateException("Thread not lock owner"); //$NON-NLS-1$
+ }
+
+ protected boolean bind(Bundle bundle) {
+ try {
+ lock();
+ if (boundBundle == null && (bundleLocation == null || bundleLocation.equals(bundle.getLocation())))
+ boundBundle = bundle;
+ return (boundBundle == bundle);
+ } finally {
+ unlock();
+ }
+ }
+
+ protected void unbind(Bundle bundle) {
+ try {
+ lock();
+ if (boundBundle == bundle)
+ boundBundle = null;
+ } finally {
+ unlock();
+ }
+ }
+
+ public void delete() throws IOException {
+ try {
+ lock();
+ checkDeleted();
+ deleted = true;
+ configurationAdminFactory.notifyConfigurationDeleted(this, factoryPid != null);
+ configurationAdminFactory.dispatchEvent(ConfigurationEvent.CM_DELETED, factoryPid, pid);
+ } finally {
+ unlock();
+ }
+ configurationStore.removeConfiguration(pid);
+ }
+
+ private void checkDeleted() {
+ if (deleted)
+ throw new IllegalStateException("deleted"); //$NON-NLS-1$
+ }
+
+ public String getBundleLocation() {
+ try {
+ lock();
+ checkDeleted();
+ if (bundleLocation != null)
+ return bundleLocation;
+ if (boundBundle != null)
+ return boundBundle.getLocation();
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ protected String getFactoryPid(boolean checkDeleted) {
+ try {
+ lock();
+ if (checkDeleted)
+ checkDeleted();
+ return factoryPid;
+ } finally {
+ unlock();
+ }
+ }
+
+ public String getFactoryPid() {
+ return getFactoryPid(true);
+ }
+
+ protected String getPid(boolean checkDeleted) {
+ try {
+ lock();
+ if (checkDeleted)
+ checkDeleted();
+ return pid;
+ } finally {
+ unlock();
+ }
+ }
+
+ public String getPid() {
+ return getPid(true);
+ }
+
+ public Dictionary getProperties() {
+ try {
+ lock();
+ checkDeleted();
+ if (dictionary == null)
+ return null;
+
+ Dictionary copy = dictionary.copy();
+ copy.put(Constants.SERVICE_PID, pid);
+ if (factoryPid != null)
+ copy.put(ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid);
+
+ return copy;
+ } finally {
+ unlock();
+ }
+ }
+
+ protected Dictionary getAllProperties() {
+ try {
+ lock();
+ if (deleted)
+ return null;
+ Dictionary copy = getProperties();
+ if (copy != null && bundleLocation != null)
+ copy.put(ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation());
+ return copy;
+ } finally {
+ unlock();
+ }
+ }
+
+ public void setBundleLocation(String bundleLocation) {
+ try {
+ lock();
+ checkDeleted();
+ configurationAdminFactory.checkConfigurationPermission();
+ this.bundleLocation = bundleLocation;
+ } finally {
+ unlock();
+ }
+ }
+
+ public void update() throws IOException {
+ try {
+ lock();
+ checkDeleted();
+ if (dictionary == null)
+ dictionary = new ConfigurationDictionary();
+ configurationStore.saveConfiguration(pid, this);
+ configurationAdminFactory.notifyConfigurationUpdated(this, factoryPid != null);
+ } finally {
+ unlock();
+ }
+ }
+
+ public void update(Dictionary properties) throws IOException {
+ try {
+ lock();
+ checkDeleted();
+ updateDictionary(properties);
+ configurationStore.saveConfiguration(pid, this);
+ configurationAdminFactory.notifyConfigurationUpdated(this, factoryPid != null);
+ configurationAdminFactory.dispatchEvent(ConfigurationEvent.CM_UPDATED, factoryPid, pid);
+ } finally {
+ unlock();
+ }
+ }
+
+ private void updateDictionary(Dictionary properties) {
+ ConfigurationDictionary newDictionary = new ConfigurationDictionary();
+ Enumeration keys = properties.keys();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ if (newDictionary.get(key) == null)
+ newDictionary.put(key, properties.get(key));
+ else
+ throw new IllegalArgumentException(key + " is already present or is a case variant."); //$NON-NLS-1$
+ }
+ newDictionary.remove(Constants.SERVICE_PID);
+ newDictionary.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
+ newDictionary.remove(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
+
+ dictionary = newDictionary;
+ }
+
+ public boolean equals(Object obj) {
+ return pid.equals(((Configuration) obj).getPid());
+ }
+
+ public int hashCode() {
+ return pid.hashCode();
+ }
+
+ protected boolean isDeleted() {
+ try {
+ lock();
+ return deleted;
+ } finally {
+ unlock();
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java
new file mode 100644
index 00000000..756dba3a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java
@@ -0,0 +1,202 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2008 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import org.eclipse.equinox.internal.cm.reliablefile.*;
+import org.osgi.framework.*;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.log.LogService;
+
+/**
+ * ConfigurationStore manages all active configurations along with persistence. The current
+ * implementation uses a filestore and serialization of the configuration dictionaries to files
+ * identified by their pid. Persistence details are in the constructor, saveConfiguration, and
+ * deleteConfiguration and can be factored out separately if required.
+ */
+class ConfigurationStore {
+
+ private final ConfigurationAdminFactory configurationAdminFactory;
+ private static final String STORE_DIR = "store"; //$NON-NLS-1$
+ private static final String PID_EXT = ".pid"; //$NON-NLS-1$
+ private final Map configurations = new HashMap();
+ private int createdPidCount = 0;
+ private final File store;
+
+ public ConfigurationStore(ConfigurationAdminFactory configurationAdminFactory, BundleContext context) {
+ this.configurationAdminFactory = configurationAdminFactory;
+ store = context.getDataFile(STORE_DIR);
+ if (store == null)
+ return; // no persistent store
+
+ store.mkdir();
+ File[] configurationFiles = store.listFiles();
+ for (int i = 0; i < configurationFiles.length; ++i) {
+ String configurationFileName = configurationFiles[i].getName();
+ if (!configurationFileName.endsWith(PID_EXT))
+ continue;
+
+ InputStream ris = null;
+ ObjectInputStream ois = null;
+ boolean deleteFile = false;
+ try {
+ ris = new ReliableFileInputStream(configurationFiles[i]);
+ ois = new ObjectInputStream(ris);
+ Dictionary dictionary = (Dictionary) ois.readObject();
+ ConfigurationImpl config = new ConfigurationImpl(configurationAdminFactory, this, dictionary);
+ configurations.put(config.getPid(), config);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ String pid = configurationFileName.substring(0, configurationFileName.length() - 4);
+ String errorMessage = "{Configuration Admin - pid = " + pid + "} could not be restored." + ((message == null) ? "" : " " + message); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ configurationAdminFactory.log(LogService.LOG_ERROR, errorMessage);
+ deleteFile = true;
+ } catch (ClassNotFoundException e) {
+ configurationAdminFactory.log(LogService.LOG_ERROR, e.getMessage());
+ } finally {
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (ris != null) {
+ try {
+ ris.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ if (deleteFile) {
+ ReliableFile.delete(configurationFiles[i]);
+ configurationFiles[i].delete();
+ }
+ }
+ }
+
+ public void saveConfiguration(String pid, ConfigurationImpl config) throws IOException {
+ if (store == null)
+ return; // no persistent store
+
+ config.checkLocked();
+ final File configFile = new File(store, pid + PID_EXT);
+ final Dictionary configProperties = config.getAllProperties();
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ writeConfigurationFile(configFile, configProperties);
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+ }
+
+ void writeConfigurationFile(File configFile, Dictionary configProperties) throws IOException {
+ OutputStream ros = null;
+ ObjectOutputStream oos = null;
+ try {
+ configFile.createNewFile();
+ ros = new ReliableFileOutputStream(configFile);
+ oos = new ObjectOutputStream(ros);
+ oos.writeObject(configProperties);
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (ros != null) {
+ try {
+ ros.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ public synchronized void removeConfiguration(String pid) {
+ configurations.remove(pid);
+ if (store == null)
+ return; // no persistent store
+ final File configFile = new File(store, pid + PID_EXT);
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ deleteConfigurationFile(configFile);
+ return null;
+ }
+ });
+ }
+
+ void deleteConfigurationFile(File configFile) {
+ ReliableFile.delete(configFile);
+ configFile.delete();
+ }
+
+ public synchronized Configuration getConfiguration(String pid, String location) {
+ Configuration config = (Configuration) configurations.get(pid);
+ if (config == null) {
+ config = new ConfigurationImpl(configurationAdminFactory, this, null, pid, location);
+ configurations.put(pid, config);
+ }
+ return config;
+ }
+
+ public synchronized Configuration createFactoryConfiguration(String factoryPid, String location) {
+ String pid = factoryPid + "-" + new Date().getTime() + "-" + createdPidCount++; //$NON-NLS-1$ //$NON-NLS-2$
+ ConfigurationImpl config = new ConfigurationImpl(configurationAdminFactory, this, factoryPid, pid, location);
+ configurations.put(pid, config);
+ return config;
+ }
+
+ public synchronized ConfigurationImpl findConfiguration(String pid) {
+ return (ConfigurationImpl) configurations.get(pid);
+ }
+
+ public synchronized ConfigurationImpl[] getFactoryConfigurations(String factoryPid) {
+ List resultList = new ArrayList();
+ for (Iterator it = configurations.values().iterator(); it.hasNext();) {
+ ConfigurationImpl config = (ConfigurationImpl) it.next();
+ String otherFactoryPid = config.getFactoryPid();
+ if (otherFactoryPid != null && otherFactoryPid.equals(factoryPid))
+ resultList.add(config);
+ }
+ return (ConfigurationImpl[]) resultList.toArray(new ConfigurationImpl[0]);
+ }
+
+ public synchronized Configuration[] listConfigurations(Filter filter) {
+ List resultList = new ArrayList();
+ for (Iterator it = configurations.values().iterator(); it.hasNext();) {
+ ConfigurationImpl config = (ConfigurationImpl) it.next();
+ Dictionary properties = config.getAllProperties();
+ if (properties != null && filter.match(properties))
+ resultList.add(config);
+ }
+ int size = resultList.size();
+ return size == 0 ? null : (Configuration[]) resultList.toArray(new Configuration[size]);
+ }
+
+ public synchronized void unbindConfigurations(Bundle bundle) {
+ for (Iterator it = configurations.values().iterator(); it.hasNext();) {
+ ConfigurationImpl config = (ConfigurationImpl) it.next();
+ config.unbind(bundle);
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/EventDispatcher.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/EventDispatcher.java
new file mode 100644
index 00000000..9da1be88
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/EventDispatcher.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * EventDispatcher is responsible for delivering Configuration Events to ConfigurationListeners.
+ * The originating ConfigAdmin ServiceReference is needed when delivering events. This reference
+ * is made available by the service factory before returning the service object.
+ */
+
+public class EventDispatcher {
+ final ServiceTracker tracker;
+ private final SerializedTaskQueue queue = new SerializedTaskQueue("ConfigurationListener Event Queue"); //$NON-NLS-1$
+ /** @GuardedBy this */
+ private ServiceReference configAdminReference;
+ final LogService log;
+
+ public EventDispatcher(BundleContext context, LogService log) {
+ this.log = log;
+ tracker = new ServiceTracker(context, ConfigurationListener.class.getName(), null);
+ }
+
+ public void start() {
+ tracker.open();
+ }
+
+ public void stop() {
+ tracker.close();
+ synchronized (this) {
+ configAdminReference = null;
+ }
+ }
+
+ synchronized void setServiceReference(ServiceReference reference) {
+ if (configAdminReference == null)
+ configAdminReference = reference;
+ }
+
+ public void dispatchEvent(int type, String factoryPid, String pid) {
+ final ConfigurationEvent event = createConfigurationEvent(type, factoryPid, pid);
+ if (event == null)
+ return;
+
+ ServiceReference[] refs = tracker.getServiceReferences();
+ if (refs == null)
+ return;
+
+ for (int i = 0; i < refs.length; ++i) {
+ final ServiceReference ref = refs[i];
+ queue.put(new Runnable() {
+ public void run() {
+ ConfigurationListener listener = (ConfigurationListener) tracker.getService(ref);
+ if (listener == null) {
+ return;
+ }
+ try {
+ listener.configurationEvent(event);
+ } catch (Throwable t) {
+ log.log(LogService.LOG_ERROR, t.getMessage(), t);
+ }
+ }
+ });
+ }
+ }
+
+ private synchronized ConfigurationEvent createConfigurationEvent(int type, String factoryPid, String pid) {
+ if (configAdminReference == null)
+ return null;
+
+ return new ConfigurationEvent(configAdminReference, type, factoryPid, pid);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/LogTracker.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/LogTracker.java
new file mode 100644
index 00000000..d13db9e8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/LogTracker.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright (c) 1998, 2007 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
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.io.PrintStream;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * LogTracker class. This class encapsulates the LogService
+ * and handles all issues such as the service coming and going.
+ */
+
+public class LogTracker extends ServiceTracker implements LogService {
+ /** LogService interface class name */
+ protected final static String clazz = "org.osgi.service.log.LogService"; //$NON-NLS-1$
+
+ /** PrintStream to use if LogService is unavailable */
+ private final PrintStream out;
+
+ /**
+ * Create new LogTracker.
+ *
+ * @param context BundleContext of parent bundle.
+ * @param out Default PrintStream to use if LogService is unavailable.
+ */
+ public LogTracker(BundleContext context, PrintStream out) {
+ super(context, clazz, null);
+ this.out = out;
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * LogService Interface implementation
+ * ----------------------------------------------------------------------
+ */
+
+ public void log(int level, String message) {
+ log(null, level, message, null);
+ }
+
+ public void log(int level, String message, Throwable exception) {
+ log(null, level, message, exception);
+ }
+
+ public void log(ServiceReference reference, int level, String message) {
+ log(reference, level, message, null);
+ }
+
+ public synchronized void log(ServiceReference reference, int level, String message, Throwable exception) {
+ ServiceReference[] references = getServiceReferences();
+
+ if (references != null) {
+ int size = references.length;
+
+ for (int i = 0; i < size; i++) {
+ LogService service = (LogService) getService(references[i]);
+ if (service != null) {
+ try {
+ service.log(reference, level, message, exception);
+ } catch (Exception e) {
+ // TODO: consider printing to System Error
+ }
+ }
+ }
+
+ return;
+ }
+
+ noLogService(level, message, exception, reference);
+ }
+
+ /**
+ * The LogService is not available so we write the message to a PrintStream.
+ *
+ * @param level Logging level
+ * @param message Log message.
+ * @param throwable Log exception or null if none.
+ * @param reference ServiceReference associated with message or null if none.
+ */
+ protected void noLogService(int level, String message, Throwable throwable, ServiceReference reference) {
+ if (out != null) {
+ synchronized (out) {
+ // Bug #113286. If no log service present and messages are being
+ // printed to stdout, prepend message with a timestamp.
+ String timestamp = getDate(new Date());
+ out.print(timestamp + " "); //$NON-NLS-1$
+
+ switch (level) {
+ case LOG_DEBUG : {
+ out.print("Debug"); //$NON-NLS-1$
+ break;
+ }
+ case LOG_INFO : {
+ out.print("Info"); //$NON-NLS-1$
+ break;
+ }
+ case LOG_WARNING : {
+ out.print("Warning"); //$NON-NLS-1$
+ break;
+ }
+ case LOG_ERROR : {
+ out.print("Error"); //$NON-NLS-1$
+ break;
+ }
+ default : {
+ out.print("["); //$NON-NLS-1$
+ out.print("Unknown Log Level"); //$NON-NLS-1$
+ out.print("]"); //$NON-NLS-1$
+ break;
+ }
+ }
+ out.print(": "); //$NON-NLS-1$
+ out.println(message);
+
+ if (reference != null) {
+ out.println(reference);
+ }
+
+ if (throwable != null) {
+ throwable.printStackTrace(out);
+ }
+ }
+ }
+ }
+
+ // from EclipseLog to avoid using DateFormat -- see bug 149892#c10
+ private String getDate(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ StringBuffer sb = new StringBuffer();
+ appendPaddedInt(c.get(Calendar.YEAR), 4, sb).append('-');
+ appendPaddedInt(c.get(Calendar.MONTH) + 1, 2, sb).append('-');
+ appendPaddedInt(c.get(Calendar.DAY_OF_MONTH), 2, sb).append(' ');
+ appendPaddedInt(c.get(Calendar.HOUR_OF_DAY), 2, sb).append(':');
+ appendPaddedInt(c.get(Calendar.MINUTE), 2, sb).append(':');
+ appendPaddedInt(c.get(Calendar.SECOND), 2, sb).append('.');
+ appendPaddedInt(c.get(Calendar.MILLISECOND), 3, sb);
+ return sb.toString();
+ }
+
+ private StringBuffer appendPaddedInt(int value, int pad, StringBuffer buffer) {
+ pad = pad - 1;
+ if (pad == 0)
+ return buffer.append(Integer.toString(value));
+ int padding = (int) Math.pow(10, pad);
+ if (value >= padding)
+ return buffer.append(Integer.toString(value));
+ while (padding > value && padding > 1) {
+ buffer.append('0');
+ padding = padding / 10;
+ }
+ buffer.append(value);
+ return buffer;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java
new file mode 100644
index 00000000..2d7d8908
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.osgi.framework.*;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * ManagedServiceFactoryTracker tracks... ManagedServiceFactory(s) and notifies them about related configuration changes
+ */
+class ManagedServiceFactoryTracker extends ServiceTracker {
+
+ final ConfigurationAdminFactory configurationAdminFactory;
+ private final ConfigurationStore configurationStore;
+ private final Map managedServiceFactories = new HashMap();
+ private final Map managedServiceFactoryReferences = new HashMap();
+ private final SerializedTaskQueue queue = new SerializedTaskQueue("ManagedServiceFactory Update Queue"); //$NON-NLS-1$
+
+ public ManagedServiceFactoryTracker(ConfigurationAdminFactory configurationAdminFactory, ConfigurationStore configurationStore, BundleContext context) {
+ super(context, ManagedServiceFactory.class.getName(), null);
+ this.configurationAdminFactory = configurationAdminFactory;
+ this.configurationStore = configurationStore;
+ }
+
+ protected void notifyDeleted(ConfigurationImpl config) {
+ config.checkLocked();
+ String factoryPid = config.getFactoryPid(false);
+ ServiceReference reference = getManagedServiceFactoryReference(factoryPid);
+ if (reference != null && config.bind(reference.getBundle()))
+ asynchDeleted(getManagedServiceFactory(factoryPid), config.getPid(false));
+ }
+
+ protected void notifyUpdated(ConfigurationImpl config) {
+ config.checkLocked();
+ String factoryPid = config.getFactoryPid();
+ ServiceReference reference = getManagedServiceFactoryReference(factoryPid);
+ if (reference != null && config.bind(reference.getBundle())) {
+ Dictionary properties = config.getProperties();
+ configurationAdminFactory.modifyConfiguration(reference, properties);
+ asynchUpdated(getManagedServiceFactory(factoryPid), config.getPid(), properties);
+ }
+ }
+
+ public Object addingService(ServiceReference reference) {
+ String factoryPid = (String) reference.getProperty(Constants.SERVICE_PID);
+ if (factoryPid == null)
+ return null;
+
+ ManagedServiceFactory service = (ManagedServiceFactory) context.getService(reference);
+ if (service == null)
+ return null;
+
+ synchronized (configurationStore) {
+ add(reference, factoryPid, service);
+ }
+ return service;
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ String factoryPid = (String) reference.getProperty(Constants.SERVICE_PID);
+ synchronized (configurationStore) {
+ if (getManagedServiceFactory(factoryPid) == service)
+ return;
+ String previousPid = getPidForManagedServiceFactory(service);
+ remove(reference, previousPid);
+ addingService(reference);
+ }
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ String factoryPid = (String) reference.getProperty(Constants.SERVICE_PID);
+ synchronized (configurationStore) {
+ remove(reference, factoryPid);
+ }
+ context.ungetService(reference);
+ }
+
+ private void add(ServiceReference reference, String factoryPid, ManagedServiceFactory service) {
+ ConfigurationImpl[] configs = configurationStore.getFactoryConfigurations(factoryPid);
+ try {
+ for (int i = 0; i < configs.length; ++i)
+ configs[i].lock();
+
+ if (trackManagedServiceFactory(factoryPid, reference, service)) {
+ for (int i = 0; i < configs.length; ++i) {
+ if (configs[i].isDeleted()) {
+ // ignore this config
+ } else if (configs[i].bind(reference.getBundle())) {
+ Dictionary properties = configs[i].getProperties();
+ configurationAdminFactory.modifyConfiguration(reference, properties);
+ asynchUpdated(service, configs[i].getPid(), properties);
+ } else {
+ configurationAdminFactory.log(LogService.LOG_WARNING, "Configuration for " + Constants.SERVICE_PID + "=" + configs[i].getPid() + " could not be bound to " + reference.getBundle().getLocation()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+ }
+ } finally {
+ for (int i = 0; i < configs.length; ++i)
+ configs[i].unlock();
+ }
+ }
+
+ private void remove(ServiceReference reference, String factoryPid) {
+ ConfigurationImpl[] configs = configurationStore.getFactoryConfigurations(factoryPid);
+ try {
+ for (int i = 0; i < configs.length; ++i)
+ configs[i].lock();
+ untrackManagedServiceFactory(factoryPid, reference);
+ } finally {
+ for (int i = 0; i < configs.length; ++i)
+ configs[i].unlock();
+ }
+ }
+
+ private synchronized boolean trackManagedServiceFactory(String factoryPid, ServiceReference reference, ManagedServiceFactory service) {
+ if (managedServiceFactoryReferences.containsKey(factoryPid)) {
+ configurationAdminFactory.log(LogService.LOG_WARNING, ManagedServiceFactory.class.getName() + " already registered for " + Constants.SERVICE_PID + "=" + factoryPid); //$NON-NLS-1$ //$NON-NLS-2$
+ return false;
+ }
+ managedServiceFactoryReferences.put(factoryPid, reference);
+ managedServiceFactories.put(factoryPid, service);
+ return true;
+ }
+
+ private synchronized void untrackManagedServiceFactory(String factoryPid, ServiceReference reference) {
+ managedServiceFactoryReferences.remove(factoryPid);
+ managedServiceFactories.remove(factoryPid);
+ }
+
+ private synchronized ManagedServiceFactory getManagedServiceFactory(String factoryPid) {
+ return (ManagedServiceFactory) managedServiceFactories.get(factoryPid);
+ }
+
+ private synchronized ServiceReference getManagedServiceFactoryReference(String factoryPid) {
+ return (ServiceReference) managedServiceFactoryReferences.get(factoryPid);
+ }
+
+ private String getPidForManagedServiceFactory(Object service) {
+ for (Iterator it = managedServiceFactories.entrySet().iterator(); it.hasNext();) {
+ Entry entry = (Entry) it.next();
+ if (entry.getValue() == service)
+ return (String) entry.getKey();
+ }
+ return null;
+ }
+
+ private void asynchDeleted(final ManagedServiceFactory service, final String pid) {
+ queue.put(new Runnable() {
+ public void run() {
+ try {
+ service.deleted(pid);
+ } catch (Throwable t) {
+ configurationAdminFactory.log(LogService.LOG_ERROR, t.getMessage(), t);
+ }
+ }
+ });
+ }
+
+ private void asynchUpdated(final ManagedServiceFactory service, final String pid, final Dictionary properties) {
+ queue.put(new Runnable() {
+ public void run() {
+ try {
+ service.updated(pid, properties);
+ } catch (ConfigurationException e) {
+ // we might consider doing more for ConfigurationExceptions
+ Throwable cause = e.getCause();
+ configurationAdminFactory.log(LogService.LOG_ERROR, e.getMessage(), cause != null ? cause : e);
+ } catch (Throwable t) {
+ configurationAdminFactory.log(LogService.LOG_ERROR, t.getMessage(), t);
+ }
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java
new file mode 100644
index 00000000..4b0cf5b3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.osgi.framework.*;
+import org.osgi.service.cm.*;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * ManagedServiceTracker tracks... ManagedServices and notifies them about related configuration changes
+ */
+class ManagedServiceTracker extends ServiceTracker {
+
+ final ConfigurationAdminFactory configurationAdminFactory;
+ private final ConfigurationStore configurationStore;
+ private final Map managedServices = new HashMap();
+ private final Map managedServiceReferences = new HashMap();
+ private final SerializedTaskQueue queue = new SerializedTaskQueue("ManagedService Update Queue"); //$NON-NLS-1$
+
+ public ManagedServiceTracker(ConfigurationAdminFactory configurationAdminFactory, ConfigurationStore configurationStore, BundleContext context) {
+ super(context, ManagedService.class.getName(), null);
+ this.configurationAdminFactory = configurationAdminFactory;
+ this.configurationStore = configurationStore;
+ }
+
+ protected void notifyDeleted(ConfigurationImpl config) {
+ config.checkLocked();
+ String pid = config.getPid(false);
+ ServiceReference reference = getManagedServiceReference(pid);
+ if (reference != null && config.bind(reference.getBundle()))
+ asynchUpdated(getManagedService(pid), null);
+ }
+
+ protected void notifyUpdated(ConfigurationImpl config) {
+ config.checkLocked();
+ String pid = config.getPid();
+ ServiceReference reference = getManagedServiceReference(pid);
+ if (reference != null && config.bind(reference.getBundle())) {
+ Dictionary properties = config.getProperties();
+ configurationAdminFactory.modifyConfiguration(reference, properties);
+ asynchUpdated(getManagedService(pid), properties);
+ }
+ }
+
+ public Object addingService(ServiceReference reference) {
+ String pid = (String) reference.getProperty(Constants.SERVICE_PID);
+ if (pid == null)
+ return null;
+
+ ManagedService service = (ManagedService) context.getService(reference);
+ if (service == null)
+ return null;
+
+ synchronized (configurationStore) {
+ add(reference, pid, service);
+ }
+ return service;
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ String pid = (String) reference.getProperty(Constants.SERVICE_PID);
+ synchronized (configurationStore) {
+ if (getManagedService(pid) == service)
+ return;
+ String previousPid = getPidForManagedService(service);
+ remove(reference, previousPid);
+ addingService(reference);
+ }
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ String pid = (String) reference.getProperty(Constants.SERVICE_PID);
+ synchronized (configurationStore) {
+ remove(reference, pid);
+ }
+ context.ungetService(reference);
+ }
+
+ private void add(ServiceReference reference, String pid, ManagedService service) {
+ ConfigurationImpl config = configurationStore.findConfiguration(pid);
+ if (config == null && trackManagedService(pid, reference, service)) {
+ asynchUpdated(service, null);
+ } else {
+ try {
+ config.lock();
+ if (trackManagedService(pid, reference, service)) {
+ if (config.getFactoryPid() != null) {
+ configurationAdminFactory.log(LogService.LOG_WARNING, "Configuration for " + Constants.SERVICE_PID + "=" + pid + " should only be used by a " + ManagedServiceFactory.class.getName()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ } else if (config.isDeleted()) {
+ asynchUpdated(service, null);
+ } else if (config.bind(reference.getBundle())) {
+ Dictionary properties = config.getProperties();
+ configurationAdminFactory.modifyConfiguration(reference, properties);
+ asynchUpdated(service, properties);
+ } else {
+ configurationAdminFactory.log(LogService.LOG_WARNING, "Configuration for " + Constants.SERVICE_PID + "=" + pid + " could not be bound to " + reference.getBundle().getLocation()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+ } finally {
+ config.unlock();
+ }
+ }
+ }
+
+ private void remove(ServiceReference reference, String pid) {
+ ConfigurationImpl config = configurationStore.findConfiguration(pid);
+ if (config == null) {
+ untrackManagedService(pid, reference);
+ } else {
+ try {
+ config.lock();
+ untrackManagedService(pid, reference);
+ } finally {
+ config.unlock();
+ }
+ }
+ }
+
+ private synchronized boolean trackManagedService(String pid, ServiceReference reference, ManagedService service) {
+ if (managedServiceReferences.containsKey(pid)) {
+ String message = ManagedService.class.getName() + " already registered for " + Constants.SERVICE_PID + "=" + pid; //$NON-NLS-1$ //$NON-NLS-2$
+ configurationAdminFactory.log(LogService.LOG_WARNING, message);
+ return false;
+ }
+ managedServiceReferences.put(pid, reference);
+ managedServices.put(pid, service);
+ return true;
+ }
+
+ private synchronized void untrackManagedService(String pid, ServiceReference reference) {
+ managedServiceReferences.remove(pid);
+ managedServices.remove(pid);
+ }
+
+ private synchronized ManagedService getManagedService(String pid) {
+ return (ManagedService) managedServices.get(pid);
+ }
+
+ private synchronized ServiceReference getManagedServiceReference(String pid) {
+ return (ServiceReference) managedServiceReferences.get(pid);
+ }
+
+ private synchronized String getPidForManagedService(Object service) {
+ for (Iterator it = managedServices.entrySet().iterator(); it.hasNext();) {
+ Entry entry = (Entry) it.next();
+ if (entry.getValue() == service)
+ return (String) entry.getKey();
+ }
+ return null;
+ }
+
+ private void asynchUpdated(final ManagedService service, final Dictionary properties) {
+ queue.put(new Runnable() {
+ public void run() {
+ try {
+ service.updated(properties);
+ } catch (ConfigurationException e) {
+ // we might consider doing more for ConfigurationExceptions
+ Throwable cause = e.getCause();
+ configurationAdminFactory.log(LogService.LOG_ERROR, e.getMessage(), cause != null ? cause : e);
+ } catch (Throwable t) {
+ configurationAdminFactory.log(LogService.LOG_ERROR, t.getMessage(), t);
+ }
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java
new file mode 100644
index 00000000..0d11b136
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Cognos Incorporated, 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:
+ * Cognos Incorporated - initial API and implementation
+ * IBM Corporation - bug fixes and enhancements
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.TreeSet;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationPlugin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * PluginManager tracks and allows customization via ConfigurationPlugin
+ */
+public class PluginManager {
+ private final PluginTracker pluginTracker;
+
+ public PluginManager(BundleContext context) {
+ pluginTracker = new PluginTracker(context);
+ }
+
+ public void start() {
+ pluginTracker.open();
+ }
+
+ public void stop() {
+ pluginTracker.close();
+ }
+
+ public void modifyConfiguration(ServiceReference managedReference, Dictionary properties) {
+ if (properties == null)
+ return;
+
+ ServiceReference[] references = pluginTracker.getServiceReferences();
+ for (int i = 0; i < references.length; ++i) {
+ String[] pids = (String[]) references[i].getProperty(ConfigurationPlugin.CM_TARGET);
+ if (pids != null) {
+ String pid = (String) properties.get(Constants.SERVICE_PID);
+ if (!Arrays.asList(pids).contains(pid))
+ continue;
+ }
+ ConfigurationPlugin plugin = (ConfigurationPlugin) pluginTracker.getService(references[i]);
+ if (plugin != null)
+ plugin.modifyConfiguration(managedReference, properties);
+ }
+ }
+
+ private static class PluginTracker extends ServiceTracker {
+ final Integer ZERO = new Integer(0);
+ private TreeSet serviceReferences = new TreeSet(new Comparator() {
+ public int compare(Object o1, Object o2) {
+ return getRank((ServiceReference) o1).compareTo(getRank((ServiceReference) o2));
+ }
+
+ private Integer getRank(ServiceReference ref) {
+ Object ranking = ref.getProperty(ConfigurationPlugin.CM_RANKING);
+ if (ranking == null || !(ranking instanceof Integer))
+ return ZERO;
+ return ((Integer) ranking);
+ }
+ });
+
+ public PluginTracker(BundleContext context) {
+ super(context, ConfigurationPlugin.class.getName(), null);
+ }
+
+ /* NOTE: this method alters the contract of the overriden method.
+ * Rather than returning null if no references are present, it
+ * returns an empty array.
+ */
+ public ServiceReference[] getServiceReferences() {
+ synchronized (serviceReferences) {
+ return (ServiceReference[]) serviceReferences.toArray(new ServiceReference[0]);
+ }
+ }
+
+ public Object addingService(ServiceReference reference) {
+ synchronized (serviceReferences) {
+ serviceReferences.add(reference);
+ }
+ return context.getService(reference);
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ // nothing to do
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ synchronized (serviceReferences) {
+ serviceReferences.remove(reference);
+ }
+ context.ungetService(reference);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/SerializedTaskQueue.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/SerializedTaskQueue.java
new file mode 100644
index 00000000..89a5760b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/SerializedTaskQueue.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2006 Cognos Incorporated
+ * 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:
+ * Cognos Incorporated - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.cm;
+
+import java.util.LinkedList;
+
+/**
+ * SerializedTaskQueue is a utility class that will allow asynchronous but serialized execution of tasks
+ */
+public class SerializedTaskQueue {
+
+ private static final int MAX_WAIT = 5000;
+ private final LinkedList tasks = new LinkedList();
+ private Thread thread;
+ private final String queueName;
+
+ public SerializedTaskQueue(String queueName) {
+ this.queueName = queueName;
+ }
+
+ public synchronized void put(Runnable newTask) {
+ tasks.add(newTask);
+ if (thread == null) {
+ thread = new Thread(queueName) {
+ public void run() {
+ Runnable task = nextTask(MAX_WAIT);
+ while (task != null) {
+ task.run();
+ task = nextTask(MAX_WAIT);
+ }
+ }
+ };
+ thread.start();
+ } else
+ notify();
+ }
+
+ synchronized Runnable nextTask(int maxWait) {
+ if (tasks.isEmpty()) {
+ try {
+ wait(maxWait);
+ } catch (InterruptedException e) {
+ // ignore -- we control the stack here and do not need to propagate it.
+ }
+
+ if (tasks.isEmpty()) {
+ thread = null;
+ return null;
+ }
+ }
+ return (Runnable) tasks.removeFirst();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFile.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFile.java
new file mode 100644
index 00000000..3d141a6e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFile.java
@@ -0,0 +1,840 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.cm.reliablefile;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+import org.eclipse.equinox.internal.cm.Activator;
+
+//This is a copy of org.eclipse.osgi.framework.internal.reliablefile.ReliableFile
+
+/**
+ * ReliableFile class used by ReliableFileInputStream and ReliableOutputStream.
+ * This class encapsulates all the logic for reliable file support.
+ *
+ */
+public class ReliableFile {
+ /**
+ * Open mask. Obtain the best data stream available. If the primary data
+ * contents are invalid (corrupt, missing, etc.), the data for a prior
+ * version may be used.
+ * An IOException will be thrown if a valid data content can not be
+ * determined.
+ * This is mutually exclusive with <code>OPEN_FAIL_ON_PRIMARY</code>.
+ */
+ public static final int OPEN_BEST_AVAILABLE = 0;
+ /**
+ * Open mask. Obtain only the data stream for the primary file where any other
+ * version will not be valid. This should be used for data streams that are
+ * managed as a group as a prior contents may not match the other group data.
+ * If the primary data is not invalid, a IOException will be thrown.
+ * This is mutually exclusive with <code>OPEN_BEST_AVAILABLE</code>.
+ */
+ public static final int OPEN_FAIL_ON_PRIMARY = 1;
+
+ /**
+ * Use the last generation of the file
+ */
+ public static final int GENERATION_LATEST = 0;
+ /**
+ * Keep infinite backup files
+ */
+ public static final int GENERATIONS_INFINITE = 0;
+
+ /**
+ * Extension of tmp file used during writing.
+ * A reliable file with this extension should
+ * never be directly used.
+ */
+ public static final String tmpExt = ".tmp"; //$NON-NLS-1$
+
+ /**
+ * Property to set the maximum size of a file that will be buffered. When calculating a ReliableFile
+ * checksum, if the file is this size or small, ReliableFile will read the file contents into a
+ * <code>BufferedInputStream</code> and reset the buffer to avoid having to read the data from the
+ * media twice. Since this method require memory for storage, it is limited to this size. The default
+ * maximum is 128-KBytes.
+ */
+ public static final String PROP_MAX_BUFFER = "osgi.reliableFile.maxInputStreamBuffer"; //$NON-NLS-1$
+ /**
+ * The maximum number of generations to keep as backup files in case last generation
+ * file is determined to be invalid.
+ */
+ public static final String PROP_MAX_GENERATIONS = "osgi.ReliableFile.maxGenerations"; //$NON-NLS-1$
+ /**
+ *see org.eclipse.core.runtime.internal.adaptor.BasicLocation#PROP_OSGI_LOCKING
+ */
+ public static final String PROP_OSGI_LOCKING = "osgi.locking"; //$NON-NLS-1$
+
+ private static final int FILETYPE_VALID = 0;
+ private static final int FILETYPE_CORRUPT = 1;
+ private static final int FILETYPE_NOSIGNATURE = 2;
+
+ private static final byte identifier1[] = {'.', 'c', 'r', 'c'};
+ private static final byte identifier2[] = {'.', 'v', '1', '\n'};
+
+ private static final int BUF_SIZE = 4096;
+ private static final int maxInputStreamBuffer;
+ private static final int defaultMaxGenerations;
+ private static final boolean fileSharing;
+ //our cache of the last looked up generations for a file
+ private static File lastGenerationFile = null;
+ private static int[] lastGenerations = null;
+ private static final Object lastGenerationLock = new Object();
+
+ static {
+ String prop = Activator.getProperty(PROP_MAX_BUFFER);
+ int tmpMaxInput = 128 * 1024; //128k
+ if (prop != null) {
+ try {
+ tmpMaxInput = Integer.parseInt(prop);
+ } catch (NumberFormatException e) {/*ignore*/
+ }
+ }
+ maxInputStreamBuffer = tmpMaxInput;
+
+ int tmpDefaultMax = 2;
+ prop = Activator.getProperty(PROP_MAX_GENERATIONS);
+ if (prop != null) {
+ try {
+ tmpDefaultMax = Integer.parseInt(prop);
+ } catch (NumberFormatException e) {/*ignore*/
+ }
+ }
+ defaultMaxGenerations = tmpDefaultMax;
+
+ prop = Activator.getProperty(PROP_OSGI_LOCKING);
+ boolean tmpFileSharing = true;
+ if (prop != null) {
+ if (prop.equals("none")) { //$NON-NLS-1$
+ tmpFileSharing = false;
+ }
+ }
+ fileSharing = tmpFileSharing;
+ }
+
+ /** File object for original reference file */
+ private File referenceFile;
+
+ /** List of checksum file objects: File => specific ReliableFile generation */
+ private static Hashtable cacheFiles = new Hashtable(20);
+
+ private File inputFile = null;
+ private File outputFile = null;
+ private Checksum appendChecksum = null;
+
+ /**
+ * ReliableFile object factory. This method is called by ReliableFileInputStream
+ * and ReliableFileOutputStream to get a ReliableFile object for a target file.
+ * If the object is in the cache, the cached copy is returned.
+ * Otherwise a new ReliableFile object is created and returned.
+ * The use count of the returned ReliableFile object is incremented.
+ *
+ * @param name Name of the target file.
+ * @return A ReliableFile object for the target file.
+ * @throws IOException If the target file is a directory.
+ */
+ static ReliableFile getReliableFile(String name) throws IOException {
+ return getReliableFile(new File(name));
+ }
+
+ /**
+ * ReliableFile object factory. This method is called by ReliableFileInputStream
+ * and ReliableFileOutputStream to get a ReliableFile object for a target file.
+ * If the object is in the cache, the cached copy is returned.
+ * Otherwise a new ReliableFile object is created and returned.
+ * The use count of the returned ReliableFile object is incremented.
+ *
+ * @param file File object for the target file.
+ * @return A ReliableFile object for the target file.
+ * @throws IOException If the target file is a directory.
+ */
+ static ReliableFile getReliableFile(File file) throws IOException {
+ if (file.isDirectory()) {
+ throw new FileNotFoundException("file is a directory"); //$NON-NLS-1$
+ }
+ return new ReliableFile(file);
+ }
+
+ /**
+ * Private constructor used by the static getReliableFile factory methods.
+ *
+ * @param file File object for the target file.
+ */
+ private ReliableFile(File file) {
+ referenceFile = file;
+ }
+
+ private static int[] getFileGenerations(File file) {
+ if (!fileSharing) {
+ synchronized (lastGenerationLock) {
+ if (lastGenerationFile != null) {
+ //shortcut maybe, only if filesharing is not supported
+ if (file.equals(lastGenerationFile))
+ return lastGenerations;
+ }
+ }
+ }
+ int[] generations = null;
+ try {
+ String name = file.getName();
+ String prefix = name + '.';
+ int prefixLen = prefix.length();
+ File parent = new File(file.getParent());
+ String[] files = parent.list();
+ if (files == null)
+ return null;
+ ArrayList list = new ArrayList(defaultMaxGenerations);
+ if (file.exists())
+ list.add(new Integer(0)); //base file exists
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].startsWith(prefix)) {
+ try {
+ int id = Integer.parseInt(files[i].substring(prefixLen));
+ list.add(new Integer(id));
+ } catch (NumberFormatException e) {/*ignore*/
+ }
+ }
+ }
+ if (list.size() == 0)
+ return null;
+ Object[] array = list.toArray();
+ Arrays.sort(array);
+ generations = new int[array.length];
+ for (int i = 0, j = array.length - 1; i < array.length; i++, j--) {
+ generations[i] = ((Integer) array[j]).intValue();
+ }
+ return generations;
+ } finally {
+ if (!fileSharing) {
+ synchronized (lastGenerationLock) {
+ lastGenerationFile = file;
+ lastGenerations = generations;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns an InputStream object for reading the target file.
+ *
+ * @param generation the maximum generation to evaluate
+ * @param openMask mask used to open data.
+ * are invalid (corrupt, missing, etc).
+ * @return An InputStream object which can be used to read the target file.
+ * @throws IOException If an error occurs preparing the file.
+ */
+ InputStream getInputStream(int generation, int openMask) throws IOException {
+ if (inputFile != null) {
+ throw new IOException("Input stream already open"); //$NON-NLS-1$
+ }
+ int[] generations = getFileGenerations(referenceFile);
+ if (generations == null) {
+ throw new FileNotFoundException("File not found"); //$NON-NLS-1$
+ }
+ String name = referenceFile.getName();
+ File parent = new File(referenceFile.getParent());
+
+ boolean failOnPrimary = (openMask & OPEN_FAIL_ON_PRIMARY) != 0;
+ if (failOnPrimary && generation == GENERATIONS_INFINITE)
+ generation = generations[0];
+
+ File textFile = null;
+ InputStream textIS = null;
+ for (int idx = 0; idx < generations.length; idx++) {
+ if (generation != 0) {
+ if (generations[idx] > generation || (failOnPrimary && generations[idx] != generation))
+ continue;
+ }
+ File file;
+ if (generations[idx] != 0)
+ file = new File(parent, name + '.' + generations[idx]);
+ else
+ file = referenceFile;
+ InputStream is = null;
+ CacheInfo info;
+ synchronized (cacheFiles) {
+ info = (CacheInfo) cacheFiles.get(file);
+ long timeStamp = file.lastModified();
+ if (info == null || timeStamp != info.timeStamp) {
+ try {
+ is = new FileInputStream(file);
+ if (is.available() < maxInputStreamBuffer)
+ is = new BufferedInputStream(is);
+ Checksum cksum = getChecksumCalculator();
+ int filetype = getStreamType(is, cksum);
+ info = new CacheInfo(filetype, cksum, timeStamp);
+ cacheFiles.put(file, info);
+ } catch (IOException e) {/*ignore*/
+ }
+ }
+ }
+
+ // if looking for a specific generation only, only look at one
+ // and return the result.
+ if (failOnPrimary) {
+ if (info != null && info.filetype == FILETYPE_VALID) {
+ inputFile = file;
+ if (is != null)
+ return is;
+ return new FileInputStream(file);
+ }
+ throw new IOException("ReliableFile is corrupt"); //$NON-NLS-1$
+ }
+
+ // if error, ignore this file & try next
+ if (info == null)
+ continue;
+
+ // we're not looking for a specific version, so let's pick the best case
+ switch (info.filetype) {
+ case FILETYPE_VALID :
+ inputFile = file;
+ if (is != null)
+ return is;
+ return new FileInputStream(file);
+
+ case FILETYPE_NOSIGNATURE :
+ if (textFile == null) {
+ textFile = file;
+ textIS = is;
+ }
+ break;
+ }
+ }
+
+ // didn't find any valid files, if there are any plain text files
+ // use it instead
+ if (textFile != null) {
+ inputFile = textFile;
+ if (textIS != null)
+ return textIS;
+ return new FileInputStream(textFile);
+ }
+ throw new IOException("ReliableFile is corrupt"); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns an OutputStream object for writing the target file.
+ *
+ * @param append append new data to an existing file.
+ * @param appendGeneration specific generation of file to append from.
+ * @return An OutputStream object which can be used to write the target file.
+ * @throws IOException IOException If an error occurs preparing the file.
+ */
+ OutputStream getOutputStream(boolean append, int appendGeneration) throws IOException {
+ if (outputFile != null)
+ throw new IOException("Output stream is already open"); //$NON_NLS-1$ //$NON-NLS-1$
+ String name = referenceFile.getName();
+ File parent = new File(referenceFile.getParent());
+ File tmpFile = File.createTempFile(name, tmpExt, parent);
+
+ if (!append) {
+ OutputStream os = new FileOutputStream(tmpFile);
+ outputFile = tmpFile;
+ return os;
+ }
+
+ InputStream is;
+ try {
+ is = getInputStream(appendGeneration, OPEN_BEST_AVAILABLE);
+ } catch (FileNotFoundException e) {
+ OutputStream os = new FileOutputStream(tmpFile);
+ outputFile = tmpFile;
+ return os;
+ }
+
+ try {
+ CacheInfo info = (CacheInfo) cacheFiles.get(inputFile);
+ appendChecksum = info.checksum;
+ OutputStream os = new FileOutputStream(tmpFile);
+ if (info.filetype == FILETYPE_NOSIGNATURE) {
+ cp(is, os, 0);
+ } else {
+ cp(is, os, 16); // don't copy checksum signature
+ }
+ outputFile = tmpFile;
+ return os;
+ } finally {
+ closeInputFile();
+ }
+ }
+
+ /**
+ * Close the target file for reading.
+ *
+ * @param checksum Checksum of the file contents
+ * @throws IOException If an error occurs closing the file.
+ */
+ void closeOutputFile(Checksum checksum) throws IOException {
+ if (outputFile == null)
+ throw new IOException("Output stream is not open"); //$NON-NLS-1$
+ int[] generations = getFileGenerations(referenceFile);
+ String name = referenceFile.getName();
+ File parent = new File(referenceFile.getParent());
+ File newFile;
+ if (generations == null)
+ newFile = new File(parent, name + ".1"); //$NON-NLS-1$
+ else
+ newFile = new File(parent, name + '.' + (generations[0] + 1));
+
+ mv(outputFile, newFile); // throws IOException if problem
+ outputFile = null;
+ appendChecksum = null;
+ CacheInfo info = new CacheInfo(FILETYPE_VALID, checksum, newFile.lastModified());
+ cacheFiles.put(newFile, info);
+ cleanup(generations, true);
+ lastGenerationFile = null;
+ lastGenerations = null;
+ }
+
+ /**
+ * Abort the current output stream and do not update the reliable file table.
+ *
+ */
+ void abortOutputFile() {
+ if (outputFile == null)
+ return;
+ outputFile.delete();
+ outputFile = null;
+ appendChecksum = null;
+ }
+
+ File getOutputFile() {
+ return outputFile;
+ }
+
+ /**
+ * Close the target file for reading.
+ */
+ void closeInputFile() {
+ inputFile = null;
+ }
+
+ private void cleanup(int[] generations, boolean generationAdded) {
+ if (generations == null)
+ return;
+ String name = referenceFile.getName();
+ File parent = new File(referenceFile.getParent());
+ int generationCount = generations.length;
+ // if a base file is in the list (0 in generations[]), we will
+ // never delete these files, so don't count them in the old
+ // generation count.
+ if (generations[generationCount - 1] == 0)
+ generationCount--;
+ // assume here that the int[] does not include a file just created
+ int rmCount = generationCount - defaultMaxGenerations;
+ if (generationAdded)
+ rmCount++;
+ if (rmCount < 1)
+ return;
+ synchronized (cacheFiles) {
+ // first, see if any of the files not deleted are known to
+ // be corrupt. If so, be sure to keep not to delete good
+ // backup files.
+ for (int idx = 0, count = generationCount - rmCount; idx < count; idx++) {
+ File file = new File(parent, name + '.' + generations[idx]);
+ CacheInfo info = (CacheInfo) cacheFiles.get(file);
+ if (info != null) {
+ if (info.filetype == FILETYPE_CORRUPT)
+ rmCount--;
+ }
+ }
+ for (int idx = generationCount - 1; rmCount > 0; idx--, rmCount--) {
+ File rmFile = new File(parent, name + '.' + generations[idx]);
+ rmFile.delete();
+ cacheFiles.remove(rmFile);
+ }
+ }
+ }
+
+ /**
+ * Rename a file.
+ *
+ * @param from The original file.
+ * @param to The new file name.
+ * @throws IOException If the rename failed.
+ */
+ private static void mv(File from, File to) throws IOException {
+ if (!from.renameTo(to)) {
+ throw new IOException("rename failed"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Copy a file.
+ *
+ * @throws IOException If the copy failed.
+ */
+ private static void cp(InputStream in, OutputStream out, int truncateSize) throws IOException {
+ try {
+ int length = in.available();
+ if (truncateSize > length)
+ length = 0;
+ else
+ length -= truncateSize;
+ if (length > 0) {
+ int bufferSize;
+ if (length > BUF_SIZE) {
+ bufferSize = BUF_SIZE;
+ } else {
+ bufferSize = length;
+ }
+
+ byte buffer[] = new byte[bufferSize];
+ int size = 0;
+ int count;
+ while ((count = in.read(buffer, 0, length)) > 0) {
+ if ((size + count) >= length)
+ count = length - size;
+ out.write(buffer, 0, count);
+ size += count;
+ }
+ }
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {/*ignore*/
+ }
+ out.close();
+ }
+ }
+
+ /**
+ * Answers a boolean indicating whether or not the specified reliable file
+ * exists on the underlying file system. This call only returns if a file
+ * exists and not if the file contents are valid.
+ * @param file returns true if the specified reliable file exists; otherwise false is returned
+ *
+ * @return <code>true</code> if the specified reliable file exists,
+ * <code>false</code> otherwise.
+ */
+ public static boolean exists(File file) {
+ String prefix = file.getName() + '.';
+ File parent = new File(file.getParent());
+ int prefixLen = prefix.length();
+ String[] files = parent.list();
+ if (files == null)
+ return false;
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].startsWith(prefix)) {
+ try {
+ Integer.parseInt(files[i].substring(prefixLen));
+ return true;
+ } catch (NumberFormatException e) {/*ignore*/
+ }
+ }
+ }
+ return file.exists();
+ }
+
+ /**
+ * Returns the time that the reliable file was last modified. Only the time
+ * of the last file generation is returned.
+ * @param file the file to determine the time of.
+ * @return time the file was last modified (see java.io.File.lastModified()).
+ */
+ public static long lastModified(File file) {
+ int[] generations = getFileGenerations(file);
+ if (generations == null)
+ return 0L;
+ if (generations[0] == 0)
+ return file.lastModified();
+ String name = file.getName();
+ File parent = new File(file.getParent());
+ File newFile = new File(parent, name + '.' + generations[0]);
+ return newFile.lastModified();
+ }
+
+ /**
+ * Returns the time that this ReliableFile was last modified. This method is only valid
+ * after requesting an input stream and the time of the actual input file is returned.
+ *
+ * @return time the file was last modified (see java.io.File.lastModified()) or
+ * 0L if an input stream is not open.
+ */
+ public long lastModified() {
+ if (inputFile != null) {
+ return inputFile.lastModified();
+ }
+ return 0L;
+ }
+
+ /**
+ * Returns the a version number of a reliable managed file. The version can be expected
+ * to be unique for each successful file update.
+ *
+ * @param file the file to determine the version of.
+ * @return a unique version of this current file. A value of -1 indicates the file does
+ * not exist or an error occurred.
+ */
+ public static int lastModifiedVersion(File file) {
+ int[] generations = getFileGenerations(file);
+ if (generations == null)
+ return -1;
+ return generations[0];
+ }
+
+ /**
+ * Delete the specified reliable file on the underlying file system.
+ * @param deleteFile the reliable file to delete
+ *
+ * @return <code>true</code> if the specified reliable file was deleted,
+ * <code>false</code> otherwise.
+ */
+ public static boolean delete(File deleteFile) {
+ int[] generations = getFileGenerations(deleteFile);
+ if (generations == null)
+ return false;
+ String name = deleteFile.getName();
+ File parent = new File(deleteFile.getParent());
+ synchronized (cacheFiles) {
+ for (int idx = 0; idx < generations.length; idx++) {
+ // base files (.0 in generations[]) will never be deleted
+ if (generations[idx] == 0)
+ continue;
+ File file = new File(parent, name + '.' + generations[idx]);
+ if (file.exists()) {
+ file.delete();
+ }
+ cacheFiles.remove(file);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get a list of ReliableFile base names in a given directory. Only files with a valid
+ * ReliableFile generation are included.
+ * @param directory the directory to inquire.
+ * @return an array of ReliableFile names in the directory.
+ * @throws IOException if an error occurs.
+ */
+ public static String[] getBaseFiles(File directory) throws IOException {
+ if (!directory.isDirectory())
+ throw new IOException("Not a valid directory"); //$NON-NLS-1$
+ String files[] = directory.list();
+ HashSet list = new HashSet(files.length / 2);
+ for (int idx = 0; idx < files.length; idx++) {
+ String file = files[idx];
+ int pos = file.lastIndexOf('.');
+ if (pos == -1)
+ continue;
+ String ext = file.substring(pos + 1);
+ int generation = 0;
+ try {
+ generation = Integer.parseInt(ext);
+ } catch (NumberFormatException e) {/*skip*/
+ }
+ if (generation == 0)
+ continue;
+ String base = file.substring(0, pos);
+ list.add(base);
+ }
+ files = new String[list.size()];
+ int idx = 0;
+ for (Iterator iter = list.iterator(); iter.hasNext();) {
+ files[idx++] = (String) iter.next();
+ }
+ return files;
+ }
+
+ /**
+ * Delete any old excess generations of a given reliable file.
+ * @param base realible file.
+ */
+ public static void cleanupGenerations(File base) {
+ ReliableFile rf = new ReliableFile(base);
+ int[] generations = getFileGenerations(base);
+ rf.cleanup(generations, false);
+ lastGenerationFile = null;
+ lastGenerations = null;
+ }
+
+ /**
+ * Inform ReliableFile that a file has been updated outside of
+ * ReliableFile.
+ * @param file
+ */
+ public static void fileUpdated(File file) {
+ lastGenerationFile = null;
+ lastGenerations = null;
+ }
+
+ /**
+ * Append a checksum value to the end of an output stream.
+ * @param out the output stream.
+ * @param checksum the checksum value to append to the file.
+ * @throws IOException if a write error occurs.
+ */
+ void writeChecksumSignature(OutputStream out, Checksum checksum) throws IOException {
+ // tag on our signature and checksum
+ out.write(ReliableFile.identifier1);
+ out.write(intToHex((int) checksum.getValue()));
+ out.write(ReliableFile.identifier2);
+ }
+
+ /**
+ * Returns the size of the ReliableFile signature + CRC at the end of the file.
+ * This method should be called only after calling getInputStream() or
+ * getOutputStream() methods.
+ *
+ * @return <code>int</code> size of the ReliableFIle signature + CRC appended
+ * to the end of the file.
+ * @throws IOException if getInputStream() or getOutputStream has not been
+ * called.
+ */
+ int getSignatureSize() throws IOException {
+ if (inputFile != null) {
+ CacheInfo info = (CacheInfo) cacheFiles.get(inputFile);
+ if (info != null) {
+ switch (info.filetype) {
+ case FILETYPE_VALID :
+ case FILETYPE_CORRUPT :
+ return 16;
+ case FILETYPE_NOSIGNATURE :
+ return 0;
+ }
+ }
+ }
+ throw new IOException("ReliableFile signature size is unknown"); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a Checksum object for the current file contents. This method
+ * should be called only after calling getInputStream() or
+ * getOutputStream() methods.
+ *
+ * @return Object implementing Checksum interface initialized to the
+ * current file contents.
+ * @throws IOException if getOutputStream for append has not been called.
+ */
+ Checksum getFileChecksum() throws IOException {
+ if (appendChecksum == null)
+ throw new IOException("Checksum is invalid!"); //$NON-NLS-1$
+ return appendChecksum;
+ }
+
+ /**
+ * Create a checksum implementation used by ReliableFile.
+ *
+ * @return Object implementing Checksum interface used to calculate
+ * a reliable file checksum
+ */
+ Checksum getChecksumCalculator() {
+ // Using CRC32 because Adler32 isn't in the eeMinimum library.
+ return new CRC32();
+ }
+
+ /**
+ * Determine if a File is a valid ReliableFile
+ *
+ * @return <code>true</code> if the file is a valid ReliableFile
+ * @throws IOException If an error occurs verifying the file.
+ */
+ private int getStreamType(InputStream is, Checksum crc) throws IOException {
+ boolean markSupported = is.markSupported();
+ if (markSupported)
+ is.mark(is.available());
+ try {
+ int len = is.available();
+ if (len < 16) {
+ if (crc != null) {
+ byte data[] = new byte[16];
+ int num = is.read(data);
+ if (num > 0)
+ crc.update(data, 0, num);
+ }
+ return FILETYPE_NOSIGNATURE;
+ }
+ len -= 16;
+
+ int pos = 0;
+ byte data[] = new byte[BUF_SIZE];
+
+ while (pos < len) {
+ int read = data.length;
+ if (pos + read > len)
+ read = len - pos;
+
+ int num = is.read(data, 0, read);
+ if (num == -1) {
+ throw new IOException("Unable to read entire file."); //$NON-NLS-1$
+ }
+
+ crc.update(data, 0, num);
+ pos += num;
+ }
+
+ int num = is.read(data); // read last 16-byte signature
+ if (num != 16) {
+ throw new IOException("Unable to read entire file."); //$NON-NLS-1$
+ }
+
+ int i, j;
+ for (i = 0; i < 4; i++)
+ if (identifier1[i] != data[i]) {
+ crc.update(data, 0, 16); // update crc w/ sig bytes
+ return FILETYPE_NOSIGNATURE;
+ }
+ for (i = 0, j = 12; i < 4; i++, j++)
+ if (identifier2[i] != data[j]) {
+ crc.update(data, 0, 16); // update crc w/ sig bytes
+ return FILETYPE_NOSIGNATURE;
+ }
+ long crccmp;
+ try {
+ crccmp = Long.valueOf(new String(data, 4, 8, "UTF-8"), 16).longValue(); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ crccmp = Long.valueOf(new String(data, 4, 8), 16).longValue();
+ }
+ if (crccmp == crc.getValue()) {
+ return FILETYPE_VALID;
+ }
+ // do not update CRC
+ return FILETYPE_CORRUPT;
+ } finally {
+ if (markSupported)
+ is.reset();
+ }
+ }
+
+ private static byte[] intToHex(int l) {
+ byte[] buffer = new byte[8];
+ int count = 8;
+
+ do {
+ int ch = (l & 0xf);
+ if (ch > 9)
+ ch = ch - 10 + 'a';
+ else
+ ch += '0';
+ buffer[--count] = (byte) ch;
+ l >>= 4;
+ } while (count > 0);
+ return buffer;
+ }
+
+ private class CacheInfo {
+ int filetype;
+ Checksum checksum;
+ long timeStamp;
+
+ CacheInfo(int filetype, Checksum checksum, long timeStamp) {
+ this.filetype = filetype;
+ this.checksum = checksum;
+ this.timeStamp = timeStamp;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileInputStream.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileInputStream.java
new file mode 100644
index 00000000..5b61c8a5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileInputStream.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 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
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.cm.reliablefile;
+
+import java.io.*;
+
+//This is a copy of org.eclipse.osgi.framework.internal.reliablefile.ReliableFileInputStream
+
+/**
+ * A ReliableFile FileInputStream replacement class.
+ * This class can be used just like FileInputStream. The class
+ * is in partnership with ReliableFileOutputStream to avoid losing
+ * file data by using multiple files.
+ *
+ * @see ReliableFileOutputStream
+ */
+public class ReliableFileInputStream extends FilterInputStream {
+ /**
+ * ReliableFile object for this file.
+ */
+ private ReliableFile reliable;
+
+ /**
+ * size of crc and signature
+ */
+ private int sigSize;
+
+ /**
+ * current position reading from file
+ */
+ private int readPos;
+
+ /**
+ * total file length available for reading
+ */
+ private int length;
+
+ /**
+ * Constructs a new ReliableFileInputStream on the file named <code>name</code>. If the
+ * file does not exist, the <code>FileNotFoundException</code> is thrown.
+ * The <code>name</code> may be absolute or relative
+ * to the System property <code>"user.dir"</code>.
+ *
+ * @param name the file on which to stream reads.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileInputStream(String name) throws IOException {
+ this(ReliableFile.getReliableFile(name), ReliableFile.GENERATION_LATEST, ReliableFile.OPEN_BEST_AVAILABLE);
+ }
+
+ /**
+ * Constructs a new ReliableFileInputStream on the File <code>file</code>. If the
+ * file does not exist, the <code>FileNotFoundException</code> is thrown.
+ *
+ * @param file the File on which to stream reads.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileInputStream(File file) throws IOException {
+ this(ReliableFile.getReliableFile(file), ReliableFile.GENERATION_LATEST, ReliableFile.OPEN_BEST_AVAILABLE);
+ }
+
+ /**
+ * Constructs a new ReliableFileInputStream on the File <code>file</code>. If the
+ * file does not exist, the <code>FileNotFoundException</code> is thrown.
+ *
+ * @param file the File on which to stream reads.
+ * @param generation a specific generation requested.
+ * @param openMask mask used to open data.
+ * are invalid (corrupt, missing, etc).
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileInputStream(File file, int generation, int openMask) throws IOException {
+ this(ReliableFile.getReliableFile(file), generation, openMask);
+ }
+
+ /**
+ *
+ * @param reliable The ReliableFile on which to read.
+ * @param generation a specific generation requested.
+ * @param openMask mask used to open data.
+ * are invalid (corrupt, missing, etc).
+ * @throws IOException If an error occurs opening the file.
+ */
+ private ReliableFileInputStream(ReliableFile reliable, int generation, int openMask) throws IOException {
+ super(reliable.getInputStream(generation, openMask));
+
+ this.reliable = reliable;
+ sigSize = reliable.getSignatureSize();
+ readPos = 0;
+ length = super.available();
+ if (sigSize > length)
+ length = 0; // shouldn't ever happen
+ else
+ length -= sigSize;
+ }
+
+ /**
+ * Closes this input stream and releases any system resources associated
+ * with the stream.
+ *
+ * @exception java.io.IOException If an error occurs closing the file.
+ */
+ public synchronized void close() throws IOException {
+ if (reliable != null) {
+ try {
+ super.close();
+ } finally {
+ reliable.closeInputFile();
+ reliable = null;
+ }
+ }
+ }
+
+ /**
+ * Override default FilterInputStream method.
+ * @see FilterInputStream#read(byte[], int, int)
+ */
+ public synchronized int read(byte b[], int off, int len) throws IOException {
+ if (readPos >= length) {
+ return -1;
+ }
+ int num = super.read(b, off, len);
+
+ if (num != -1) {
+ if (num + readPos > length) {
+ num = length - readPos;
+ }
+ readPos += num;
+ }
+ return num;
+ }
+
+ /**
+ * Override default FilterInputStream method.
+ * @see FilterInputStream#read(byte[])
+ */
+ public synchronized int read(byte b[]) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * Override default FilterInputStream method.
+ * @see FilterInputStream#read()
+ */
+ public synchronized int read() throws IOException {
+ if (readPos >= length) {
+ return -1;
+ }
+ int num = super.read();
+
+ if (num != -1) {
+ readPos++;
+ }
+ return num;
+ }
+
+ /**
+ * Override default available method.
+ * @see FilterInputStream#available()
+ */
+ public synchronized int available() throws IOException {
+ if (readPos < length) // just in case
+ return (length - readPos);
+ return 0;
+ }
+
+ /**
+ * Override default skip method.
+ * @see FilterInputStream#skip(long)
+ */
+ public synchronized long skip(long n) throws IOException {
+ long len = super.skip(n);
+ if (readPos + len > length)
+ len = length - readPos;
+ readPos += len;
+ return len;
+ }
+
+ /**
+ * Override default markSupported method.
+ * @see FilterInputStream#markSupported()
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Override default mark method.
+ * @see FilterInputStream#mark(int)
+ */
+ public void mark(int readlimit) {
+ //ignore
+ }
+
+ /**
+ * Override default reset method.
+ * @see FilterInputStream#reset()
+ */
+ public void reset() throws IOException {
+ throw new IOException("reset not supported."); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileOutputStream.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileOutputStream.java
new file mode 100644
index 00000000..9a39a1a0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/reliablefile/ReliableFileOutputStream.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 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
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.cm.reliablefile;
+
+import java.io.*;
+import java.util.zip.Checksum;
+
+//This is a copy of org.eclipse.osgi.framework.internal.reliablefile.ReliableFileOutputStream
+
+/**
+ * A ReliableFile FileOutputStream replacement class.
+ * This class can be used just like FileOutputStream. The class
+ * is in partnership with ReliableFileInputStream to avoid losing
+ * file data by using multiple files.
+ *
+ * @see ReliableFileInputStream
+ */
+public class ReliableFileOutputStream extends FilterOutputStream {
+ /**
+ * ReliableFile object for the file.
+ */
+ private ReliableFile reliable;
+
+ /**
+ * Checksum calculator
+ */
+ private Checksum crc;
+
+ private boolean outputOpen = false;
+
+ /**
+ * Constructs a new ReliableFileOutputStream on the File <code>file</code>. If the
+ * file exists, it is written over. See the constructor which can append to
+ * the file if so desired.
+ *
+ * @param file the File on which to stream reads.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileOutputStream(File file) throws IOException {
+ this(ReliableFile.getReliableFile(file), false);
+ }
+
+ /**
+ * Constructs a new ReliableFileOutputStream on the File <code>file</code>.
+ *
+ * @param file the File on which to stream reads.
+ * @param append a boolean indicating whether or not to append to an existing file.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileOutputStream(File file, boolean append) throws IOException {
+ this(ReliableFile.getReliableFile(file), append);
+ }
+
+ /**
+ * Constructs a new ReliableFileOutputStream on the file named <code>name</code>. If
+ * the file exists, it is written over. See the constructor which can append to
+ * the file if so desired.
+ * The <code>name</code> may be absolute or relative
+ * to the System property <code>"user.dir"</code>.
+ *
+ * @param name the file on which to stream writes.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileOutputStream(String name) throws IOException {
+ this(ReliableFile.getReliableFile(name), false);
+ }
+
+ /**
+ * Constructs a new ReliableFileOutputStream on the file named <code>name</code>.
+ * The <code>name</code> may be absolute or relative
+ * to the System property <code>"user.dir"</code>.
+ *
+ * @param name the file on which to stream writes.
+ * @param append a boolean indicating whether or not to append to an existing file.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ public ReliableFileOutputStream(String name, boolean append) throws IOException {
+ this(ReliableFile.getReliableFile(name), append);
+ }
+
+ /**
+ * Private constructor used by other constructors.
+ *
+ * @param reliable the ReliableFile on which to read.
+ * @param append a boolean indicating whether or not to append to an existing file.
+ * @exception java.io.IOException If an error occurs opening the file.
+ */
+ private ReliableFileOutputStream(ReliableFile reliable, boolean append) throws IOException {
+ super(reliable.getOutputStream(append, ReliableFile.GENERATION_LATEST));
+
+ this.reliable = reliable;
+ outputOpen = true;
+ if (append)
+ crc = reliable.getFileChecksum();
+ else
+ crc = reliable.getChecksumCalculator();
+ }
+
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with this stream. The general contract of <code>close</code>
+ * is that it closes the output stream. A closed stream cannot perform
+ * output operations and cannot be reopened.
+ *
+ * @exception java.io.IOException If an error occurs closing the file.
+ */
+ public synchronized void close() throws IOException {
+ closeIntermediateFile();
+ reliable.closeOutputFile(crc);
+ // if the previouse closeOutpuFile() throws exception,
+ // we don't null out reliable to give another opportunity
+ // to rename the file.
+ reliable = null;
+ }
+
+ public File closeIntermediateFile() throws IOException {
+ if (reliable == null)
+ throw new IOException("ReliableFile stream not open"); //$NON-NLS-1$
+ if (outputOpen) {
+ // tag on our signature and checksum
+ reliable.writeChecksumSignature(out, crc);
+ out.flush();
+ try {
+ ((FileOutputStream) out).getFD().sync();
+ } catch (IOException e) {
+ // just ignore this Exception
+ //Debug
+ e.printStackTrace();
+ }
+ out.close();
+ outputOpen = false;
+ }
+ return reliable.getOutputFile();
+ }
+
+ /**
+ * Override default FilterOutputStream method.
+ * @see FilterOutputStream#write(byte[])
+ */
+ public void write(byte[] b) throws IOException {
+ this.write(b, 0, b.length);
+ }
+
+ /**
+ * Override default FilterOutputStream method.
+ * @see FilterOutputStream#write(byte[], int, int)
+ */
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ crc.update(b, off, len);
+ }
+
+ /**
+ * Override default FilterOutputStream method.
+ * @see FilterOutputStream#write(int)
+ */
+ public void write(int b) throws IOException {
+ out.write(b);
+ crc.update((byte) b);
+ }
+
+ public void abort() {
+ if (reliable == null)
+ return;
+ if (outputOpen) {
+ try {
+ out.close();
+ } catch (IOException e) {/*ignore*/
+ }
+ outputOpen = false;
+ }
+ reliable.abortOutputFile();
+ reliable = null;
+ }
+}

Back to the top