Patches related to FELIX-1324 create BundleForm class to encapsulate a certain bundle configuration - Client.apply(BundleForm) merges the specified form into the currently running framework configuration (i.e. applies delta to currently running bundles)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@830559 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/sigil/common/runtime/sigil.properties b/sigil/common/runtime/sigil.properties
index 6705725..23156cc 100644
--- a/sigil/common/runtime/sigil.properties
+++ b/sigil/common/runtime/sigil.properties
@@ -14,7 +14,8 @@
 -imports: \
 	org.apache.felix.sigil.common.runtime,\
 	org.osgi.framework, \
-	org.osgi.framework.launch;resolve=compile;version=1.0.0, \
+	org.osgi.framework.launch, \
+	org.osgi.service.packageadmin, \
 
 header;Main-Class: org.apache.felix.sigil.common.runtime.Main
 
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/BundleForm.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/BundleForm.java
new file mode 100644
index 0000000..57c1ed7
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/BundleForm.java
@@ -0,0 +1,197 @@
+/*
+ * 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.sigil.common.runtime;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+public class BundleForm
+{    
+    public static class BundleStatus
+    {
+        private String location;
+        private String bundleSymbolicName;
+        private String version;
+        private long id;
+        private int status;
+        
+        public String getLocation()
+        {
+            return location;
+        }
+        
+        public void setLocation(String location)
+        {
+            this.location = location;
+        }
+        
+        public String getBundleSymbolicName()
+        {
+            return bundleSymbolicName;
+        }
+        
+        public void setBundleSymbolicName(String bundleSymbolicName)
+        {
+            this.bundleSymbolicName = bundleSymbolicName;
+        }
+        
+        public String getVersion()
+        {
+            return version;
+        }
+        
+        public void setVersion(String version)
+        {
+            this.version = version;
+        }
+        
+        public long getId()
+        {
+            return id;
+        }
+        
+        public void setId(long id)
+        {
+            this.id = id;
+        }
+        
+        public void setStatus(int status)
+        {
+            this.status = status;
+        }    
+        
+        public int getStatus() {
+            return status;
+        }
+
+        public boolean isMatch(BundleStatus n)
+        {
+            return bundleSymbolicName.equals( n.bundleSymbolicName ) && version.equals(n.version);
+        }
+    }
+    
+    private String[] bundles;
+    private Set<String> startMap = new HashSet<String>();
+
+    public BundleForm() {
+    }
+    
+    public static BundleForm resolve(URL formURL) throws IOException, URISyntaxException {
+        InputStream in = formURL.openStream();
+        try {
+            BundleForm f = new BundleForm();
+            BufferedReader r = new BufferedReader(new InputStreamReader(in));
+            LinkedList<String> locs = new LinkedList<String>();
+            for(;;) {
+                String l = r.readLine();
+                if ( l == null ) break;
+                URI uri = URI.create(l);
+                String status = uri.getScheme();
+                uri = URI.create(uri.getSchemeSpecificPart());
+                String loc = uri.toString();
+                locs.add( loc );
+                f.setStarted(loc, "start".equalsIgnoreCase(status) );
+            }
+            f.setBundles(locs.toArray(new String[locs.size()]));
+            return f;
+        }
+        finally {
+            try
+            {
+                in.close();
+            }
+            catch (IOException e)
+            {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void setBundles(String[] bundles) {
+        this.bundles = bundles;
+    }
+    
+    public String[] getBundles()
+    {
+        return bundles;
+    }
+
+    public boolean isStarted(String url)
+    {
+        return startMap.contains(url);
+    }
+    
+    public void setStarted(String url, boolean started) {
+        if ( started ) {
+            startMap.add(url);
+        }
+        else {
+            startMap.remove(url);
+        }
+    }
+    
+    public BundleStatus[] toStatus() throws IOException  {
+        ArrayList<BundleStatus> ret = new ArrayList<BundleStatus>(bundles.length);
+        for ( String loc : bundles ) {
+            URL url = new URL(loc);
+            InputStream in = url.openStream();
+            try {
+                JarInputStream jin = new JarInputStream(in);
+                Manifest mf = jin.getManifest();
+                Attributes attr = mf.getMainAttributes();
+                String bsn = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
+                String ver = attr.getValue(Constants.BUNDLE_VERSION);
+                BundleStatus st = new BundleStatus();
+                st.setBundleSymbolicName(bsn);
+                st.setVersion(ver);
+                st.setLocation(loc);
+                st.setStatus(isStarted(loc) ? Bundle.ACTIVE : Bundle.INSTALLED);
+                ret.add(st);
+            }
+            finally {
+                try
+                {
+                    in.close();
+                }
+                catch (IOException e)
+                {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+        }
+        return ret.toArray(new BundleStatus[ret.size()] );
+    }
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java
index 4b6c955..807b783 100644
--- a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java
@@ -26,16 +26,18 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.util.Map;
 import java.util.Properties;
 
+import org.apache.felix.sigil.common.runtime.BundleForm.BundleStatus;
 import org.apache.felix.sigil.common.runtime.io.InstallAction;
+import org.apache.felix.sigil.common.runtime.io.RefreshAction;
 import org.apache.felix.sigil.common.runtime.io.StartAction;
 import org.apache.felix.sigil.common.runtime.io.StatusAction;
 import org.apache.felix.sigil.common.runtime.io.StopAction;
 import org.apache.felix.sigil.common.runtime.io.UninstallAction;
 import org.apache.felix.sigil.common.runtime.io.UpdateAction;
 import org.apache.felix.sigil.common.runtime.io.UpdateAction.Update;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
 
 import static org.apache.felix.sigil.common.runtime.Runtime.PORT_PROPERTY;
@@ -90,6 +92,27 @@
         out = null;
     }
 
+    public void apply(BundleForm form) throws IOException, BundleException {
+        BundleStatus[] newStatus = form.toStatus();
+
+        BundleStatus[] currentStatus = status();
+        
+        stopOldBundles(currentStatus, newStatus);
+        
+        boolean change = uninstallOldBundles(currentStatus, newStatus);
+        change |= installNewBundles(currentStatus, newStatus);
+        
+        if ( change )
+            refresh();
+        
+        startNewBundles(newStatus);
+    }
+
+    public void refresh() throws IOException, BundleException
+    {
+        if ( socket == null ) throw new IllegalStateException( "Not connected" );
+        new RefreshAction( in, out ).client();
+    }
 
     public long install( String url ) throws IOException, BundleException
     {
@@ -135,9 +158,84 @@
     }
 
 
-    public Map<Long, String> status() throws IOException, BundleException
+    public BundleStatus[] status() throws IOException, BundleException
     {
         if ( socket == null ) throw new IllegalStateException( "Not connected" );
         return new StatusAction( in, out ).client();
     }
+    
+    private boolean installNewBundles(BundleStatus[] status, BundleStatus[] newStatus) throws IOException, BundleException
+    {
+        boolean change = false;
+        for (BundleStatus n : newStatus) {
+            boolean found = false;
+            for ( BundleStatus o : status ) {
+                if ( o.isMatch(n) ) {
+                    update(o.getId(), n.getLocation());
+                    found = true;
+                    change = true;
+                    break;
+                }
+            }
+            
+            if ( !found ) {
+                install(n.getLocation());
+                change = true;
+            }
+        }
+        
+        return change;
+    }
+
+    private void startNewBundles(BundleStatus[] newStatus) throws IOException, BundleException
+    {
+        BundleStatus[] status = status();
+        for (BundleStatus n : newStatus) {
+            if ( n.getStatus() == Bundle.ACTIVE ) {
+                for ( BundleStatus o : status ) {
+                    if ( o.isMatch(n) ) {
+                        start(o.getId());
+                    }
+                }
+            }            
+        }
+    }
+
+    private void stopOldBundles(BundleStatus[] status, BundleStatus[] newStatus) throws IOException, BundleException
+    {
+        for (BundleStatus n : newStatus) {
+            if ( n.getStatus() == Bundle.INSTALLED ) {
+                for ( BundleStatus o : status ) {
+                    if ( o.getId() != 0 ) {
+                        if ( o.isMatch(n) ) {
+                            stop(o.getId());
+                        }                        
+                    }
+                }
+            }            
+        }
+    }
+
+    private boolean uninstallOldBundles(BundleStatus[] status, BundleStatus[] newStatus) throws IOException, BundleException
+    {
+        boolean change = false;
+        for ( BundleStatus o : status ) {
+            if ( o.getId() != 0 ) {
+                boolean found = false;
+                
+                for (BundleStatus n : newStatus) {
+                    if ( o.isMatch(n) ) {
+                        found = true;
+                        break;
+                    }
+                }
+                
+                if ( !found ) {
+                    change = true;
+                    uninstall(o.getId());
+                }
+            }
+        }
+        return change;
+    }    
 }
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Runtime.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Runtime.java
index 87f3bfb..7fbf762 100644
--- a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Runtime.java
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Runtime.java
@@ -19,7 +19,7 @@
 
 package org.apache.felix.sigil.common.runtime;
 
-public class Runtime
+public interface Runtime
 {
     public static final String PORT_PROPERTY = "port";
     public static final String ADDRESS_PROPERTY = "address";
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java
index 36cef3a..6d37887 100644
--- a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java
@@ -35,6 +35,7 @@
 
 import org.apache.felix.sigil.common.runtime.io.Action;
 import org.apache.felix.sigil.common.runtime.io.InstallAction;
+import org.apache.felix.sigil.common.runtime.io.RefreshAction;
 import org.apache.felix.sigil.common.runtime.io.StartAction;
 import org.apache.felix.sigil.common.runtime.io.StatusAction;
 import org.apache.felix.sigil.common.runtime.io.StopAction;
@@ -164,6 +165,9 @@
                         case STATUS:
                             task = new StatusAction( in, out );
                             break;
+                        case REFRESH:
+                            task = new RefreshAction(in, out);
+                            break;
                     }
                     
                     if ( task == null ) {
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java
index 1d1e788..d3221a8 100644
--- a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java
@@ -31,4 +31,5 @@
     public static final int UNINSTALL = 4;
     public static final int UPDATE = 5;
     public static final int STATUS = 6;
+    public static final int REFRESH = 7;
 }
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/RefreshAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/RefreshAction.java
new file mode 100644
index 0000000..9f0064d
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/RefreshAction.java
@@ -0,0 +1,75 @@
+/*
+ * 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.sigil.common.runtime.io;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.REFRESH;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class RefreshAction extends Action<Void, Void>
+{
+
+    public RefreshAction(DataInputStream in, DataOutputStream out) throws IOException
+    {
+        super(in, out);
+    }
+
+    @Override
+    public Void client(Void input) throws IOException, BundleException
+    {
+        writeInt( REFRESH );
+        flush();
+        if ( checkOk() )
+        {
+            return null;
+        }
+        else
+        {
+            String msg = readString();
+            throw new BundleException( msg );
+        }
+    }
+
+    @Override
+    public void server(Framework fw) throws IOException
+    {
+        ServiceReference ref = fw.getBundleContext().getServiceReference(PackageAdmin.class.getName());
+        if ( ref != null ) {
+            PackageAdmin pa = (PackageAdmin) fw.getBundleContext().getService(ref);
+            if ( pa != null ) {
+                try {
+                    pa.refreshPackages(null);
+                }
+                finally {
+                    fw.getBundleContext().ungetService(ref);
+                }
+            }
+        }
+        writeOk();
+        flush();
+    }
+
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java
index 5b22c07..8776960 100644
--- a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java
@@ -21,23 +21,25 @@
 
 
 import java.io.DataInputStream;
+
 import java.io.DataOutputStream;
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
 
+import org.apache.felix.sigil.common.runtime.BundleForm.BundleStatus;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.launch.Framework;
 
 import static org.apache.felix.sigil.common.runtime.io.Constants.STATUS;
 import static org.osgi.framework.Constants.BUNDLE_VERSION;
+import static org.osgi.framework.Constants.BUNDLE_NAME;
 
 
 /**
  * @author dave
  *
  */
