[FELIX-3544] Add a BaseManagedServiceFactory to help writing such factories

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1348938 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/utils/src/main/java/org/apache/felix/utils/service/BaseManagedServiceFactory.java b/utils/src/main/java/org/apache/felix/utils/service/BaseManagedServiceFactory.java
new file mode 100644
index 0000000..890a2ae
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/service/BaseManagedServiceFactory.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.utils.service;
+
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+
+public abstract class BaseManagedServiceFactory<T> implements ManagedServiceFactory {
+
+    public static final long DEFAULT_TIMEOUT_BEFORE_INTERRUPT = 30000;
+
+    private final BundleContext context;
+    private final String name;
+    private final long timeoutBeforeInterrupt;
+    private final AtomicBoolean destroyed;
+    private final ExecutorService executor;
+    private final Map<String, Pair<T, ServiceRegistration>> services;
+
+    public BaseManagedServiceFactory(BundleContext context, String name) {
+        this(context, name, DEFAULT_TIMEOUT_BEFORE_INTERRUPT);
+    }
+
+    public BaseManagedServiceFactory(BundleContext context, String name, long timeoutBeforeInterrupt) {
+        this.context = context;
+        this.name = name;
+        this.timeoutBeforeInterrupt = timeoutBeforeInterrupt;
+        this.destroyed = new AtomicBoolean(false);
+        this.executor = Executors.newSingleThreadExecutor();
+        this.services = new ConcurrentHashMap<String, Pair<T, ServiceRegistration>>();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void updated(final String pid, final Dictionary properties) throws ConfigurationException {
+        if (destroyed.get()) {
+            return;
+        }
+        checkConfiguration(pid, properties);
+        executor.submit(new Runnable() {
+            public void run() {
+                try {
+                    internalUpdate(pid, properties);
+                } catch (Throwable t) {
+                    warn("Error destroying service for ManagedServiceFactory " + getName(), t);
+                }
+            }
+        });
+    }
+
+    public void deleted(final String pid) {
+        if (destroyed.get()) {
+            return;
+        }
+        executor.submit(new Runnable() {
+            public void run() {
+                try {
+                    internalDelete(pid);
+                } catch (Throwable throwable) {
+                    warn("Error destroying service for ManagedServiceFactory " + getName(), throwable);
+                }
+            }
+        });
+    }
+
+    protected void checkConfiguration(String pid, Dictionary properties) throws ConfigurationException {
+        // Do nothing
+    }
+
+    protected abstract T doCreate(Dictionary properties) throws Exception;
+
+    protected T doUpdate(T t, Dictionary properties) throws Exception {
+        doDestroy(t);
+        return doCreate(properties);
+    }
+
+    protected abstract void doDestroy(T t) throws Exception;
+
+    protected abstract String[] getExposedClasses(T t);
+
+    private void internalUpdate(String pid, Dictionary properties) {
+        Pair<T, ServiceRegistration> pair = services.get(pid);
+        if (pair != null) {
+            try {
+                T t = doUpdate(pair.getFirst(), properties);
+                pair.setFirst(t);
+                pair.getSecond().setProperties(properties);
+            } catch (Throwable throwable) {
+                internalDelete(pid);
+                warn("Error updating service for ManagedServiceFactory " + getName(), throwable);
+            }
+        } else {
+            if (destroyed.get()) {
+                return;
+            }
+            try {
+                T t = doCreate(properties);
+                try {
+                    if (destroyed.get()) {
+                        throw new IllegalStateException("ManagedServiceFactory has been destroyed");
+                    }
+                    ServiceRegistration registration = context.registerService(getExposedClasses(t), t, properties);
+                    services.put(pid, new Pair<T, ServiceRegistration>(t, registration));
+                } catch (Throwable throwable1) {
+                    try {
+                        doDestroy(t);
+                    } catch (Throwable throwable2) {
+                        // Ignore
+                    }
+                    throw throwable1;
+                }
+            } catch (Throwable throwable) {
+                warn("Error creating service for ManagedServiceFactory " + getName(), throwable);
+            }
+        }
+    }
+
+    private void internalDelete(String pid) {
+        Pair<T, ServiceRegistration> pair = services.remove(pid);
+        if (pair != null) {
+            try {
+                pair.getSecond().unregister();
+            } catch (Throwable t) {
+                info("Error unregistering service", t);
+            }
+            try {
+                doDestroy(pair.getFirst());
+            } catch (Throwable t) {
+                info("Error destroying service", t);
+            }
+        }
+    }
+
+    protected abstract void warn(String message, Throwable t);
+
+    protected abstract void info(String message, Throwable t);
+
+    public void destroy() {
+        if (destroyed.compareAndSet(false, true)) {
+            executor.shutdown();
+            try {
+                executor.awaitTermination(timeoutBeforeInterrupt, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Shutdown interrupted");
+            }
+            if (!executor.isTerminated()) {
+                executor.shutdownNow();
+                try {
+                    executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException("Shutdown interrupted");
+                }
+            }
+
+            while (!services.isEmpty()) {
+                String pid = services.keySet().iterator().next();
+                internalDelete(pid);
+            }
+        }
+    }
+
+    static class Pair<U,V> {
+        private U first;
+        private V second;
+        public Pair(U first, V second) {
+            this.first = first;
+            this.second = second;
+        }
+        public U getFirst() {
+            return first;
+        }
+        public V getSecond() {
+            return second;
+        }
+        public void setFirst(U first) {
+            this.first = first;
+        }
+        public void setSecond(V second) {
+            this.second = second;
+        }
+    }
+
+}