ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r788992 [16/25] - in /incubator/ace/trunk: gateway/ gateway/src/ gateway/src/net/ gateway/src/net/luminis/ gateway/src/net/luminis/liq/ gateway/src/net/luminis/liq/bootstrap/ gateway/src/net/luminis/liq/bootstrap/multigateway/ gateway/src/n...
Date Sat, 27 Jun 2009 15:53:26 GMT
Added: incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,27 @@
+package net.luminis.liq.server.action.popupmessage;
+
+import java.util.Properties;
+
+import net.luminis.liq.server.action.Action;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) {
+        Properties props = new Properties();
+        props.put(Action.ACTION_NAME, PopupMessageAction.NAME);
+        manager.add(createService()
+            .setInterface(Action.class.getName(), props)
+            .setImplementation(PopupMessageAction.class)
+            );
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) {
+        // do nothing
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/PopupMessageAction.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/PopupMessageAction.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/PopupMessageAction.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/action/popupmessage/PopupMessageAction.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,36 @@
+package net.luminis.liq.server.action.popupmessage;
+
+import javax.swing.JOptionPane;
+
+import net.luminis.liq.server.action.MessageAction;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Shows a message in a popup dialog. Does not wait for the user to respond, so multiple popups can
+ * be shown at the same time (each one in its own thread). This action is mainly for demonstration
+ * purposes, to replace actions like a mail action, that might be hard to configure in a simple
+ * demo scenario.
+ */
+public class PopupMessageAction implements MessageAction {
+    public static final String NAME = "PopupMessageAction";
+
+    public void handle(Event event) {
+        final User user = (User) event.getProperty(USER);
+        final String description = (String) event.getProperty(DESCRIPTION);
+        final String shortDescription = (String) event.getProperty(SHORT_DESCRIPTION);
+
+        Thread t = new Thread("Notification") {
+            @Override
+            public void run() {
+                JOptionPane.showMessageDialog(null,
+                    "<html><table><tr><td>To: </td><td>" + user.getName() + " " + (String) user.getProperties().get("email") + "</td></tr>" +
+                    "<tr><td>Subject: </td><td>" + shortDescription + "</td></tr>" +
+                    "<tr><td valign='top'>Message: </td><td>" + description.replaceAll("\n", "<p>")
+                );
+            }
+        };
+        t.start();
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,81 @@
+package net.luminis.liq.server.log;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServlet;
+
+import net.luminis.liq.http.listener.constants.HttpConstants;
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase implements ManagedServiceFactory {
+    private static final String LOG_NAME = "name";
+
+    private final Map<String, Service> m_instances = new HashMap<String, Service>(); // String -> Service
+    private DependencyManager m_manager;
+    private volatile LogService m_log;
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        m_manager = manager;
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "net.luminis.liq.server.log.servlet.factory");
+        manager.add(createService()
+            .setInterface(ManagedServiceFactory.class.getName(), props)
+            .setImplementation(this)
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+    }
+
+    public void deleted(String pid) {
+        Service log = m_instances.remove(pid);
+        if (log != null) {
+            m_manager.remove(log);
+        }
+    }
+
+    public String getName() {
+        return "Log Servlet Factory";
+    }
+
+    @SuppressWarnings("unchecked")
+    public void updated(String pid, Dictionary dict) throws ConfigurationException {
+        String name = (String) dict.get(LOG_NAME);
+        if ((name == null) || "".equals(name)) {
+            throw new ConfigurationException(LOG_NAME, "Log name has to be specified.");
+        }
+        String endpoint = (String) dict.get(HttpConstants.ENDPOINT);
+        if ((endpoint == null) || "".equals(endpoint)) {
+            throw new ConfigurationException(HttpConstants.ENDPOINT, "Servlet endpoint has to be specified.");
+        }
+
+        Service service = m_instances.get(pid);
+        if (service == null) {
+            Properties props = new Properties();
+            props.put(HttpConstants.ENDPOINT, endpoint);
+            service = m_manager.createService()
+                .setInterface(HttpServlet.class.getName(), props)
+                .setImplementation(new LogServlet(name))
+                .add(createServiceDependency().setService(LogService.class).setRequired(false))
+                .add(createServiceDependency().setService(LogStore.class, "(&("+Constants.OBJECTCLASS+"="+LogStore.class.getName()+")(name=" + name + "))").setRequired(true));
+
+            m_instances.put(pid, service);
+            m_manager.add(service);
+        } else {
+            m_log.log(LogService.LOG_INFO, "Ignoring configuration update because factory instance was already configured: " + name);
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/LogServlet.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/LogServlet.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/LogServlet.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/LogServlet.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,232 @@
+package net.luminis.liq.server.log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.luminis.liq.log.LogEvent;
+import net.luminis.liq.log.LogDescriptor;
+import net.luminis.liq.repository.SortedRangeSet;
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.osgi.service.log.LogService;
+
+/**
+ * This class acts as a servlet and handles the log protocol. This means a number of requests will be handled:
+ *
+ * The endpoint is configured externally, 'auditlog' is used as an example here.
+ *
+ * Querying existing audit log event id's:
+ * http://host:port/auditlog/query - Return all known event ranges
+ * http://host:port/auditlog/query?gwid=myid&logid=123712636323 - Return the event range belonging to the specified gateway and log id
+ *
+ * Accepting new audit log events:
+ * http://host:port/auditlog/send - Gets a new log event and puts it in the store, the event is inside the request and should be a formatted as done in <code>LogEvent.toRepresentation()</code>.
+ *
+ * Querying existing audit log events:
+ * http://host:port/auditlog/receive - Return all known events
+ * http://host:port/auditlog/receive?gwid=myid - Return all known events belonging to the specified gateway ID
+ * http://host:port/auditlog/receive?gwid=myid&logid=2374623874 - Return all known events belonging to the specified gateway ID
+ *
+ * If the request is not correctly formatted or other problems arise error code <code>HttpServletResponse.SC_NOT_FOUND</code> will be sent in the response.
+ */
+public class LogServlet extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    // response mime type
+    private static final String TEXT_MIMETYPE = "text/plain";
+
+    // url path names available on the endpoint
+    private static final String QUERY = "/query";
+    private static final String SEND = "/send";
+    private static final String RECEIVE = "/receive";
+
+    // url parameter keys
+    private static final String GWID_KEY = "gwid";
+    private static final String FILTER_KEY = "filter";
+    private static final String LOGID_KEY = "logid";
+    private static final String RANGE_KEY = "range";
+
+    private volatile LogService m_log; /* will be injected by dependencymanager */
+    private volatile LogStore m_store; /* will be injected by dependencymanager */
+
+    private final String m_name;
+
+    public LogServlet(String name) {
+        m_name = name;
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+        // 'send' calls are POST calls
+        String path = request.getPathInfo();
+        response.setContentType(TEXT_MIMETYPE);
+        try {
+            if (SEND.equals(path) && !handleSend(request.getInputStream())) {
+                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Could not construct a log event for all events received");
+            }
+        }
+        catch (IOException e) {
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error processing received log events");
+        }
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+        // 'query' and 'receive' calls are GET calls
+
+        String path = request.getPathInfo();
+        String gatewayID = request.getParameter(GWID_KEY);
+        String logID = request.getParameter(LOGID_KEY);
+        String filter = request.getParameter(FILTER_KEY);
+        String range = request.getParameter(RANGE_KEY);
+
+        m_log.log(LogService.LOG_DEBUG, "Log servlet called: path(" + path + ") gatewayID(" + gatewayID + ") logID(" + logID + ") range( " + range + ") filter(" + filter +")");
+        response.setContentType(TEXT_MIMETYPE);
+
+        ServletOutputStream output = null;
+        try {
+            output = response.getOutputStream();
+            if (QUERY.equals(path) && !handleQuery(gatewayID, logID, filter, output)) {
+                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Unable to interpret query");
+            }
+            else if (RECEIVE.equals(path) && !handleReceive(gatewayID, logID, range, filter, output)) {
+                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Unable to interpret receive query");
+            }
+        }
+        catch (IOException e) {
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process query");
+        }
+        finally {
+            try {
+                if (output != null) {
+                    output.close();
+                }
+            }
+            catch (Exception ex) {
+                m_log.log(LogService.LOG_WARNING, "Exception trying to close stream after request: " + request.getRequestURL(), ex);
+            }
+        }
+    }
+
+    // Handle a call to the query 'command'
+    protected boolean handleQuery(String gatewayID, String logID, String filter, ServletOutputStream output) throws IOException {
+        if ((gatewayID != null) && (logID != null)) {
+            // gateway and log id are specified, return only the range that matches these id's
+            LogDescriptor range = m_store.getDescriptor(gatewayID, Long.parseLong(logID));
+            output.print(range.toRepresentation());
+            return true;
+        }
+        else if ((gatewayID == null) && (logID == null)) {
+            // no gateway or log id has been specified, return all ranges
+            List<LogDescriptor> ranges = m_store.getDescriptors();
+            for (LogDescriptor range : ranges) {
+                output.print(range.toRepresentation() + "\n");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    // Handle a call to the receive 'command'
+    protected boolean handleReceive(String gatewayID, String logID, String range, String filter, ServletOutputStream output) throws IOException {
+        if ((gatewayID != null) && (logID != null)) {
+            // gateway and log id are specified, return only the events that are in the range that matches these id's
+            if (range != null) {
+                LogDescriptor storeDescriptor = m_store.getDescriptor(gatewayID, Long.parseLong(logID));
+                outputRange(output, new LogDescriptor(storeDescriptor.getGatewayID(), storeDescriptor.getLogID(), new SortedRangeSet(range)));
+            }
+            else {
+                outputRange(output, m_store.getDescriptor(gatewayID, Long.parseLong(logID)));
+            }
+            return true;
+        }
+        else if ((gatewayID != null) && (logID == null)) {
+            // gateway id is specified, log id is not, return all events that belong to the specified gateway id
+            List<LogDescriptor> descriptors = m_store.getDescriptors(gatewayID);
+            for (LogDescriptor descriptor : descriptors) {
+                outputRange(output, descriptor);
+            }
+            return true;
+        }
+        else if ((gatewayID == null) && (logID == null)) {
+            // no gateway or log id has been specified, return all events
+            List<LogDescriptor> descriptors = m_store.getDescriptors();
+            for (LogDescriptor descriptor : descriptors) {
+                outputRange(output, descriptor);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    // Handle a call to the send 'command'
+    protected boolean handleSend(ServletInputStream input) throws IOException {
+        List<LogEvent> events = new ArrayList<LogEvent>();
+        boolean success = true;
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(input));
+
+            String eventString;
+            while ((eventString = reader.readLine()) != null) {
+                try {
+                    m_log.log(LogService.LOG_DEBUG, "Log event received: '" + eventString +"'");
+                    LogEvent event = new LogEvent(eventString);
+                    events.add(event);
+                }
+                catch (IllegalArgumentException iae) {
+                    success = false;
+                    m_log.log(LogService.LOG_WARNING, "Could not construct LogEvent from string: '" + eventString + "'");
+                }
+            }
+        }
+        finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                }
+                catch (Exception ex) {
+                    // not much we can do
+                }
+            }
+        }
+        m_store.put(events);
+        return success;
+    }
+
+    // print string representations of all events in the specified range to the specified output
+    private void outputRange(ServletOutputStream output, LogDescriptor range) throws IOException {
+        List<LogEvent> events = m_store.get(range);
+        for (LogEvent event : events) {
+            output.print(event.toRepresentation() + "\n");
+        }
+    }
+
+    // send an error response
+    private void sendError(HttpServletResponse response, int statusCode, String description) {
+        m_log.log(LogService.LOG_WARNING, "Log request failed: " + description);
+        try {
+            response.sendError(statusCode, description);
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_WARNING, "Unable to send error response", e);
+        }
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "Log Endpoint (channel=" + m_name + ")";
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/LogStore.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/LogStore.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/LogStore.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/LogStore.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,76 @@
+package net.luminis.liq.server.log.store;
+
+
+import java.io.IOException;
+import java.util.List;
+
+import net.luminis.liq.log.LogEvent;
+import net.luminis.liq.log.LogDescriptor;
+
+/**
+ * Log store interface. Implementation of this service interface provide a persisted storage for LogEvent logs.
+ */
+public interface LogStore {
+	
+    /**
+     * Event topic that indicates a new LogEvent that has been added to the store. The name
+     * of the log is available as EVENT_PROP_LOGNAME, the original LogEvent as EVENT_PROP_LOG_EVENT.
+     */
+    public static final String EVENT_TOPIC = LogStore.class.getName().replace('.', '/') + "/LogEvent";
+    
+    /**
+     * Event property key containing the name of the log on which the LogEvent has been added. 
+     */
+    public static final String EVENT_PROP_LOGNAME = "name";
+    
+    /**
+     * Event property key containing the LogEvent that has been added. 
+     */
+    public static final String EVENT_PROP_LOG_EVENT = "logEvent";
+
+    /**
+     * Return all events in a given range.
+     *
+     * @param range the range to filter events by.
+     * @return a list of all events in this store that are in the given range.
+     * @throws IOException in case of any error.
+     */
+    public List<LogEvent> get(LogDescriptor range) throws IOException;
+
+    /**
+     * Get the range for the given id and the given log.
+     *
+     * @param gatewayID the id for which to return the log range.
+     * @param logID the log id for which to return the range.
+     * @return the range for the given id and the given log.
+     * @throws IOException in case of any error.
+     */
+    public LogDescriptor getDescriptor(String gatewayID, long logID) throws IOException;
+
+    /**
+     * Store the given events. The implementation does not have to be transactional i.e., it might throw an exception and still
+     * store part of the events. However, individual events should be either stored or not.
+     *
+     * @param events a list of events to store.
+     * @throws IOException in case of any error. It might be possible that only part of the events get stored.
+     */
+    public void put(List<LogEvent> events) throws IOException;
+
+    /**
+     * Get the ranges of all logs of the given id.
+     *
+     * @param gatewayID the id for which to return the log ranges.
+     * @return a list of the ranges of all logs for the given id.
+     * @throws IOException in case of any error.
+     */
+    public List<LogDescriptor> getDescriptors(String gatewayID) throws IOException;
+
+    /**
+     * Get the ranges of all logs of all ids in this store.
+     *
+     * @return a list of ranges of all logs for all ids in this store.
+     * @throws IOException in case of any error.
+     */
+    public List<LogDescriptor> getDescriptors() throws IOException;
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,89 @@
+package net.luminis.liq.server.log.store.impl;
+
+import java.io.File;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase implements ManagedServiceFactory {
+
+    private static final String LOG_NAME = "name";
+    private DependencyManager m_manager;
+    private final Map<String, Service> m_instances = new HashMap<String, Service>();
+    private BundleContext m_context;
+    private volatile LogService m_log;
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        m_context = context;
+        m_manager = manager;
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "net.luminis.liq.server.log.store.factory");
+        manager.add(createService()
+            .setInterface(ManagedServiceFactory.class.getName(), props)
+            .setImplementation(this)
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+    }
+
+    public void deleted(String pid) {
+        Service log = m_instances.remove(pid);
+        if (log != null) {
+            m_manager.remove(log);
+            delete(new File(m_context.getDataFile(""), pid));
+        }
+    }
+
+    private void delete(File root) {
+        if (root.isDirectory()) {
+            for (File file : root.listFiles()) {
+                delete(file);
+            }
+        }
+        root.delete();
+    }
+
+    public String getName() {
+        return "Log Store Factory";
+    }
+
+    @SuppressWarnings("unchecked")
+    public synchronized void updated(String pid, Dictionary dict) throws ConfigurationException {
+        String name = (String) dict.get(LOG_NAME);
+        if ((name == null) || "".equals(name)) {
+            throw new ConfigurationException(LOG_NAME, "Log name has to be specified.");
+        }
+
+        Service service = m_instances.get(pid);
+        if (service == null) {
+            Properties props = new Properties();
+            props.put(LOG_NAME, name);
+            File baseDir = new File(m_context.getDataFile(""), pid);
+            service = m_manager.createService()
+                .setInterface(LogStore.class.getName(), props)
+                .setImplementation(new LogStoreImpl(baseDir, name))
+                .add(createServiceDependency().setService(EventAdmin.class).setRequired(false));
+            m_instances.put(pid, service);
+            m_manager.add(service);
+        } else {
+            m_log.log(LogService.LOG_INFO, "Ignoring configuration update because factory instance was already configured: " + name);
+        }
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/LogStoreImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/LogStoreImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/LogStoreImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/store/impl/LogStoreImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,276 @@
+package net.luminis.liq.server.log.store.impl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import net.luminis.liq.log.LogEvent;
+import net.luminis.liq.log.LogDescriptor;
+import net.luminis.liq.repository.SortedRangeSet;
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * A simple implementation of the LogStore interface.
+ */
+public class LogStoreImpl implements LogStore {
+
+    private volatile EventAdmin m_eventAdmin; /* Injected by dependency manager */
+
+    // the dir to store logs in - init is in the start method
+    private final File m_dir;
+    private final String m_name;
+
+    public LogStoreImpl(File baseDir, String name) {
+        m_name = name;
+        m_dir = new File(baseDir, "store");
+    }
+
+    /*
+     * init the dir in which to store logs in - thows IllegalArgumentException if we can't get it.
+     */
+    protected void start() throws IOException {
+        if (!m_dir.isDirectory() && !m_dir.mkdirs()) {
+            throw new IllegalArgumentException("Need valid dir");
+        }
+    }
+
+    /**
+     * @see net.luminis.liq.server.log.store.LogStore#get(net.luminis.liq.log.LogDescriptor)
+     */
+    public synchronized List<LogEvent> get(LogDescriptor descriptor) throws IOException {
+        final List<LogEvent> result = new ArrayList<LogEvent>();
+        final SortedRangeSet set = descriptor.getRangeSet();
+        BufferedReader in = null;
+        try {
+            File log = new File(new File(m_dir, gatewayIDToFilename(descriptor.getGatewayID())), String.valueOf(descriptor.getLogID()));
+            if (!log.isFile()) {
+                return result;
+            }
+            in = new BufferedReader(new FileReader(log));
+            for (String line = in.readLine(); line != null; line = in.readLine()) {
+                LogEvent event = new LogEvent(line);
+                if (set.contains(event.getID())) {
+                    result.add(event);
+                }
+            }
+        }
+        finally {
+            if (in != null) {
+                try {
+                    in.close();
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see net.luminis.liq.server.log.store.LogStore#getDescriptor(java.lang.String, long)
+     */
+    public LogDescriptor getDescriptor(String gatewayID, long logID) throws IOException {
+        List<Long> ids = new ArrayList<Long>();
+        for (LogEvent event : get(new LogDescriptor(gatewayID, logID, SortedRangeSet.FULL_SET))) {
+            ids.add(event.getID());
+        }
+        long[] idsArray = new long[ids.size()];
+        int i = 0;
+        for (Long l : ids) {
+            idsArray[i++] = l;
+        }
+        return new LogDescriptor(gatewayID, logID, new SortedRangeSet(idsArray));
+    }
+
+    /**
+     * @see net.luminis.liq.server.log.store.LogStore#getDescriptors(java.lang.String)
+     */
+    public List<LogDescriptor> getDescriptors(String gatewayID) throws IOException {
+        File dir = new File(m_dir, gatewayIDToFilename(gatewayID));
+        List<LogDescriptor> result = new ArrayList<LogDescriptor>();
+        if (!dir.isDirectory()) {
+            return result;
+        }
+
+        for (String name : notNull(dir.list())) {
+            result.add(getDescriptor(gatewayID, Long.parseLong(name)));
+        }
+
+        return result;
+    }
+
+    /**
+     * @see net.luminis.liq.server.log.store.LogStore#getDescriptors()
+     */
+    public List<LogDescriptor> getDescriptors() throws IOException {
+        List<LogDescriptor> result = new ArrayList<LogDescriptor>();
+        for (String name : notNull(m_dir.list())) {
+            result.addAll(getDescriptors(filenameToGatewayID(name)));
+        }
+        return result;
+    }
+
+    /**
+     * @see net.luminis.liq.server.log.store.LogStore#put(java.util.List)
+     */
+    public void put(List<LogEvent> events) throws IOException {
+        Map<String, Map<Long, List<LogEvent>>> sorted = sort(events);
+        for (String gatewayID : sorted.keySet()) {
+            for (Long logID : sorted.get(gatewayID).keySet()) {
+                put(gatewayID, logID, sorted.get(gatewayID).get(logID));
+            }
+        }
+    }
+
+    /**
+     * Add a list of events to the log of the given ids.
+     *
+     * @param gatewayID the id of the gateway to append to its log.
+     * @param logID the id of the given gateway log.
+     * @param list a list of events to store.
+     * @throws IOException in case of any error.
+     */
+    protected synchronized void put(String gatewayID, Long logID, List<LogEvent> list) throws IOException {
+        if ((list == null) || (list.size() == 0)) {
+            // nothing to add, so return
+            return;
+        }
+        // we actually need to distinguish between two scenarios here:
+        // 1. we can append events at the end of the existing file
+        // 2. we need to insert events in the existing file (meaning we have to rewrite basically the whole file)
+        List<LogEvent> events = get(new LogDescriptor(gatewayID, logID, SortedRangeSet.FULL_SET));
+        // remove duplicates first
+        list.removeAll(events);
+
+        if (list.size() == 0) {
+            //nothing to add anymore, so return
+            return;
+        }
+
+        PrintWriter out = null;
+        try {
+            File dir = new File(m_dir, gatewayIDToFilename(gatewayID));
+            if (!dir.isDirectory() && !dir.mkdirs()) {
+                throw new IOException("Unable to create backup store.");
+            }
+            if ((events.size() == 0) || (events.get(events.size() - 1).getID() < list.get(0).getID())) {
+                // we can append to the existing file
+                out = new PrintWriter(new FileWriter(new File(dir, logID.toString()), true));
+            }
+            else {
+                // we have to merge the lists
+                list.addAll(events);
+                // and sort
+                Collections.sort(list);
+                out = new PrintWriter(new FileWriter(new File(dir, logID.toString())));
+            }
+            for (LogEvent event : list) {
+                out.println(event.toRepresentation());
+                // send (eventadmin)event about a new (log)event being stored
+                Dictionary props = new Hashtable();
+                props.put(LogStore.EVENT_PROP_LOGNAME, m_name);
+                props.put(LogStore.EVENT_PROP_LOG_EVENT, event);
+                m_eventAdmin.postEvent(new Event(LogStore.EVENT_TOPIC, props));
+            }
+        }
+        finally {
+            try {
+                out.close();
+            }
+            catch (Exception ex) {
+                // Not much we can do
+            }
+        }
+    }
+
+    /**
+     * Sort the given list of events into a map of maps according to the gatewayID and the logID of each event.
+     *
+     * @param events a list of events to sort.
+     * @return a map of maps that maps gateway ids to a map that maps log ids to a list of events that have those ids.
+     */
+    @SuppressWarnings("boxing")
+    protected Map<String, Map<Long, List<LogEvent>>> sort(List<LogEvent> events) {
+        Map<String, Map<Long, List<LogEvent>>> result = new HashMap<String, Map<Long, List<LogEvent>>>();
+        for (LogEvent event : events) {
+            Map<Long, List<LogEvent>> gateway = result.get(event.getGatewayID());
+
+            if (gateway == null) {
+                gateway = new HashMap<Long, List<LogEvent>>();
+                result.put(event.getGatewayID(), gateway);
+            }
+
+            List<LogEvent> list = gateway.get(event.getLogID());
+            if (list == null) {
+                list = new ArrayList<LogEvent>();
+                gateway.put(event.getLogID(), list);
+            }
+
+            list.add(event);
+        }
+        return result;
+    }
+
+    /*
+     * throw IOException in case the target is null else return the target.
+     */
+    private <T> T notNull(T target) throws IOException {
+        if (target == null) {
+            throw new IOException("Unknown IO error while trying to access the store.");
+        }
+        return target;
+    }
+
+    private static String filenameToGatewayID(String filename) {
+        byte[] bytes = new byte[filename.length() / 2];
+        for (int i = 0; i < (filename.length() / 2); i ++) {
+            String hexValue = filename.substring(i * 2, (i + 1) * 2);
+            bytes[i] = Byte.parseByte(hexValue, 16);
+        }
+
+        String result = null;
+        try {
+            result = new String(bytes, "UTF-8");
+        }
+        catch (UnsupportedEncodingException e) {
+            // UTF-8 is a mandatory encoding; this will never happen.
+        }
+        return result;
+    }
+
+    private static String gatewayIDToFilename(String gatewayID) {
+        StringBuilder result = new StringBuilder();
+
+        try {
+            for (Byte b : gatewayID.getBytes("UTF-8")) {
+                String hexValue = Integer.toHexString(b.intValue());
+                if (hexValue.length() % 2 == 0) {
+                    result.append(hexValue);
+                }
+                else {
+                    result.append('0').append(hexValue);
+                }
+            }
+        }
+        catch (UnsupportedEncodingException e) {
+            // UTF-8 is a mandatory encoding; this will never happen.
+        }
+
+        return result.toString();
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,79 @@
+package net.luminis.liq.server.log.task;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import net.luminis.liq.discovery.Discovery;
+import net.luminis.liq.log.LogSync;
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase implements ManagedServiceFactory {
+
+    private static final String LOG_NAME = "name";
+    private DependencyManager m_manager;
+    private final Map<String, Service> m_instances = new HashMap<String, Service>();
+    private volatile LogService m_log;
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        m_manager = manager;
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "net.luminis.liq.server.log.task.factory");
+        manager.add(createService()
+            .setInterface(ManagedServiceFactory.class.getName(), props)
+            .setImplementation(this)
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+    }
+
+    public void deleted(String pid) {
+        Service service = m_instances.remove(pid);
+        if (service != null) {
+            m_manager.remove(service);
+        }
+    }
+
+    public String getName() {
+        return "Log Sync Task Factory";
+    }
+
+    @SuppressWarnings("unchecked")
+    public synchronized void updated(String pid, Dictionary dict) throws ConfigurationException {
+        String name = (String) dict.get(LOG_NAME);
+        if ((name == null) || "".equals(name)) {
+            throw new ConfigurationException(LOG_NAME, "Log name has to be specified.");
+        }
+
+        Service service = m_instances.get(pid);
+        if (service == null) {
+            Properties props = new Properties();
+            props.put(LOG_NAME, name);
+            props.put("taskName", LogSyncTask.class.getName());
+            props.put("description", "Syncs log (name=" + name + ") with a server.");
+            service = m_manager.createService()
+                .setInterface(new String[] { Runnable.class.getName(), LogSync.class.getName() }, props)
+                .setImplementation(new LogSyncTask(name, name))
+                .add(createServiceDependency().setService(LogStore.class, "(&("+Constants.OBJECTCLASS+"="+LogStore.class.getName()+")(name=" + name + "))").setRequired(true))
+                .add(createServiceDependency().setService(Discovery.class).setRequired(true))
+                .add(createServiceDependency().setService(LogService.class).setRequired(false));
+            m_instances.put(pid, service);
+            m_manager.add(service);
+        } else {
+            m_log.log(LogService.LOG_INFO, "Ignoring configuration update because factory instance was already configured: " + name);
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/LogSyncTask.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/LogSyncTask.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/LogSyncTask.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/server/log/task/LogSyncTask.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,312 @@
+package net.luminis.liq.server.log.task;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.luminis.liq.discovery.Discovery;
+import net.luminis.liq.log.LogDescriptor;
+import net.luminis.liq.log.LogEvent;
+import net.luminis.liq.log.LogSync;
+import net.luminis.liq.repository.SortedRangeSet;
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.osgi.service.log.LogService;
+
+public class LogSyncTask implements Runnable, LogSync {
+
+    private static final String COMMAND_QUERY = "query";
+    private static final String COMMAND_SEND = "send";
+    private static final String COMMAND_RECEIVE = "receive";
+
+    private static final String GWID_KEY = "gwid";
+    private static final String FILTER_KEY = "filter";
+    private static final String LOGID_KEY = "logid";
+    private static final String RANGE_KEY = "range";
+
+    // injected by dependencymanager
+    private volatile Discovery m_discovery;
+    private volatile LogService m_log;
+    private volatile LogStore m_logStore;
+    private final String m_endpoint;
+    private final String m_name;
+
+    public LogSyncTask(String endpoint, String name) {
+        m_endpoint = endpoint;
+        m_name = name;
+    }
+
+    public void run() {
+        try {
+            push();
+        }
+        catch (MalformedURLException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (fully) synchronize log (name=" + m_name + ") with remote");
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (fully) synchronize log (name=" + m_name + ") with remote", e);
+        }
+    }
+
+    public boolean pull() throws IOException {
+        return synchronize(false, true);
+    }
+
+    public boolean push() throws IOException {
+        return synchronize(true, false);
+    }
+
+    public boolean pushpull() throws IOException {
+        return synchronize(true, true);
+    }
+
+    /**
+     * Synchronizes the local store with the discovered remote one.
+     * @throws IOException
+     */
+    private boolean synchronize(boolean push, boolean pull) throws IOException {
+        URL host = m_discovery.discover();
+
+        Connection queryConnection = new Connection(new URL(host, m_endpoint + "/" + COMMAND_QUERY));
+        InputStream queryInput = queryConnection.getInputStream();
+
+        List<LogDescriptor> localRanges = m_logStore.getDescriptors();
+        List<LogDescriptor> remoteRanges = getRanges(queryInput);
+
+        boolean result = false;
+        if (push) {
+            result |= doPush(host, localRanges, remoteRanges);
+        }
+        if (pull) {
+            result |= doPull(host, localRanges, remoteRanges);
+        }
+        return result;
+    }
+
+    protected boolean doPush(URL host, List<LogDescriptor> localRanges, List<LogDescriptor> remoteRanges) {
+        boolean result = false;
+        OutputStream sendOutput = null;
+        try {
+            Connection sendConnection = new Connection(new URL(host, m_endpoint + "/" + COMMAND_SEND));
+            sendOutput = sendConnection.getOutputStream();
+
+            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sendOutput));
+            List<LogDescriptor> delta = calculateDelta(localRanges, remoteRanges);
+            result = !delta.isEmpty();
+            writeDelta(delta, writer);
+
+            sendOutput.flush();
+            sendOutput.close();
+            sendConnection.close();
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (fully) synchronize log with remote", e);
+        }
+        finally {
+            if (sendOutput != null) {
+                try {
+                    sendOutput.close();
+                }
+                catch (Exception ex) {
+                    // not much we can do
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Writes the difference between local and remote to a writer.
+     * @param descriptors A list of LogDescriptors that identifies all local log entries that need to be written.
+     * @param writer A writer to write to.
+     * @throws IOException
+     */
+    protected void writeDelta(List<LogDescriptor> descriptors, Writer writer) throws IOException {
+        for (LogDescriptor l : descriptors) {
+            writeLogDescriptor(l, writer);
+        }
+    }
+
+    /**
+     * Writes the LogEvents described by the descriptor to the writer.
+     * @param descriptor A LogDescriptor that identifies the events to be written.
+     * @param writer A writer to write the events to.
+     * @throws IOException Thrown when either the writer goes wrong, or there is a problem
+     * communicating with the local log store.
+     */
+    protected void writeLogDescriptor(LogDescriptor descriptor, Writer writer) throws IOException {
+        List<LogEvent> events = m_logStore.get(descriptor);
+        for (LogEvent event : events) {
+            writer.write(event.toRepresentation() + "\n");
+        }
+        writer.flush();
+    }
+
+    protected boolean doPull(URL host, List<LogDescriptor> localRanges, List<LogDescriptor> remoteRanges) {
+        boolean result = false;
+        List<LogDescriptor> delta = calculateDelta(remoteRanges, localRanges);
+        result = !delta.isEmpty();
+        for (LogDescriptor l : delta) {
+            Connection receiveConnection;
+            try {
+                /*
+                 * The request currently contains a range. This is not yet supported by the servlet, but it will
+                 * simply be ignored.
+                 */
+                receiveConnection = new Connection(new URL(host, m_endpoint + "/" + COMMAND_RECEIVE + "?" + GWID_KEY +
+                    "=" + l.getGatewayID() + "&" + LOGID_KEY + "=" + l.getLogID() + "&" + RANGE_KEY + "=" + l.getRangeSet().toRepresentation()));
+                InputStream receiveInput = receiveConnection.getInputStream();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(receiveInput));
+                readLogs(reader);
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Unable to connect to retrieve log events.", e);
+            }
+        }
+        return result;
+    }
+
+    protected void readLogs(BufferedReader reader) {
+        try {
+            List<LogEvent> events = new ArrayList<LogEvent>();
+
+            String eventString = null;
+            while ((eventString = reader.readLine()) != null) {
+                try {
+                    LogEvent event = new LogEvent(eventString);
+                    events.add(event);
+                }
+                catch (IllegalArgumentException e) {
+                    // Just skip this one.
+                }
+            }
+            m_logStore.put(events);
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_DEBUG, "Error reading line from reader", e);
+        }
+
+    }
+
+    /**
+     * Calculates the difference between two lists of <code>LogDescriptor</code>. The result will contain whatever is
+     * not in <code>destination</code>, but is in <code>source</code>.
+     */
+    protected List<LogDescriptor> calculateDelta(List<LogDescriptor> source, List<LogDescriptor> destination) {
+        /*
+         * For each local descriptor, we try to find a matching remote one. If so, we will synchronize all events
+         * that the remote does not have. If we do not find a matching one at all, we send the complete local
+         * log.
+         */
+        List<LogDescriptor> result = new ArrayList<LogDescriptor>();
+        for (LogDescriptor s : source) {
+            LogDescriptor diffs = s;
+            for (LogDescriptor d : destination) {
+                if ((s.getLogID() == d.getLogID()) && (s.getGatewayID().equals(d.getGatewayID()))) {
+                    SortedRangeSet rangeDiff = d.getRangeSet().diffDest(s.getRangeSet());
+                    if (!isEmptyRangeSet(rangeDiff)) {
+                        diffs = new LogDescriptor(s.getGatewayID(), s.getLogID(), rangeDiff);
+                    }
+                    else {
+                        diffs = null;
+                    }
+                }
+            }
+            if (diffs != null) {
+                result.add(diffs);
+            }
+        }
+        return result;
+    }
+
+    private boolean isEmptyRangeSet(SortedRangeSet set) {
+        return !set.iterator().hasNext();
+    }
+
+    protected List<LogDescriptor> getRanges(InputStream stream) throws IOException {
+        List<LogDescriptor> result = new ArrayList<LogDescriptor>();
+        BufferedReader queryReader = null;
+        try {
+            queryReader = new BufferedReader(new InputStreamReader(stream));
+
+            for (String line = queryReader.readLine(); line != null; line = queryReader.readLine()) {
+                try {
+                    result.add(new LogDescriptor(line));
+                }
+                catch (IllegalArgumentException iae) {
+                    throw new IOException("Could not determine highest remote event id, received malformed event range: " + line);
+                }
+            }
+        }
+        finally {
+            if (queryReader != null) {
+                try {
+                    queryReader.close();
+                }
+                catch (Exception ex) {
+                    // not much we can do
+                }
+            }
+        }
+        return result;
+
+    }
+
+    // helper class that abstracts handling of a URLConnection somewhat.
+    private class Connection {
+        private URLConnection m_connection;
+
+        public Connection(URL url) throws IOException {
+            m_connection = url.openConnection();
+        }
+
+        /**
+         * Enables the retrieving of input using this connection and returns an inputstream
+         * to the connection.
+         *
+         * @return Inputstream to the connection.
+         * @throws IOException If I/O problems occur.
+         */
+        public InputStream getInputStream() throws IOException {
+            m_connection.setDoInput(true);
+            return m_connection.getInputStream();
+        }
+
+        /**
+         * Enables the sending of output using this connection and returns an outputstream
+         * to the connection.
+         *
+         * @return Outputstream to the connection.
+         * @throws IOException If I/O problems occur.
+         */
+        public OutputStream getOutputStream() throws IOException {
+            m_connection.setDoOutput(true);
+            return m_connection.getOutputStream();
+        }
+
+        /**
+         * Should be called when a <code>Connection</code> is used to do a POST (write to it's outputstream)
+         * without reading it's inputstream (the response). Calling this will make sure the POST request is sent.
+         *
+         * @throws IOException If I/O problems occur dealing with the connection.
+         */
+        public void close() throws IOException {
+            m_connection.getContent();
+        }
+
+    }
+
+    public String getName() {
+        return m_name;
+    }
+}

Added: incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/Activator.java (added)
+++ incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,63 @@
+/*
+ * 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.deployment.rp.autoconf;
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.deployment.rp.autoconf.impl.AutoConfResourceProcessor;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.log.LogService;
+
+/**
+ * Bundle activator for the AutoConf Resource Processor Customizer bundle
+ *
+ * @author  Christian van Spaandonk
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+    	Dictionary properties = new Properties();
+        properties.put(Constants.SERVICE_PID, "org.osgi.deployment.rp.autoconf");
+
+        manager.add(createService()
+            .setInterface(ResourceProcessor.class.getName(), properties)
+            .setImplementation(AutoConfResourceProcessor.class)
+            .add(createServiceDependency()
+                .setService(ConfigurationAdmin.class)
+                .setRequired(true))
+            .add(createServiceDependency()
+                .setService(ConfigurationAdmin.class)
+                .setRequired(false))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+}
\ No newline at end of file

Added: incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResource.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResource.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResource.java (added)
+++ incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResource.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,34 @@
+package org.apache.felix.deployment.rp.autoconf.impl;
+
+import java.util.Dictionary;
+
+public class AutoConfResource {
+
+	public String m_pid;
+	public String m_factoryPid;
+	public Dictionary m_oldProps;
+	public Dictionary m_newProps;
+	
+	public AutoConfResource(String pid, String factoryPid, Dictionary oldProps, Dictionary newProps) {
+		m_pid = pid;
+		m_factoryPid = factoryPid;
+		m_newProps = oldProps;
+		m_newProps = newProps;
+	}
+
+	public String getPid() {
+		return m_pid;
+	}
+
+	public String getFactoryPid() {
+		return m_factoryPid;
+	}
+
+	public Dictionary getOldProps() {
+		return m_oldProps;
+	}
+
+	public Dictionary getNewProps() {
+		return m_newProps;
+	}
+}

Added: incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResourceProcessor.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResourceProcessor.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResourceProcessor.java (added)
+++ incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/AutoConfResourceProcessor.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,419 @@
+/*
+ * 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.deployment.rp.autoconf.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Vector;
+
+import org.apache.felix.metatype.Attribute;
+import org.apache.felix.metatype.Designate;
+import org.apache.felix.metatype.DesignateObject;
+import org.apache.felix.metatype.MetaData;
+import org.apache.felix.metatype.MetaDataReader;
+import org.apache.felix.metatype.OCD;
+import org.apache.felix.metatype.internal.LocalizedObjectClassDefinition;
+import org.apache.felix.metatype.internal.l10n.BundleResources;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.deploymentadmin.spi.DeploymentSession;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class AutoConfResourceProcessor implements ResourceProcessor {
+
+	private static final String LOCATION_PREFIX = "osgi-dp:";
+	
+	private volatile LogService m_log;					// injected by dependency manager
+	private volatile ConfigurationAdmin m_configAdmin;	// injected by dependency manager
+	private volatile MetaTypeService m_metaService;		// injected by dependency manager
+	private volatile BundleContext m_bc;				// injected by dependency manager
+	
+	private DeploymentSession m_session = null;
+
+	private Map m_currentDesignates = new HashMap();
+	private Map m_currentProps = new HashMap();
+	
+	/**
+	  * Called when the Deployment Admin starts a new operation on the given deployment package, 
+	  * and the resource processor is associated a resource within the package. Only one 
+	  * deployment package can be processed at a time.
+	  * 
+	  * @param session object that represents the current session to the resource processor
+	  * @see DeploymentSession
+	  */
+   public void begin(DeploymentSession session) {
+	   m_log.log(LogService.LOG_DEBUG, "Begin called" + ", rp instance:" + this);
+	   m_session = session;
+   }
+ 
+   /**
+    * Called when a resource is encountered in the deployment package for which this resource 
+    * processor has been  selected to handle the processing of that resource.
+    * 
+    * @param name The name of the resource relative to the deployment package root directory. 
+    * @param stream The stream for the resource. 
+    * @throws ResourceProcessorException if the resource cannot be processed. Only 
+    *         {@link ResourceProcessorException#CODE_RESOURCE_SHARING_VIOLATION} and 
+    *         {@link ResourceProcessorException#CODE_OTHER_ERROR} error codes are allowed.
+    */
+   public void process(String name, InputStream stream) throws ResourceProcessorException {
+	   m_log.log(LogService.LOG_DEBUG, "Process called for resource: " + name + ", rp instance:" + this);
+	   if (m_session == null) {
+		   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Can not process resource without a Deployment Session" + ", rp instance:" + this);
+	   }
+	   MetaDataReader reader = new MetaDataReader();
+	   try {
+		   MetaData data = reader.parse(stream);
+		   Map designates = data.getDesignates();
+		   Map ocds = data.getObjectClassDefinitions();
+		   Set keySet = designates.keySet();
+		   Iterator it = keySet.iterator();
+		   while (it.hasNext()) {
+			   Designate designate = (Designate) designates.get(it.next());
+			   if (designate.getFactoryPid() != null || "".equals(designate.getFactoryPid())) {
+				   // TODO: support factory configurations
+				   m_log.log(LogService.LOG_ERROR, "Factory configurations are not supported yet.");
+				   continue;
+			   }
+			   
+			   // determine bundle
+			   String bundleLocation = designate.getBundleLocation();
+			   Bundle bundle = null;
+			   if (bundleLocation.startsWith(LOCATION_PREFIX)) {
+				   bundle = m_session.getSourceDeploymentPackage().getBundle(bundleLocation.substring(LOCATION_PREFIX.length()));
+			   }
+			   if (bundle == null) {
+				   // added to allow foreign bundles
+				   Bundle[] bundles = m_bc.getBundles();
+				   for (int i = 0; i < bundles.length; i++) {
+					   String location = bundles[i].getLocation();
+					   if (bundleLocation.equals(location)) {
+						   bundle = bundles[i];
+						   break;
+					   }
+				   }
+				   // end of foreign bundle code
+				   if (bundle == null) {
+					   throw new ResourceProcessorException(ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION);
+				   }
+			   }
+			   
+			   // find correct ocd
+			   DesignateObject designateObject = designate.getObject();
+			   String ocdRef = designateObject.getOcdRef();
+			   OCD internalOcd = (OCD) ocds.get(ocdRef);
+			   ObjectClassDefinition ocd = null;
+			   if (internalOcd != null) {
+				   // use local ocd
+				   ocd = new LocalizedObjectClassDefinition(bundle, internalOcd, BundleResources.getResources(bundle, "", ""));
+				   // TODO: getting a 'Resources' object like this is probably not a good idea
+			   }
+			   else {
+				   // obtain ocd from metatypeservice
+				   MetaTypeInformation mti = m_metaService.getMetaTypeInformation(bundle);
+				   if (mti != null) {
+					   try {
+						   ocd = mti.getObjectClassDefinition(designate.getPid(), null);
+					   }
+					   catch (IllegalArgumentException iae) {
+						   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Unable to get Object Class Definition.", iae);
+					   }
+				   }
+				   else {
+					   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Unable to get Object Class Definition.");
+				   }
+			   }
+			   
+			   // match attributes with attribute definitions from ocd
+			   Dictionary newProps = new Properties();
+			   AttributeDefinition[] ads = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
+			   List attributes = designateObject.getAttributes();
+			   Iterator it2 = attributes.iterator();
+			   while (it2.hasNext()) {
+				   Attribute attribute = (Attribute) it2.next();
+				   String adRef = attribute.getAdRef();
+				   boolean found = false;
+				   for(int i = 0; i < ads.length; i++) {
+					   AttributeDefinition ad = ads[i];
+					   if (adRef.equals(ad.getID())) {
+						   Object value = getValue(attribute, ad);
+						   m_log.log(LogService.LOG_DEBUG, "RP FOUND VALUE: adref=" + adRef + ", value=" + value);
+						   if (value == null && !designate.isOptional()) {
+							   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Could not match attribute to it's definition adref=" + adRef);
+						   }
+						   newProps.put(adRef, value);
+						   found = true;
+						   break;
+					   }
+				   }
+				   if (!found && !designate.isOptional()) {
+					   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Could not find attribute definition: adref=" + adRef);
+				   }
+			   }
+
+			   m_currentDesignates.put(name, designate);
+			   m_currentProps.put(name, newProps);
+		   }
+		} catch (IOException e) {
+			throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Unable to process resource due to I/O problems.");
+		} catch (XmlPullParserException e) {
+			throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Supplied configuration is not conform the metatype xml specification.");
+		}
+    }
+   
+	/**
+	  * Called when a resource, associated with a particular resource processor, had belonged to 
+	  * an earlier version of a deployment package but is not present in the current version of 
+	  * the deployment package.  This provides an opportunity for the processor to cleanup any 
+	  * memory and persistent data being maintained for the particular resource.  
+	  * This method will only be called during "update" deployment sessions.
+	  * 
+	  * @param resource the name of the resource to drop (it is the same as the value of the 
+	  *        "Name" attribute in the deployment package's manifest)
+	  * @throws ResourceProcessorException if the resource is not allowed to be dropped. Only the 
+	  *         {@link ResourceProcessorException#CODE_OTHER_ERROR} error code is allowed
+	  */
+   public void dropped(String resource) throws ResourceProcessorException {
+	   m_log.log(LogService.LOG_DEBUG, "Dropped called for resource: " + resource + ", rp instance:" + this);
+	   if (m_session == null) {
+		   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Can not process resource without a Deployment Session" + ", rp instance:" + this);
+	   }
+   }
+   
+   /**
+    * This method is called during an "uninstall" deployment session.
+    * This method will be called on all resource processors that are associated with resources 
+    * in the deployment package being uninstalled. This provides an opportunity for the processor 
+    * to cleanup any memory and persistent data being maintained for the deployment package.
+    * 
+    * @throws ResourceProcessorException if all resources could not be dropped. Only the 
+    *         {@link ResourceProcessorException#CODE_OTHER_ERROR} is allowed.
+    */
+   public void dropAllResources() throws ResourceProcessorException {
+	   m_log.log(LogService.LOG_DEBUG, "DropAllResources called" + ", rp instance:" + this);
+	   if (m_session == null) {
+		   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Can not process resource without a Deployment Session" + ", rp instance:" + this);
+	   }
+   }
+ 
+   /**
+    * This method is called on the Resource Processor immediately before calling the 
+    * <code>commit</code> method. The Resource Processor has to check whether it is able 
+    * to commit the operations since the last <code>begin</code> method call. If it determines 
+    * that it is not able to commit the changes, it has to raise a 
+    * <code>ResourceProcessorException</code> with the {@link ResourceProcessorException#CODE_PREPARE} 
+    * error code.
+    * 
+    * @throws ResourceProcessorException if the resource processor is able to determine it is 
+    *         not able to commit. Only the {@link ResourceProcessorException#CODE_PREPARE} error 
+    *         code is allowed.
+    */
+   public void prepare() throws ResourceProcessorException {
+	   m_log.log(LogService.LOG_DEBUG, "Prepare called" + ", rp instance:" + this);
+	   if (m_session == null) {
+		   throw new ResourceProcessorException(ResourceProcessorException.CODE_OTHER_ERROR, "Can not process resource without a Deployment Session" + ", rp instance:" + this);
+	   }
+   }
+  
+   /**
+    * Called when the processing of the current deployment package is finished. 
+    * This method is called if the processing of the current deployment package was successful, 
+    * and the changes must be made permanent.
+    */
+   public synchronized void commit() {
+	   m_log.log(LogService.LOG_DEBUG, "Commit called" + ", rp instance:" + this);
+	   Set keySet = m_currentDesignates.keySet();
+	   Iterator i = keySet.iterator();
+	   while (i.hasNext()) {
+		   String key = (String) i.next();
+		   Designate designate = (Designate) m_currentDesignates.get(key);
+		   Dictionary newProps = (Dictionary) m_currentProps.get(key);
+		   if (newProps == null) {
+			   m_log.log(LogService.LOG_DEBUG, "Internal error: no new properties found for resource (name=" + key + ")" + ", rp instance:" + this);
+		   }
+		   // update configuration
+		   try {
+			   Configuration configuration = m_configAdmin.getConfiguration(designate.getPid(), designate.getBundleLocation());
+			   if (designate.isMerge()) {
+				   Dictionary currentProps = configuration.getProperties();
+				   if (currentProps != null) {
+					   Enumeration keys = currentProps.keys();
+					   while (keys.hasMoreElements()) {
+						   Object propKey = keys.nextElement();
+						   newProps.put(propKey, currentProps.get(propKey));
+					   }
+				   }
+			   }
+			   configuration.update(newProps);
+		   }
+		   catch (IOException ioe) {
+			   m_log.log(LogService.LOG_DEBUG, "Could not commit resource (name=" + key + ")" + ", rp instance:" + this);
+		   }
+	   }
+	   m_currentDesignates.clear();
+	   m_currentProps.clear();
+	   m_session = null;
+   }
+    
+   /**
+    * Called when the processing of the current deployment package is finished. 
+    * This method is called if the processing of the current deployment package was unsuccessful, 
+    * and the changes made during the processing of the deployment package should be removed.  
+    */
+   public void rollback() {
+	   m_log.log(LogService.LOG_DEBUG, "Rollback called" + ", rp instance:" + this);
+	   m_currentDesignates.clear();
+	   m_currentProps.clear();
+	   m_session = null;
+   }
+   
+   /**
+    * Processing of a resource passed to the resource processor may take long. 
+    * The <code>cancel()</code> method notifies the resource processor that it should 
+    * interrupt the processing of the current resource. This method is called by the 
+    * <code>DeploymentAdmin</code> implementation after the
+    * <code>DeploymentAdmin.cancel()</code> method is called.
+    */
+   public void cancel() {
+	   m_log.log(LogService.LOG_DEBUG, "Cancel called" + ", rp instance:" + this);
+	   m_currentDesignates.clear();
+	   m_currentProps.clear();
+	   m_session = null;
+   }
+
+   /**
+    * Gets the values of the specified attribute, return value is based on the specified attribute definitions.
+    * 
+    * @param attribute Attribute containing the value(s)
+    * @param ad Attribute definition
+    * @return Object Object representing the value(s) of the attribute, or <code>null</code> if the attribute did not match it's definition. Object can be
+    * a single object of one of the types specified as constants in <code>AttributeDefinition</code> or a <code>Vector</code> or an <code>array<code> with elements
+    * of one of these types.
+    */
+   private Object getValue(Attribute attribute, AttributeDefinition ad) {
+	   if (!attribute.getAdRef().equals(ad.getID())) {
+		   // wrong attribute or definition
+		   return null;
+	   }
+	   String[] content = attribute.getContent();
+		
+		// verify correct type of the value(s)
+		int type = ad.getType();
+		Object[] typedContent = new Object[content.length];
+		try {
+			for (int i = 0; i < content.length; i++) {
+				String value = content[i];
+				switch (type) {
+				case AttributeDefinition.BOOLEAN:
+					typedContent[i] = Boolean.valueOf(value);	
+					break;
+				case AttributeDefinition.BYTE:
+					typedContent[i] = Byte.valueOf(value);
+					break;
+				case AttributeDefinition.CHARACTER:
+					char[] charArray = value.toCharArray();
+					if (charArray.length == 1) {
+						typedContent[i] = Character.valueOf(charArray[0]);
+					}
+					else {
+						return null;
+					}
+					break;
+				case AttributeDefinition.DOUBLE:
+					typedContent[i] = Double.valueOf(value);
+					break;
+				case AttributeDefinition.FLOAT:
+					typedContent[i] = Float.valueOf(value);
+					break;
+				case AttributeDefinition.INTEGER:
+					typedContent[i] = Integer.valueOf(value);
+					break;
+				case AttributeDefinition.LONG:
+					typedContent[i] = Long.valueOf(value);
+					break;
+				case AttributeDefinition.SHORT:
+					typedContent[i] = Short.valueOf(value);
+					break;
+				case AttributeDefinition.STRING:
+					typedContent[i] = value;
+					break;
+				default:
+					// unsupported type
+					return null;
+				}
+			}
+		}
+		catch (NumberFormatException nfe) {
+			return null;
+		}
+		
+		// verify cardinality of value(s)
+		int cardinality = ad.getCardinality();
+		Object result = null;
+		if (cardinality == 0) {
+			if (typedContent.length == 1) {
+				result = typedContent[0];
+			}
+			else {
+				result = null;
+			}
+		}
+		else if (cardinality == Integer.MIN_VALUE) {
+			result = new Vector(Arrays.asList(typedContent));
+		}
+		else if (cardinality == Integer.MAX_VALUE) {
+			result = typedContent;
+		}
+		else if (cardinality < 0) {
+			if (typedContent.length == cardinality) {
+				result = new Vector(Arrays.asList(typedContent));
+			}
+			else {
+				result = null;
+			}
+		}
+		else if (cardinality > 0) {
+			if (typedContent.length == cardinality) {
+				result = typedContent;
+			}
+			else {
+				result = null;
+			}
+		}
+		return result;
+	}
+}

Added: incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/Test.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/Test.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/Test.java (added)
+++ incubator/ace/trunk/server/src/org/apache/felix/deployment/rp/autoconf/impl/Test.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,73 @@
+package org.apache.felix.deployment.rp.autoconf.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.metatype.MetaData;
+import org.apache.felix.metatype.MetaDataReader;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class Test {
+
+	public static void main(String[] args) throws Exception {
+		   MetaDataReader reader = new MetaDataReader();
+		   File file = new File("/Users/christianvanspaandonk/autoconf/sample.xml");
+		   try {
+			   MetaData data = reader.parse(new FileInputStream(file));
+			   Map designates = data.getDesignates();
+			   Map ocds = data.getObjectClassDefinitions();
+			   Set keySet = designates.keySet();
+			   Iterator it = keySet.iterator();
+//			   while (it.hasNext()) {
+//				   Designate designate = (Designate) designates.get(it.next());
+//				   String bundleLocation = designate.getBundleLocation();
+//				   Bundle bundle = null;
+//				   if (bundleLocation.startsWith(LOCATION_PREFIX)) {
+//					   bundle = m_session.getTargetDeploymentPackage().getBundle(bundleLocation.substring(LOCATION_PREFIX.length()));
+//				   }
+//				   if (bundle == null) {
+//					   throw new ResourceProcessorException(ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION);
+//				   }
+//				   DesignateObject designateObject = designate.getObject();
+//				   String ocdRef = designateObject.getOcdRef();
+//				   ObjectClassDefinition ocd = null;
+//				   OCD internalOcd = (OCD) ocds.get(ocdRef);
+//				   if (internalOcd != null) {
+//					   // use supplied ocd
+//					   ocd = new LocalizedObjectClassDefinition(bundle, internalOcd, null);
+//				   }
+//				   else {
+//					   // obtain ocd from metatypeservice
+//					   MetaTypeInformation mti = m_metaService.getMetaTypeInformation(bundle);
+//					   ocd = mti.getObjectClassDefinition(ocdRef, null);
+//				   }
+//				   
+//				   AttributeDefinition[] ads = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
+//				   List attributes = designateObject.getAttributes();
+//				   Iterator it2 = attributes.iterator();
+//				   while (it2.hasNext()) {
+//					   Attribute attribute = (Attribute) it2.next();
+//					   String adRef = attribute.getAdRef();
+//					   for(int i = 0; i < ads.length; i++) {
+//						   AttributeDefinition ad = ads[i];
+//						   if (adRef.equals(ad.getID())) {
+//							   
+//							   //
+//						   }
+//					   }
+//				   }
+//			   }
+			   System.out.println(data.getDesignates());
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (XmlPullParserException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+	}
+}



Mime
View raw message