You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/04/11 19:20:48 UTC

[26/33] Revert "[KARAF-2852] Merge features/core and features/command"

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
new file mode 100644
index 0000000..56e5102
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
@@ -0,0 +1,102 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.URI;
+
+import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JaxbUtil;
+
+/**
+ * The repository implementation.
+ */
+public class RepositoryImpl implements Repository {
+
+    private final URI uri;
+    private Features features;
+
+    public RepositoryImpl(URI uri) {
+        this.uri = uri;
+    }
+
+    public URI getURI() {
+        return uri;
+    }
+
+    public String getName() throws IOException {
+        load();
+        return features.getName();
+    }
+
+    public URI[] getRepositories() throws Exception {
+        load();
+        URI[] result = new URI[features.getRepository().size()];
+        for (int i = 0; i < features.getRepository().size(); i++) {
+            String uri = features.getRepository().get(i);
+            uri = uri.trim();
+            result[i] = URI.create(uri);
+        }
+        return result;
+    }
+
+    public org.apache.karaf.features.Feature[] getFeatures() throws Exception {
+        load();
+        return features.getFeature().toArray(new org.apache.karaf.features.Feature[features.getFeature().size()]);
+    }
+
+
+    public void load() throws IOException {
+        load(false);
+    }
+
+    public void load(boolean validate) throws IOException {
+        if (features == null) {
+            try {
+                InputStream inputStream = uri.toURL().openStream();
+                inputStream = new FilterInputStream(inputStream) {
+    				@Override
+    				public int read(byte[] b, int off, int len) throws IOException {
+    					if (Thread.currentThread().isInterrupted()) {
+    						throw new InterruptedIOException();
+    					}
+    					return super.read(b, off, len);
+    				}
+    			};
+                try {
+                    features = JaxbUtil.unmarshal(uri.toASCIIString(), inputStream, validate);
+                } finally {
+                    inputStream.close();
+                }
+            } catch (IllegalArgumentException e) {
+                throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
+            } catch (Exception e) {
+                throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        throw new UnsupportedOperationException();
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java
new file mode 100644
index 0000000..96b99ee
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java
@@ -0,0 +1,84 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.karaf.features.internal.resolver.CapabilitySet;
+import org.apache.karaf.features.internal.resolver.SimpleFilter;
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class RequirementSort<T extends Resource>  {
+
+    /**
+     * Sorts {@link Resource} based on their {@link Requirement}s and {@link Capability}s.
+     */
+    public static <T extends Resource> Collection<T> sort(Collection<T> resources) {
+        Set<String> namespaces = new HashSet<String>();
+        for (Resource r : resources) {
+            for (Capability cap : r.getCapabilities(null)) {
+                namespaces.add(cap.getNamespace());
+            }
+        }
+        CapabilitySet capSet = new CapabilitySet(new ArrayList<String>(namespaces));
+        for (Resource r : resources) {
+            for (Capability cap : r.getCapabilities(null)) {
+                capSet.addCapability(cap);
+            }
+        }
+        Set<T> sorted = new LinkedHashSet<T>();
+        Set<T> visited = new LinkedHashSet<T>();
+        for (T r : resources) {
+            visit(r, visited, sorted, capSet);
+        }
+        return sorted;
+    }
+
+
+    private static <T extends Resource> void visit(T resource, Set<T> visited, Set<T> sorted, CapabilitySet capSet) {
+        if (!visited.add(resource)) {
+            return;
+        }
+        for (T r : collectDependencies(resource, capSet)) {
+            visit(r, visited, sorted, capSet);
+        }
+        sorted.add(resource);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T extends Resource> Set<T> collectDependencies(T resource, CapabilitySet capSet) {
+        Set<T> result = new LinkedHashSet<T>();
+        for (Requirement requirement : resource.getRequirements(null)) {
+            String filter = requirement.getDirectives().get(Constants.FILTER_DIRECTIVE);
+            SimpleFilter sf = (filter != null)
+                    ? SimpleFilter.parse(filter)
+                    : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+            for (Capability cap : capSet.match(sf, true)) {
+                result.add((T) cap.getResource());
+            }
+        }
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java
new file mode 100644
index 0000000..d1f16b9
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java
@@ -0,0 +1,51 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.karaf.features.internal.deployment.Downloader;
+import org.apache.karaf.features.internal.deployment.StreamProvider;
+import org.apache.karaf.features.internal.util.MultiException;
+
+public class SimpleDownloader implements Downloader {
+
+    private final MultiException exception = new MultiException("Error");
+
+    @Override
+    public void await() throws InterruptedException, MultiException {
+        exception.throwIfExceptions();
+    }
+
+    @Override
+    public void download(final String location, final DownloadCallback downloadCallback) throws MalformedURLException {
+        final URL url = new URL(location);
+        try {
+            downloadCallback.downloaded(new StreamProvider() {
+                @Override
+                public InputStream open() throws IOException {
+                    return url.openStream();
+                }
+            });
+        } catch (Exception e) {
+            exception.addException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
new file mode 100644
index 0000000..c84f4e0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
@@ -0,0 +1,34 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class State {
+
+    public final AtomicBoolean bootDone = new AtomicBoolean();
+    public final Set<String> repositories = new TreeSet<String>();
+    public final Set<String> features = new TreeSet<String>();
+    public final Set<String> installedFeatures = new TreeSet<String>();
+    public final Set<Long> managedBundles = new TreeSet<Long>();
+    public final Map<String, Long> bundleChecksums = new HashMap<String, Long>();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
new file mode 100644
index 0000000..ac54ab0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
@@ -0,0 +1,175 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.karaf.features.Feature;
+
+public abstract class StateStorage {
+
+    public void load(State state) throws IOException {
+        state.repositories.clear();
+        state.features.clear();
+        state.installedFeatures.clear();
+        state.managedBundles.clear();
+        InputStream is = getInputStream();
+        if (is != null) {
+            try {
+                Properties props = new Properties();
+                props.load(is);
+                state.bootDone.set(loadBool(props, "bootDone"));
+                state.repositories.addAll(loadSet(props, "repositories."));
+                state.features.addAll(loadSet(props, "features."));
+                state.installedFeatures.addAll(loadSet(props, "installed."));
+                state.managedBundles.addAll(toLongSet(loadSet(props, "managed.")));
+                state.bundleChecksums.putAll(toStringLongMap(loadMap(props, "checksums.")));
+            } finally {
+                close(is);
+            }
+        }
+    }
+
+    public void save(State state) throws IOException {
+        OutputStream os = getOutputStream();
+        if (os != null) {
+            try {
+                Properties props = new Properties();
+                saveBool(props, "bootDone", state.bootDone.get());
+                saveSet(props, "repositories.", state.repositories);
+                saveSet(props, "features.", state.features);
+                saveSet(props, "installed.", state.installedFeatures);
+                saveSet(props, "managed.", toStringSet(state.managedBundles));
+                saveMap(props, "checksums.", toStringStringMap(state.bundleChecksums));
+                props.store(os, "FeaturesService State");
+            } finally {
+                close(os);
+            }
+        }
+    }
+
+    protected abstract InputStream getInputStream() throws IOException;
+    protected abstract OutputStream getOutputStream() throws IOException;
+
+    protected boolean loadBool(Properties props, String key) {
+        return Boolean.parseBoolean(props.getProperty(key));
+    }
+
+    protected void saveBool(Properties props, String key, boolean val) {
+        props.setProperty(key, Boolean.toString(val));
+    }
+
+    protected Set<String> toStringSet(Set<Long> set) {
+        Set<String> ns = new TreeSet<String>();
+        for (long l : set) {
+            ns.add(Long.toString(l));
+        }
+        return ns;
+    }
+
+    protected Set<Long> toLongSet(Set<String> set) {
+        Set<Long> ns = new TreeSet<Long>();
+        for (String s : set) {
+            ns.add(Long.parseLong(s));
+        }
+        return ns;
+    }
+
+    protected void saveSet(Properties props, String prefix, Set<String> set) {
+        List<String> l = new ArrayList<String>(set);
+        props.put(prefix + "count", Integer.toString(l.size()));
+        for (int i = 0; i < l.size(); i++) {
+            props.put(prefix + "item." + i, l.get(i));
+        }
+    }
+
+    protected Set<String> loadSet(Properties props, String prefix) {
+        Set<String> l = new HashSet<String>();
+        String countStr = (String) props.get(prefix + "count");
+        if (countStr != null) {
+            int count = Integer.parseInt(countStr);
+            for (int i = 0; i < count; i++) {
+                l.add((String) props.get(prefix + "item." + i));
+            }
+        }
+        return l;
+    }
+
+    protected Map<String, String> toStringStringMap(Map<String, Long> map) {
+        Map<String, String> nm = new HashMap<String, String>();
+        for (Map.Entry<String, Long> entry : map.entrySet()) {
+            nm.put(entry.getKey(), Long.toString(entry.getValue()));
+        }
+        return nm;
+    }
+
+    protected Map<String, Long> toStringLongMap(Map<String, String> map) {
+        Map<String, Long> nm = new HashMap<String, Long>();
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            nm.put(entry.getKey(), Long.parseLong(entry.getValue()));
+        }
+        return nm;
+    }
+
+
+    protected void saveMap(Properties props, String prefix, Map<String, String> map) {
+        List<Map.Entry<String, String>> l = new ArrayList<Map.Entry<String, String>>(map.entrySet());
+        props.put(prefix + "count", Integer.toString(l.size()));
+        for (int i = 0; i < l.size(); i++) {
+            props.put(prefix + "key." + i, l.get(i).getKey());
+            props.put(prefix + "val." + i, l.get(i).getValue());
+        }
+    }
+
+    protected Map<String, String> loadMap(Properties props, String prefix) {
+        Map<String, String> l = new HashMap<String, String>();
+        String countStr = (String) props.get(prefix + "count");
+        if (countStr != null) {
+            int count = Integer.parseInt(countStr);
+            for (int i = 0; i < count; i++) {
+                String key = (String) props.get(prefix + "key." + i);
+                String val = (String) props.get(prefix + "val." + i);
+                l.put(key, val);
+            }
+        }
+        return l;
+    }
+
+
+    protected void close(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java
new file mode 100644
index 0000000..19fc706
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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.karaf.features.internal.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.CRC32;
+
+public class ChecksumUtils {
+
+    private ChecksumUtils() {
+    }
+
+    /**
+     * Compute a cheksum for the file or directory that consists of the name, length and the last modified date
+     * for a file and its children in case of a directory
+     *
+     * @param is the input stream
+     * @return a checksum identifying any change
+     */
+    public static long checksum(InputStream is) throws IOException
+    {
+        try {
+            CRC32 crc = new CRC32();
+            byte[] buffer = new byte[8192];
+            int l;
+            while ((l = is.read(buffer)) > 0) {
+                crc.update(buffer, 0, l);
+            }
+            return crc.getValue();
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
new file mode 100644
index 0000000..a53d8a5
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
@@ -0,0 +1,349 @@
+/*
+ * 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.karaf.features.internal.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ */
+public class JsonReader {
+
+    public static Object read(Reader reader) throws IOException {
+        return new JsonReader(reader).parse();
+    }
+
+    public static Object read(InputStream is) throws IOException {
+        return new JsonReader(new InputStreamReader(is)).parse();
+    }
+
+    //
+    // Implementation
+    //
+
+    private final Reader reader;
+    private final StringBuilder recorder;
+    private int current;
+    private int line = 1;
+    private int column = 0;
+
+    JsonReader(Reader reader) {
+        this.reader = reader;
+        recorder = new StringBuilder();
+    }
+
+    public Object parse() throws IOException {
+        read();
+        skipWhiteSpace();
+        Object result = readValue();
+        skipWhiteSpace();
+        if (!endOfText()) {
+            throw error("Unexpected character");
+        }
+        return result;
+    }
+
+    private Object readValue() throws IOException {
+        switch (current) {
+            case 'n':
+                return readNull();
+            case 't':
+                return readTrue();
+            case 'f':
+                return readFalse();
+            case '"':
+                return readString();
+            case '[':
+                return readArray();
+            case '{':
+                return readObject();
+            case '-':
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                return readNumber();
+            default:
+                throw expected("value");
+        }
+    }
+
+    private Collection<?> readArray() throws IOException {
+        read();
+        Collection<Object> array = new ArrayList<Object>();
+        skipWhiteSpace();
+        if (readChar(']')) {
+            return array;
+        }
+        do {
+            skipWhiteSpace();
+            array.add(readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar(']')) {
+            throw expected("',' or ']'");
+        }
+        return array;
+    }
+
+    private Map<String, Object> readObject() throws IOException {
+        read();
+        Map<String, Object> object = new HashMap<String, Object>();
+        skipWhiteSpace();
+        if (readChar('}')) {
+            return object;
+        }
+        do {
+            skipWhiteSpace();
+            String name = readName();
+            skipWhiteSpace();
+            if (!readChar(':')) {
+                throw expected("':'");
+            }
+            skipWhiteSpace();
+            object.put(name, readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar('}')) {
+            throw expected("',' or '}'");
+        }
+        return object;
+    }
+
+    private Object readNull() throws IOException {
+        read();
+        readRequiredChar('u');
+        readRequiredChar('l');
+        readRequiredChar('l');
+        return null;
+    }
+
+    private Boolean readTrue() throws IOException {
+        read();
+        readRequiredChar('r');
+        readRequiredChar('u');
+        readRequiredChar('e');
+        return Boolean.TRUE;
+    }
+
+    private Boolean readFalse() throws IOException {
+        read();
+        readRequiredChar('a');
+        readRequiredChar('l');
+        readRequiredChar('s');
+        readRequiredChar('e');
+        return Boolean.FALSE;
+    }
+
+    private void readRequiredChar(char ch) throws IOException {
+        if (!readChar(ch)) {
+            throw expected("'" + ch + "'");
+        }
+    }
+
+    private String readString() throws IOException {
+        read();
+        recorder.setLength(0);
+        while (current != '"') {
+            if (current == '\\') {
+                readEscape();
+            } else if (current < 0x20) {
+                throw expected("valid string character");
+            } else {
+                recorder.append((char) current);
+                read();
+            }
+        }
+        read();
+        return recorder.toString();
+    }
+
+    private void readEscape() throws IOException {
+        read();
+        switch (current) {
+            case '"':
+            case '/':
+            case '\\':
+                recorder.append((char) current);
+                break;
+            case 'b':
+                recorder.append('\b');
+                break;
+            case 'f':
+                recorder.append('\f');
+                break;
+            case 'n':
+                recorder.append('\n');
+                break;
+            case 'r':
+                recorder.append('\r');
+                break;
+            case 't':
+                recorder.append('\t');
+                break;
+            case 'u':
+                char[] hexChars = new char[4];
+                for (int i = 0; i < 4; i++) {
+                    read();
+                    if (!isHexDigit(current)) {
+                        throw expected("hexadecimal digit");
+                    }
+                    hexChars[i] = (char) current;
+                }
+                recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
+                break;
+            default:
+                throw expected("valid escape sequence");
+        }
+        read();
+    }
+
+    private Number readNumber() throws IOException {
+        recorder.setLength(0);
+        readAndAppendChar('-');
+        int firstDigit = current;
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        if (firstDigit != '0') {
+            while (readAndAppendDigit()) {
+            }
+        }
+        readFraction();
+        readExponent();
+        return Double.parseDouble(recorder.toString());
+    }
+
+    private boolean readFraction() throws IOException {
+        if (!readAndAppendChar('.')) {
+            return false;
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+        }
+        return true;
+    }
+
+    private boolean readExponent() throws IOException {
+        if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
+            return false;
+        }
+        if (!readAndAppendChar('+')) {
+            readAndAppendChar('-');
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+        }
+        return true;
+    }
+
+    private String readName() throws IOException {
+        if (current != '"') {
+            throw expected("name");
+        }
+        readString();
+        return recorder.toString();
+    }
+
+    private boolean readAndAppendChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        recorder.append(ch);
+        read();
+        return true;
+    }
+
+    private boolean readChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        read();
+        return true;
+    }
+
+    private boolean readAndAppendDigit() throws IOException {
+        if (!isDigit(current)) {
+            return false;
+        }
+        recorder.append((char) current);
+        read();
+        return true;
+    }
+
+    private void skipWhiteSpace() throws IOException {
+        while (isWhiteSpace(current) && !endOfText()) {
+            read();
+        }
+    }
+
+    private void read() throws IOException {
+        if (endOfText()) {
+            throw error("Unexpected end of input");
+        }
+        column++;
+        if (current == '\n') {
+            line++;
+            column = 0;
+        }
+        current = reader.read();
+    }
+
+    private boolean endOfText() {
+        return current == -1;
+    }
+
+    private IOException expected(String expected) {
+        if (endOfText()) {
+            return error("Unexpected end of input");
+        }
+        return error("Expected " + expected);
+    }
+
+    private IOException error(String message) {
+        return new IOException(message + " at " + line + ":" + column);
+    }
+
+    private static boolean isWhiteSpace(int ch) {
+        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
+    }
+
+    private static boolean isDigit(int ch) {
+        return ch >= '0' && ch <= '9';
+    }
+
+    private static boolean isHexDigit(int ch) {
+        return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
new file mode 100644
index 0000000..cba27fb
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
@@ -0,0 +1,120 @@
+/*
+ * 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.karaf.features.internal.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ */
+public class JsonWriter {
+
+    public static void write(Writer writer, Object value) throws IOException {
+        if (value instanceof Map) {
+            writeObject(writer, (Map) value);
+        } else if (value instanceof Collection) {
+            writeArray(writer, (Collection) value);
+        } else if (value instanceof Number) {
+            writeNumber(writer, (Number) value);
+        } else if (value instanceof String) {
+            writeString(writer, (String) value);
+        } else if (value instanceof Boolean) {
+            writeBoolean(writer, (Boolean) value);
+        } else if (value == null) {
+            writeNull(writer);
+        } else {
+            throw new IllegalArgumentException("Unsupported value: " + value);
+        }
+    }
+
+    private static void writeObject(Writer writer, Map<?, ?> value) throws IOException {
+        writer.append('{');
+        boolean first = true;
+        for (Map.Entry entry : value.entrySet()) {
+            if (!first) {
+                writer.append(',');
+            } else {
+                first = false;
+            }
+            writeString(writer, (String) entry.getKey());
+            writer.append(':');
+            write(writer, entry.getValue());
+        }
+        writer.append('}');
+    }
+
+    private static void writeString(Writer writer, String value) throws IOException {
+        writer.append('"');
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            switch (c) {
+                case '\"':
+                case '\\':
+                case '\b':
+                case '\f':
+                case '\n':
+                case '\r':
+                case '\t':
+                    writer.append('\\');
+                    writer.append(c);
+                    break;
+                default:
+                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+                        String s = Integer.toHexString(c);
+                        writer.append('\\');
+                        writer.append('u');
+                        for (int j = s.length(); j < 4; j++) {
+                            writer.append('0');
+                        }
+                        writer.append(s);
+                    } else {
+                        writer.append(c);
+                    }
+                    break;
+            }
+        }
+        writer.append('"');
+    }
+
+    private static void writeNumber(Writer writer, Number value) throws IOException {
+        writer.append(value.toString());
+    }
+
+    private static void writeBoolean(Writer writer, Boolean value) throws IOException {
+        writer.append(Boolean.toString(value));
+    }
+
+    private static void writeArray(Writer writer, Collection<?> value) throws IOException {
+        writer.append('[');
+        boolean first = true;
+        for (Object obj : value) {
+            if (!first) {
+                writer.append(',');
+            } else {
+                first = false;
+            }
+            write(writer, obj);
+        }
+        writer.append(']');
+    }
+
+    private static void writeNull(Writer writer) throws IOException {
+        writer.append("null");
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java
new file mode 100644
index 0000000..d30b7b5
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java
@@ -0,0 +1,142 @@
+/*
+ * 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.karaf.features.internal.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.Version;
+
+public class Macro {
+
+    public static String transform(String macro, String value) {
+        if (macro.startsWith("${") && macro.endsWith("}")) {
+            String[] args = macro.substring(2, macro.length() - 1).split(";");
+            if ("version".equals(args[0])) {
+                if (args.length != 2) {
+                    throw new IllegalArgumentException("Invalid syntax for macro: " + macro);
+                }
+                return version(args[1], VersionTable.getVersion(value));
+            } else if ("range".equals(args[0])) {
+                if (args.length != 2) {
+                    throw new IllegalArgumentException("Invalid syntax for macro: " + macro);
+                }
+                return range(args[1], VersionTable.getVersion(value));
+            } else {
+                throw new IllegalArgumentException("Unknown macro: " + macro);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Modify a version to set a version policy. Thed policy is a mask that is
+     * mapped to a version.
+     *
+     * <pre>
+     * +           increment
+     * -           decrement
+     * =           maintain
+     * &tilde;           discard
+     *
+     * ==+      = maintain major, minor, increment micro, discard qualifier
+     * &tilde;&tilde;&tilde;=     = just get the qualifier
+     * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
+	 * </pre>
+	 *
+	 * @param args
+     * @return
+     */
+    final static String	MASK_STRING			= "[\\-+=~0123456789]{0,3}[=~]?";
+
+    static String version(String mask, Version version) {
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+
+        for (int i = 0; i < mask.length(); i++) {
+            char c = mask.charAt(i);
+            String result = null;
+            if (c != '~') {
+                if (i > 3) {
+                    throw new IllegalArgumentException("Version mask can only specify 3 digits");
+                } else if (i == 3) {
+                    result = version.getQualifier();
+                    if (result.isEmpty()) {
+                        result = null;
+                    }
+                } else if (Character.isDigit(c)) {
+                    // Handle masks like +00, =+0
+                    result = String.valueOf(c);
+                } else {
+                    int x = 0;
+                    switch (i) {
+                        case 0: x = version.getMajor(); break;
+                        case 1: x = version.getMinor(); break;
+                        case 2: x = version.getMicro(); break;
+                    }
+                    switch (c) {
+                        case '+' :
+                            x++;
+                            break;
+                        case '-' :
+                            x--;
+                            break;
+                        case '=' :
+                            break;
+                    }
+                    result = Integer.toString(x);
+                }
+                if (result != null) {
+                    sb.append(del);
+                    del = ".";
+                    sb.append(result);
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Schortcut for version policy
+     *
+     * <pre>
+     * -provide-policy : ${policy;[==,=+)}
+     * -consume-policy : ${policy;[==,+)}
+     * </pre>
+     *
+     * @param args
+     * @return
+     */
+
+    static Pattern	RANGE_MASK		= Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
+
+    static String range(String spec, Version version) {
+        Matcher m = RANGE_MASK.matcher(spec);
+        m.matches();
+        String floor = m.group(1);
+        String floorMask = m.group(2);
+        String ceilingMask = m.group(3);
+        String ceiling = m.group(4);
+
+        String left = version(floorMask, version);
+        String right = version(ceilingMask, version);
+
+        return floor + left + "," + right + ceiling;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java
new file mode 100644
index 0000000..36af452
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java
@@ -0,0 +1,95 @@
+/*
+ * 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.karaf.features.internal.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("serial")
+public class MultiException extends Exception {
+
+    private List<Exception> exceptions = new ArrayList<Exception>();
+
+    public MultiException(String message) {
+        super(message);
+    }
+
+    public MultiException(String message, List<Exception> exceptions) {
+        super(message);
+        this.exceptions = exceptions;
+    }
+
+    public void addException(Exception e) {
+        exceptions.add(e);
+    }
+
+    public void throwIfExceptions() throws MultiException {
+        if (!exceptions.isEmpty()) {
+            throw this;
+        }
+    }
+    
+    public Throwable[] getCauses() {
+        return exceptions.toArray(new Throwable[exceptions.size()]);
+    }
+
+    @Override
+    public void printStackTrace()
+    {
+        super.printStackTrace();
+        for (Exception e : exceptions) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see Throwable#printStackTrace(java.io.PrintStream)
+     */
+    @Override
+    public void printStackTrace(PrintStream out)
+    {
+        super.printStackTrace(out);
+        for (Exception e : exceptions) {
+            e.printStackTrace(out);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter out)
+    {
+        super.printStackTrace(out);
+        for (Exception e : exceptions) {
+            e.printStackTrace(out);
+        }
+    }
+
+    public static void throwIf(String message, List<Exception> exceptions) throws MultiException {
+        if (exceptions != null && !exceptions.isEmpty()) {
+            StringBuilder sb = new StringBuilder(message);
+            sb.append(":");
+            for (Exception e : exceptions) {
+                sb.append("\n\t");
+                sb.append(e.getMessage());
+            }
+            throw new MultiException(sb.toString(), exceptions);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java b/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
new file mode 100644
index 0000000..6afbbed
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
@@ -0,0 +1,142 @@
+/*
+ * 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.karaf.features.management;
+
+import javax.management.openmbean.TabularData;
+
+public interface FeaturesServiceMBean {
+
+    TabularData getFeatures() throws Exception;
+
+    TabularData getRepositories() throws Exception;
+
+    void addRepository(String url) throws Exception;
+
+    void addRepository(String url, boolean install) throws Exception;
+
+    void removeRepository(String url) throws Exception;
+
+    void removeRepository(String url, boolean uninstall) throws Exception;
+
+    void installFeature(String name) throws Exception;
+
+    void installFeature(String name, boolean noRefresh) throws Exception;
+
+    void installFeature(String name, boolean noRefresh, boolean noStart) throws Exception;
+
+    void installFeature(String name, String version) throws Exception;
+
+    void installFeature(String name, String version, boolean noRefresh) throws Exception;
+
+    void installFeature(String name, String version, boolean noRefresh, boolean noStart) throws Exception;
+
+    TabularData infoFeature(String name) throws Exception;
+
+    TabularData infoFeature(String name, String version) throws Exception;
+
+    void uninstallFeature(String name) throws Exception;
+
+    void uninstallFeature(String name, boolean noRefresh) throws Exception;
+
+    void uninstallFeature(String name, String version) throws Exception;
+
+    void uninstallFeature(String name, String version, boolean noRefresh) throws Exception;
+
+    String FEATURE_NAME = "Name";
+
+    String FEATURE_VERSION = "Version";
+
+    String FEATURE_DEPENDENCIES = "Dependencies";
+
+    String FEATURE_BUNDLES = "Bundles";
+
+    String FEATURE_CONFIGURATIONS = "Configurations";
+    
+    String FEATURE_CONFIGURATIONFILES = "Configuration Files";
+
+    String FEATURE_INSTALLED = "Installed";
+
+    String FEATURE_CONFIG_PID = "Pid";
+    String FEATURE_CONFIG_ELEMENTS = "Elements";
+    String FEATURE_CONFIG_ELEMENT_KEY = "Key";
+    String FEATURE_CONFIG_ELEMENT_VALUE = "Value";
+    
+    String FEATURE_CONFIG_FILES_ELEMENTS = "Files";
+
+    /**
+     * The type of the event which is emitted for features events
+     */
+    String FEATURE_EVENT_TYPE = "org.apache.karaf.features.featureEvent";
+
+    String FEATURE_EVENT_EVENT_TYPE = "Type";
+
+    String FEATURE_EVENT_EVENT_TYPE_INSTALLED = "Installed";
+
+    String FEATURE_EVENT_EVENT_TYPE_UNINSTALLED = "Uninstalled";
+
+    /**
+     * The item names in the CompositeData representing a feature
+     */
+    String[] FEATURE = { FEATURE_NAME, FEATURE_VERSION, FEATURE_DEPENDENCIES, FEATURE_BUNDLES,
+                         FEATURE_CONFIGURATIONS, FEATURE_CONFIGURATIONFILES, FEATURE_INSTALLED };
+
+    String[] FEATURE_IDENTIFIER = { FEATURE_NAME, FEATURE_VERSION };
+
+    String[] FEATURE_CONFIG = { FEATURE_CONFIG_PID, FEATURE_CONFIG_ELEMENTS };
+    
+    String[] FEATURE_CONFIG_FILES = { FEATURE_CONFIG_FILES_ELEMENTS };
+
+    String[] FEATURE_CONFIG_ELEMENT = { FEATURE_CONFIG_ELEMENT_KEY, FEATURE_CONFIG_ELEMENT_VALUE };
+
+    /**
+     * The item names in the CompositeData representing the event raised for
+     * feature events within the OSGi container by this bean
+     */
+    String[] FEATURE_EVENT = { FEATURE_NAME, FEATURE_VERSION, FEATURE_EVENT_EVENT_TYPE };
+
+
+    String REPOSITORY_NAME = "Name";
+
+    String REPOSITORY_URI = "Uri";
+
+    String REPOSITORY_REPOSITORIES = "Repositories";
+
+    String REPOSITORY_FEATURES = "Features";
+
+    /**
+     * The type of the event which is emitted for repositories events
+     */
+    String REPOSITORY_EVENT_TYPE = "org.apache.karaf.features.repositoryEvent";
+
+    String REPOSITORY_EVENT_EVENT_TYPE = "Type";
+
+    String REPOSITORY_EVENT_EVENT_TYPE_ADDED = "Added";
+
+    String REPOSITORY_EVENT_EVENT_TYPE_REMOVED = "Removed";
+
+    /**
+     * The item names in the CompositeData representing a feature
+     */
+    String[] REPOSITORY = { REPOSITORY_NAME, REPOSITORY_URI,  REPOSITORY_REPOSITORIES, REPOSITORY_FEATURES };
+
+    /**
+     * The item names in the CompositeData representing the event raised for
+     * feature events within the OSGi container by this bean
+     */
+    String[] REPOSITORY_EVENT = { REPOSITORY_URI, REPOSITORY_EVENT_EVENT_TYPE };
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
new file mode 100644
index 0000000..54fa3c0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
@@ -0,0 +1,323 @@
+/*
+ * 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.karaf.features.management.codec;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.TabularDataSupport;
+
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxFeature {
+
+    /**
+     * The CompositeType which represents a single feature
+     */
+    public final static CompositeType FEATURE;
+
+    /**
+     * The TabularType which represents a list of features
+     */
+    public final static TabularType FEATURE_TABLE;
+
+    public final static CompositeType FEATURE_IDENTIFIER;
+
+    public final static TabularType FEATURE_IDENTIFIER_TABLE;
+
+    public final static CompositeType FEATURE_CONFIG_ELEMENT;
+
+    public final static TabularType FEATURE_CONFIG_ELEMENT_TABLE;
+
+    public final static CompositeType FEATURE_CONFIG;
+
+    public final static TabularType FEATURE_CONFIG_TABLE;
+
+    public final static CompositeType FEATURE_CONFIG_FILES;
+    
+    public final static TabularType FEATURE_CONFIG_FILES_TABLE;
+    
+    private final CompositeData data;
+
+    public JmxFeature(Feature feature, boolean installed) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.FEATURE;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = feature.getName();
+            itemValues[1] = feature.getVersion();
+            itemValues[2] = getDependencyIdentifierTable(feature.getDependencies());
+            itemValues[3] = getBundleUris(feature.getBundles());
+            itemValues[4] = getConfigTable(feature.getConfigurations());
+            itemValues[5] = getConfigFileList(feature.getConfigurationFiles());
+            itemValues[6] = installed;
+            data = new CompositeDataSupport(FEATURE, itemNames, itemValues);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Cannot form feature open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    public static TabularData tableFrom(Collection<JmxFeature> features) {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_TABLE);
+        for (JmxFeature feature : features) {
+            table.put(feature.asCompositeData());
+        }
+        return table;
+    }
+
+     private static TabularData getDependencyIdentifierTable(List<Dependency> features) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_IDENTIFIER_TABLE);
+        Set<String> featureSet = new HashSet<String>();
+        for (Dependency feature : features) {
+            if (featureSet.contains(feature.getName() + feature.getVersion())) {
+                continue;
+            } else {
+                featureSet.add(feature.getName() + feature.getVersion());
+            }
+            String[] itemNames = new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION };
+            Object[] itemValues = new Object[] { feature.getName(), feature.getVersion() };
+            CompositeData ident = new CompositeDataSupport(FEATURE_IDENTIFIER, itemNames, itemValues);
+            table.put(ident);
+        }
+        return table;
+    }
+
+    static String[] getBundleUris(List<BundleInfo> infos) {
+        String[] array = new String[infos.size()];
+        for (int i = 0; i < array.length; i++) {
+            array[i] = infos.get(i).getLocation();
+        }
+        return array;
+    }
+
+    static TabularData getConfigTable(Map<String, Map<String, String>> configs) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_CONFIG_TABLE);
+        for (Map.Entry<String, Map<String, String>> entry : configs.entrySet()) {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG;
+            Object[] itemValues = new Object[2];
+            itemValues[0] = entry.getKey();
+            itemValues[1] = getConfigElementTable(entry.getValue());
+            CompositeData config = new CompositeDataSupport(FEATURE_CONFIG, itemNames, itemValues);
+            table.put(config);
+        }
+        return table;
+    }
+    
+    static TabularData getConfigFileList(List<ConfigFileInfo> configFiles) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_CONFIG_FILES_TABLE);
+        for (ConfigFileInfo configFile : configFiles) {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG_FILES;
+            Object[] itemValues = { configFile.getFinalname() };
+            CompositeData config = new CompositeDataSupport(FEATURE_CONFIG_FILES, itemNames, itemValues);
+            table.put(config);
+        }
+        return table;
+    }
+
+    static TabularData getConfigElementTable(Map<String, String> config) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_CONFIG_ELEMENT_TABLE);
+        for (Map.Entry<String, String> entry : config.entrySet()) {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG_ELEMENT;
+            Object[] itemValues = { entry.getKey(), entry.getValue() };
+            CompositeData element = new CompositeDataSupport(FEATURE_CONFIG_ELEMENT, itemNames, itemValues);
+            table.put(element);
+        }
+        return table;
+    }
+
+
+    static {
+        FEATURE_IDENTIFIER = createFeatureIdentifierType();
+        FEATURE_IDENTIFIER_TABLE = createFeatureIdentifierTableType();
+        FEATURE_CONFIG_ELEMENT = createFeatureConfigElementType();
+        FEATURE_CONFIG_ELEMENT_TABLE = createFeatureConfigElementTableType();
+        FEATURE_CONFIG = createFeatureConfigType();
+        FEATURE_CONFIG_TABLE = createFeatureConfigTableType();
+        FEATURE_CONFIG_FILES =  createFeatureConfigFilesType();
+        FEATURE_CONFIG_FILES_TABLE = createFeatureConfigFilesTableType();
+        FEATURE = createFeatureType();
+        FEATURE_TABLE = createFeatureTableType();
+    }
+
+    private static CompositeType createFeatureIdentifierType() {
+        try {
+            String description = "This type identify a Karaf features";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_IDENTIFIER;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The id of the feature";
+            itemDescriptions[1] = "The version of the feature";
+
+            return new CompositeType("FeatureIdentifier", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build featureIdentifier type", e);
+        }
+    }
+
+    private static TabularType createFeatureIdentifierTableType() {
+        try {
+            return new TabularType("Features", "The table of featureIdentifiers",
+                    FEATURE_IDENTIFIER, new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build featureIdentifier table type", e);
+        }
+    }
+
+    private static CompositeType createFeatureConfigElementType() {
+        try {
+            String description = "This type encapsulates Karaf feature config element";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG_ELEMENT;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The key";
+            itemDescriptions[1] = "The value";
+
+            return new CompositeType("ConfigElement", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build configElement type", e);
+        }
+    }
+
+    private static TabularType createFeatureConfigElementTableType() {
+        try {
+            return new TabularType("ConfigElement", "The table of configurations elements",
+                    FEATURE_CONFIG_ELEMENT, new String[] { FeaturesServiceMBean.FEATURE_CONFIG_ELEMENT_KEY});
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+
+    private static CompositeType createFeatureConfigType() {
+        try {
+            String description = "This type encapsulates Karaf feature config";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = FEATURE_CONFIG_ELEMENT_TABLE;
+
+            itemDescriptions[0] = "The PID of the config";
+            itemDescriptions[1] = "The configuration elements";
+
+            return new CompositeType("Config", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build configElement type", e);
+        }
+    }
+    
+    private static CompositeType createFeatureConfigFilesType() {
+        try {
+            String description = "This type encapsulates Karaf feature config files";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG_FILES;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The configuration file";
+
+            return new CompositeType("Config", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build configElement type", e);
+        }
+    }
+
+    private static TabularType createFeatureConfigTableType() {
+        try {
+            return new TabularType("Features", "The table of configurations",
+                    FEATURE_CONFIG, new String[] { FeaturesServiceMBean.FEATURE_CONFIG_PID});
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+    
+    private static TabularType createFeatureConfigFilesTableType() {
+        try {
+            return new TabularType("Features", "The table of configuration files",
+            		FEATURE_CONFIG_FILES, new String[] { FeaturesServiceMBean.FEATURE_CONFIG_FILES_ELEMENTS });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+
+    private static CompositeType createFeatureType() {
+        try {
+            String description = "This type encapsulates Karaf features";
+            String[] itemNames = FeaturesServiceMBean.FEATURE;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+            itemTypes[2] = FEATURE_IDENTIFIER_TABLE;
+            itemTypes[3] = new ArrayType(1, SimpleType.STRING);
+            itemTypes[4] = FEATURE_CONFIG_TABLE;
+            itemTypes[5] = FEATURE_CONFIG_FILES_TABLE;
+            itemTypes[6] = SimpleType.BOOLEAN;
+
+            itemDescriptions[0] = "The name of the feature";
+            itemDescriptions[1] = "The version of the feature";
+            itemDescriptions[2] = "The feature dependencies";
+            itemDescriptions[3] = "The feature bundles";
+            itemDescriptions[4] = "The feature configurations";
+            itemDescriptions[5] = "The feature configuration files";
+            itemDescriptions[6] = "Whether the feature is installed";
+
+            return new CompositeType("Feature", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature type", e);
+        }
+    }
+
+    private static TabularType createFeatureTableType() {
+        try {
+            return new TabularType("Features", "The table of all features",
+                    FEATURE, new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeatureEvent.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeatureEvent.java b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeatureEvent.java
new file mode 100644
index 0000000..81f446b
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxFeatureEvent.java
@@ -0,0 +1,80 @@
+/*
+ * 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.karaf.features.management.codec;
+
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxFeatureEvent {
+
+    public static final CompositeType FEATURE_EVENT;
+
+    private final CompositeData data;
+
+    public JmxFeatureEvent(FeatureEvent event) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_EVENT;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = event.getFeature().getName();
+            itemValues[1] = event.getFeature().getVersion();
+            switch (event.getType()) {
+                case FeatureInstalled:   itemValues[2] = FeaturesServiceMBean.FEATURE_EVENT_EVENT_TYPE_INSTALLED; break;
+                case FeatureUninstalled: itemValues[2] = FeaturesServiceMBean.FEATURE_EVENT_EVENT_TYPE_UNINSTALLED; break;
+                default: throw new IllegalStateException("Unsupported event type: " + event.getType());
+            }
+            data = new CompositeDataSupport(FEATURE_EVENT, itemNames, itemValues);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Cannot form feature event open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    static {
+        FEATURE_EVENT = createFeatureEventType();
+    }
+
+    private static CompositeType createFeatureEventType() {
+        try {
+            String description = "This type identify a Karaf feature event";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_EVENT;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+            itemTypes[2] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The id of the feature";
+            itemDescriptions[1] = "The version of the feature";
+            itemDescriptions[2] = "The type of the event";
+
+            return new CompositeType("FeatureEvent", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build featureEvent type", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepository.java b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepository.java
new file mode 100644
index 0000000..fee1ab2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepository.java
@@ -0,0 +1,132 @@
+/*
+ * 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.karaf.features.management.codec;
+
+import java.util.Collection;
+import java.util.Arrays;
+import java.net.URI;
+import java.util.List;
+
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.CompositeDataSupport;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxRepository {
+
+    public final static CompositeType REPOSITORY;
+
+    public final static TabularType REPOSITORY_TABLE;
+
+    private final CompositeData data;
+
+    public JmxRepository(Repository repository) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = repository.getName();
+            itemValues[1] = repository.getURI().toString();
+            itemValues[2] = toStringArray(repository.getRepositories());
+            itemValues[3] = getFeatureIdentifierTable(Arrays.asList(repository.getFeatures()));
+            data = new CompositeDataSupport(REPOSITORY, itemNames, itemValues);
+        } catch (Exception e) {
+            throw new IllegalStateException("Cannot form repository open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    public static TabularData tableFrom(Collection<JmxRepository> repositories) {
+        TabularDataSupport table = new TabularDataSupport(REPOSITORY_TABLE);
+        for (JmxRepository repository : repositories) {
+            table.put(repository.asCompositeData());
+        }
+        return table;
+    }
+
+    private static String[] toStringArray(URI[] uris) {
+        if (uris == null) {
+            return null;
+        }
+        String[] res = new String[uris.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = uris[i].toString();
+        }
+        return res;
+    }
+
+    static TabularData getFeatureIdentifierTable(List<Feature> features) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(JmxFeature.FEATURE_IDENTIFIER_TABLE);
+        for (Feature feature : features) {
+            String[] itemNames = new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION };
+            Object[] itemValues = new Object[] { feature.getName(), feature.getVersion() };
+            CompositeData ident = new CompositeDataSupport(JmxFeature.FEATURE_IDENTIFIER, itemNames, itemValues);
+            table.put(ident);
+        }
+        return table;
+    }
+
+    static {
+        REPOSITORY = createRepositoryType();
+        REPOSITORY_TABLE = createRepositoryTableType();
+    }
+
+    private static CompositeType createRepositoryType() {
+        try {
+            String description = "This type identify a Karaf repository";
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+            itemTypes[2] = new ArrayType(1, SimpleType.STRING);
+            itemTypes[3] = JmxFeature.FEATURE_IDENTIFIER_TABLE;
+
+            itemDescriptions[0] = "The name of the repository";
+            itemDescriptions[1] = "The uri of the repository";
+            itemDescriptions[2] = "The dependent repositories";
+            itemDescriptions[3] = "The list of included features";
+
+            return new CompositeType("Repository", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build repository type", e);
+        }
+    }
+
+    private static TabularType createRepositoryTableType() {
+        try {
+            return new TabularType("Features", "The table of repositories",
+                    REPOSITORY, new String[] { FeaturesServiceMBean.REPOSITORY_URI });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build repository table type", e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
new file mode 100644
index 0000000..e00e85d
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
@@ -0,0 +1,77 @@
+/*
+ * 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.karaf.features.management.codec;
+
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.apache.karaf.features.RepositoryEvent;
+import org.apache.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxRepositoryEvent {
+
+    public static final CompositeType REPOSITORY_EVENT;
+
+    private final CompositeData data;
+
+    public JmxRepositoryEvent(RepositoryEvent event) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY_EVENT;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = event.getRepository().getURI().toString();
+            switch (event.getType()) {
+                case RepositoryAdded:   itemValues[1] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_ADDED; break;
+                case RepositoryRemoved: itemValues[1] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_REMOVED; break;
+                default: throw new IllegalStateException("Unsupported event type: " + event.getType());
+            }
+            data = new CompositeDataSupport(REPOSITORY_EVENT, itemNames, itemValues);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Cannot form repository event open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    static {
+        REPOSITORY_EVENT = createRepositoryEventType();
+    }
+
+    private static CompositeType createRepositoryEventType() {
+        try {
+            String description = "This type identify a Karaf repository event";
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY_EVENT;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The uri of the repository";
+            itemDescriptions[1] = "The type of event";
+
+            return new CompositeType("RepositoryEvent", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build repositoryEvent type", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/features/core/src/main/resources/OSGI-INF/bundle.info b/features/core/src/main/resources/OSGI-INF/bundle.info
new file mode 100644
index 0000000..d5b4180
--- /dev/null
+++ b/features/core/src/main/resources/OSGI-INF/bundle.info
@@ -0,0 +1,20 @@
+h1. Synopsis
+
+${project.name}
+
+${project.description}
+
+Maven URL:
+[mvn:${project.groupId}/${project.artifactId}/${project.version}]
+
+h1. Description
+
+This bundle is the core implementation of the Karaf features support.
+
+Karaf provides a simple, yet flexible, way to provision applications or "features". Such a mechanism is mainly
+provided by a set of commands available in the features shell. The provisioning system uses xml "repositories"
+that define a set of features.
+
+h1. See also
+
+Provisioning - section of the Karaf User Guide