You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2009/06/27 17:53:26 UTC
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...
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();
+ }
+ }
+}