-public class StatusAction extends Action<Void, Map<Long, String>>
+public class StatusAction extends Action<Void, BundleStatus[]>
 {
 
     public StatusAction( DataInputStream in, DataOutputStream out ) throws IOException
@@ -47,20 +49,24 @@
 
 
     @Override
-    public Map<Long, String> client( Void in ) throws IOException
+    public BundleStatus[] client( Void in ) throws IOException
     {
         writeInt(STATUS);
         flush();
         int num = readInt();
-        HashMap<Long, String> map = new HashMap<Long, String>(num);
+        ArrayList<BundleStatus> ret = new ArrayList<BundleStatus>(num);
         
         for (int i = 0; i < num; i++) {
-            long id = readLong();
-            String symbol = readString();
-            map.put( id, symbol );
+            BundleStatus s = new BundleStatus();
+            s.setId(readLong());
+            s.setBundleSymbolicName(readString());
+            s.setVersion(readString());
+            s.setLocation(readString());
+            s.setStatus(readInt());
+            ret.add(s);
         }
         
-        return map;
+        return ret.toArray(new BundleStatus[num]);
     }
 
 
@@ -72,25 +78,17 @@
         writeInt( bundles.length );
         for ( Bundle b : bundles ) {
             writeLong(b.getBundleId());
-            String symbol = b.getSymbolicName() + ":" + b.getHeaders().get( BUNDLE_VERSION ) + ":" + state(b);
-            writeString(symbol);
+            System.out.println( "Written " + b.getBundleId());
+            String bsn = b.getSymbolicName();
+            if ( bsn == null )
+                bsn = (String) b.getHeaders().get(BUNDLE_NAME);
+            
+            writeString(bsn);
+            writeString((String) b.getHeaders().get( BUNDLE_VERSION ));
+            writeString(b.getLocation());
+            writeInt(b.getState());
+            flush();
         }
-        
-        flush();
+        flush();        
     }
-
-
-    private String state( Bundle b )
-    {
-        switch ( b.getState() ) {
-            case Bundle.ACTIVE: return "active";
-            case Bundle.INSTALLED: return "installed";
-            case Bundle.RESOLVED: return "resolved";
-            case Bundle.STARTING: return "starting";
-            case Bundle.STOPPING: return "stopping";
-            case Bundle.UNINSTALLED: return "uninstalled";
-            default: return "unknown";
-        }
-    }
-
 }