Return-Path: X-Original-To: apmail-ace-commits-archive@www.apache.org Delivered-To: apmail-ace-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 90A15DF20 for ; Thu, 5 Jul 2012 12:12:09 +0000 (UTC) Received: (qmail 99640 invoked by uid 500); 5 Jul 2012 12:12:09 -0000 Delivered-To: apmail-ace-commits-archive@ace.apache.org Received: (qmail 99569 invoked by uid 500); 5 Jul 2012 12:12:08 -0000 Mailing-List: contact commits-help@ace.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ace.apache.org Delivered-To: mailing list commits@ace.apache.org Received: (qmail 99501 invoked by uid 99); 5 Jul 2012 12:12:08 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jul 2012 12:12:08 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jul 2012 12:12:01 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 9B65D2388B46 for ; Thu, 5 Jul 2012 12:10:36 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1357570 [21/34] - in /ace/sandbox/marrs: cnf/ cnf/ext/ cnf/lib/ cnf/releaserepo/ cnf/repo/ cnf/repo/.obrcache/ cnf/repo/.obrcache/http%3A%2F%2Fbundles.bndtools.org.s3.amazonaws.com%2Fcom.jcraft.jsch/ cnf/repo/.obrcache/http%3A%2F%2Fbundles... Date: Thu, 05 Jul 2012 12:10:06 -0000 To: commits@ace.apache.org From: marrs@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120705121036.9B65D2388B46@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminImpl.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminImpl.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminImpl.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminImpl.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,633 @@ +/* + * 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.ace.client.repositoryuseradmin.impl; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.apache.ace.client.repositoryuseradmin.RepositoryUserAdmin; +import org.apache.ace.repository.Repository; +import org.apache.ace.repository.ext.CachedRepository; +import org.apache.ace.repository.ext.impl.CachedRepositoryImpl; +import org.apache.ace.repository.ext.impl.FilebasedBackupRepository; +import org.apache.ace.repository.ext.impl.RemoteRepository; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.log.LogService; +import org.osgi.service.prefs.Preferences; +import org.osgi.service.prefs.PreferencesService; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.io.StreamException; +import com.thoughtworks.xstream.io.xml.DomDriver; + +/** + * RepositoryUserAdminImpl can checkout, commit and revert a repository + * containing user data. It uses XStream to read and write the data. + */ +public class RepositoryUserAdminImpl implements RepositoryUserAdmin { + + private static final String REPOSITORY_USER_ADMIN_PREFS = "repositoryUserAdminPrefs"; + private static final String PREFS_LOCAL_FILE_ROOT = "repositoryUserAdmin"; + private static final String PREFS_LOCAL_FILE_LOCATION = "FileLocation"; + private static final String PREFS_LOCAL_FILE_CURRENT = "current"; + private static final String PREFS_LOCAL_FILE_BACKUP = "backup"; + + private volatile BundleContext m_context; + private volatile LogService m_log; + private volatile PreferencesService m_preferences; + + private final Map m_roles = new ConcurrentHashMap(); + private CachedRepository m_repository; + /** + * Lock to be used when making changes to m_repository. + */ + private final Object m_repositoryLock = new Object(); + private Preferences m_repositoryPrefs; + + public void login(User user, URL repositoryLocation, String repositoryCustomer, String repositoryName) throws IOException { + synchronized(m_repositoryLock) { + // Create our own backup repository + RemoteRepository remote = new RemoteRepository(repositoryLocation, repositoryCustomer, repositoryName); + m_repositoryPrefs = getUserPrefs(user, repositoryLocation, repositoryCustomer, repositoryName); + m_repository = getCachedRepositoryFromPreferences(user, remote); + + // Fill the store with any data that might be available locally + try { + read(m_repository.getLocal(true)); + } + catch (IOException ioe) { + // TODO why is this logged as an error when it occurs when there simply is no data? + m_log.log(LogService.LOG_ERROR, "Error retrieving local data.", ioe); + } + } + } + + public void logout(boolean force) throws IOException { + // logout stores the data locally, ready for the next run + synchronized(m_repositoryLock) { + if (!force) { + ensureLoggedin(); + } + try { + writeLocal(); + } + catch (IOException ioe) { + if (!force) { + throw ioe; + } + } + catch (RuntimeException re) { + if (!force) { + throw re; + } + } + m_repository = null; + } + } + + public void checkout() throws IOException { + synchronized(m_repositoryLock) { + ensureLoggedin(); + read(m_repository.checkout(false)); + storeVersion(); + } + } + + public void commit() throws IOException { + synchronized(m_repositoryLock) { + ensureLoggedin(); + // First write to the local store, and then commit it + writeLocal(); + m_repository.commit(); + storeVersion(); + } + } + + /** + * Helper method to write out the contents of the RepositoryUserAdminImpl to + * a repository. This method will create a new thread to do the writing, and + * wait for the thread to be ready. + * @throws java.io.IOException Thrown when either this thread, or the thread that is + * started to do the writing, throws an exception. + */ + private void writeLocal() throws IOException { + PipedInputStream in = new PipedInputStream(); + final PipedOutputStream out = new PipedOutputStream(in); + final Semaphore semaphore = new Semaphore(0); + final Exception[] exceptions = new Exception[1]; + new Thread("RepositoryUserAdmin writer") { + @Override + public void run() { + try { + write(out); + } + catch (IOException e) { + m_log.log(LogService.LOG_ERROR, "Error writing out contents of RepositoryAdminUser", e); + exceptions[0] = e; + } + catch (IllegalArgumentException iae) { + m_log.log(LogService.LOG_ERROR, "Error writing out contents of RepositoryAdminUser", iae); + exceptions[0] = iae; + } + semaphore.release(); + } + }.start(); + m_repository.writeLocal(in); + try { + if (!semaphore.tryAcquire(30, TimeUnit.SECONDS)) { + throw new IOException("Error writing the contents of RepositoryUserAdmin."); + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (exceptions[0] != null) { + if (exceptions[0] instanceof IOException) { + throw (IOException) exceptions[0]; + } + if (exceptions[0] instanceof RuntimeException) { + throw (RuntimeException) exceptions[0]; + } + } + } + + public void revert() throws IOException { + synchronized(m_repositoryLock) { + ensureLoggedin(); + m_repository.revert(); + read(m_repository.getLocal(false)); + } + } + + /** + * Makes sure a user is logged in before 'stuff' can be done. Make sure the + * calling thread is holding the m_repositoryLock. + */ + private void ensureLoggedin() { + if (m_repository == null) { + throw new IllegalStateException("This operation requires a user to be logged in."); + } + } + + /** + * Reads the content of the stream, and updates this service's + * contents accordingly. The caller of this method should hold the + * m_repositoryLock. + */ + @SuppressWarnings("unchecked") + private void read(InputStream input) { + m_roles.clear(); + // We use DomDriver because the standard XPP driver has issues with attributes. + XStream xstream = new XStream(/*new DomDriver()*/); + xstream.registerConverter(ROLEMAPCONVERTER); + xstream.registerConverter(ROLECONVERTER); + xstream.registerConverter(DICTCONVERTER); + xstream.aliasType("roles", Map.class); + try { + Map fromXML = (Map) xstream.fromXML(input); + m_roles.putAll(fromXML); + } + catch (StreamException e) { + // no problem: this means that the remote repository is empty. + } + } + + /** + * Writes the current contents of this service. + * The caller of this method should hold the m_repositoryLock. + * @param out An output stream to write to. It will be closed by the this method. + * @throws java.io.IOException When there is a problem creating the stream, or + * the other end of the stream fails. + */ + private void write(OutputStream out) throws IOException { + XStream xstream = new XStream(new DomDriver()); + xstream.registerConverter(ROLEMAPCONVERTER); + xstream.registerConverter(ROLECONVERTER); + xstream.registerConverter(DICTCONVERTER); + xstream.aliasType("roles", Map.class); + xstream.toXML(m_roles, out); + try { + out.close(); + } + catch (IOException e) { + m_log.log(LogService.LOG_ERROR, "Error closing XStream output stream.", e); + throw e; + } + } + + /** + * Gets the preferences for a user/location/customer/name combination. + */ + private Preferences getUserPrefs(User user, URL location, String customer, String name) { + Preferences userPrefs = m_preferences.getUserPreferences(user.getName()); + Preferences userAdminPrefs = userPrefs.node(REPOSITORY_USER_ADMIN_PREFS); + Preferences repoPref = userAdminPrefs.node(location.getAuthority() + location.getPath()); + Preferences customerPref = repoPref.node(customer); + return customerPref.node(name); + } + + /** + * Creates a cached repository based on preferences. + */ + private CachedRepository getCachedRepositoryFromPreferences(User user, Repository repository) throws IOException { + long mostRecentVersion = m_repositoryPrefs.getLong("version", CachedRepositoryImpl.UNCOMMITTED_VERSION); + File current = getFileFromPreferences(PREFS_LOCAL_FILE_CURRENT); + File backup = getFileFromPreferences(PREFS_LOCAL_FILE_BACKUP); + return new CachedRepositoryImpl(user, repository, new FilebasedBackupRepository(current, backup), mostRecentVersion); + } + + /** + * Writes the current version of the repository we are working on to the preferences. + */ + private void storeVersion() { + m_repositoryPrefs.putLong("version", m_repository.getMostRecentVersion()); + } + + /** + * Gets a named file in preferences. If the file does not yet exist, it will + * be created, and its location noted in the preferences. + */ + private File getFileFromPreferences(String type) throws IOException { + String directory = m_repositoryPrefs.get(PREFS_LOCAL_FILE_LOCATION, ""); + + if ((directory == "") || !m_context.getDataFile(PREFS_LOCAL_FILE_ROOT + "/" + directory).isDirectory()) { + if (!m_context.getDataFile(PREFS_LOCAL_FILE_ROOT + "/" + directory).isDirectory() && (directory != "")) { + m_log.log(LogService.LOG_WARNING, "Directory '" + directory + "' should exist according to the preferences, but it does not."); + } + // The file did not exist, so create a new one. + File directoryFile = null; + File bundleDataDir = m_context.getDataFile(PREFS_LOCAL_FILE_ROOT); + if (!bundleDataDir.isDirectory()) { + if (!bundleDataDir.mkdir()) { + throw new IOException("Error creating the local repository root directory."); + } + } + directoryFile = File.createTempFile("repo", "", bundleDataDir); + + directoryFile.delete(); // No problem if this goes wrong, it just means it wasn't there yet. + if (!directoryFile.mkdir()) { + throw new IOException("Error creating the local repository storage directory."); + } + m_repositoryPrefs.put(PREFS_LOCAL_FILE_LOCATION, directoryFile.getName()); + return new File(directoryFile, type); + } + else { + // Get the given file from that location. + return m_context.getDataFile(PREFS_LOCAL_FILE_ROOT + "/" + directory + "/" + type); + } + } + + /* ****************************** + * The UserAdmin implementation * + * ******************************/ + + public Role createRole(String name, int type) { + if ((type != Role.USER) && (type != Role.GROUP)) { + throw new IllegalArgumentException("Type " + type + " is unknown."); + } + + // event tough we have a ConcurrentHashMap, we still should make the checking for existence + // and actual creation an atomic operation. + synchronized (m_roles) { + if (m_roles.containsKey(name)) { + return null; + } + + RoleImpl result = new RoleImpl(name, type); + m_roles.put(name, result); + return result; + } + } + + public Authorization getAuthorization(User user) { + throw new UnsupportedOperationException("getAuthorization is not supported by RepositoryUserAdmin."); + } + + public Role getRole(String name) { + return m_roles.get(name); + } + + public Role[] getRoles(String filter) throws InvalidSyntaxException { + if (filter == null) { + return m_roles.values().toArray(new Role[m_roles.size()]); + } + + Filter f = m_context.createFilter(filter); + + List result = new ArrayList(); + for (RoleImpl impl : m_roles.values()) { + if (f.match(impl.getProperties())) { + result.add(impl); + } + } + + // The spec requires us to return null when we have no results. + return result.size() > 0 ? result.toArray(new Role[result.size()]) : null; + } + + public User getUser(String key, String value) { + List result = new ArrayList(); + for (Role role : m_roles.values()) { + if ((role.getType() == Role.USER) && value.equals(role.getProperties().get(key))) { + result.add((User) role); + } + } + + return result.size() == 1 ? result.get(0) : null; + } + + public boolean removeRole(String name) { + RoleImpl role = m_roles.remove(name); + if (role == null) { + return false; + } + for (String groupName : role.getMemberships(this)) { + RoleImpl group = m_roles.get(groupName); + if (group != null) { + group.removeMember(role); + } + } + return true; + } + + /* *********************** + * Serialization helpers * + * ***********************/ + + /** + * XStream Converter for a Dictionary, with support for both Strings and + * byte[]'s as values. Resulting format: + *
+     * <keyname1 type = "String">value1</keyname1>
+     * <keyname1 type = "byte[]">value1</keyname1>
+     * 
+ */ + @SuppressWarnings("unchecked") + private static final Converter DICTCONVERTER = new Converter() { + public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) { + Dictionary dict = (Dictionary) object; + Enumeration e = dict.keys(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + Object value = dict.get(key); + writer.startNode(key); + if (value instanceof String) { + writer.addAttribute("type", "String"); + writer.setValue((String) value); + } + else if (value instanceof byte[]) { + writer.addAttribute("type", "byte[]"); + writer.setValue(new String((byte[]) value)); + } + else if (value == null) { + throw new IllegalArgumentException("Encountered a null value in the dictionary for key " + key); + } + else { + throw new IllegalArgumentException("The dictionary contains a non-recognized value " + value.getClass().getName() + " for key " + key); + } + writer.endNode(); + } + } + + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext converter) { + Dictionary result = new Hashtable(); + while (reader.hasMoreChildren()) { + reader.moveDown(); + Object value; + if ((reader.getAttribute("type") == null) || reader.getAttribute("type").equals("String")) { + value = reader.getValue(); + } + else if (reader.getAttribute("type").equals("byte[]")) { + value = reader.getValue().getBytes(); + } + else { + throw new IllegalArgumentException("Encountered an unknown type tag: " + reader.getAttribute("type")); + } + result.put(reader.getNodeName(), value); + reader.moveUp(); + } + return result; + } + + public boolean canConvert(Class clazz) { + return Dictionary.class.isAssignableFrom(clazz); + } + }; + + /** + * XStream convertor for RoleImpl objects. Resulting format: + *
+     * <user name="me">
+     *     <properties>
+     *     ...up to DICTCONVERTER...
+     *     </properties>
+     *     <credentials>
+     *     ...up to DICTCONVERTER...
+     *     </credentials>
+     *     <memberof>group1</memberof>
+     *     <memberof>group2</memberof>
+     * </user>
+     * 
+ * This converter will use the context property 'deserialized' to find + * groups that the currently deserialized entry should be a member of. + */ + @SuppressWarnings("unchecked") + private final Converter ROLECONVERTER = new Converter() { + public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) { + RoleImpl role = (RoleImpl) object; + + if (role.getType() == Role.USER) { + writer.startNode("user"); + } + else { + writer.startNode("group"); + } + writer.addAttribute("name", role.getName()); + + writer.startNode("properties"); + context.convertAnother(role.getProperties()); + writer.endNode(); + + writer.startNode("credentials"); + context.convertAnother(role.getCredentials()); + writer.endNode(); + + for (String s : role.getMemberships(RepositoryUserAdminImpl.this)) { + writer.startNode("memberof"); + writer.setValue(s); + writer.endNode(); + } + + writer.endNode(); + } + + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + int type; + if (reader.getNodeName().equals("user")) { + type = Role.USER; + } + else if (reader.getNodeName().equals("group")) { + type = Role.GROUP; + } + else { + throw new IllegalArgumentException("Encountered an unknown node name: " + reader.getNodeName()); + } + + RoleImpl result = new RoleImpl(reader.getAttribute("name"), type); + + while (reader.hasMoreChildren()) { + reader.moveDown(); + if (reader.getNodeName().equals("properties")) { + copyDict(result.getProperties(), (Dictionary) context.convertAnother(reader, Dictionary.class)); + } + else if (reader.getNodeName().equals("credentials")) { + copyDict(result.getCredentials(), (Dictionary) context.convertAnother(reader, Dictionary.class)); + } + else if (reader.getNodeName().equals("memberof")) { + ((Map) context.get("deserialized")).get(reader.getValue()).addMember(result); + } + reader.moveUp(); + } + + return result; + } + + /** + * Helper method that copies the contents of one dictionary to another. + */ + private void copyDict(Dictionary to, Dictionary from) { + Enumeration e = from.keys(); + while (e.hasMoreElements()) { + String key = e.nextElement(); + to.put(key, from.get(key)); + } + } + + public boolean canConvert(Class clazz) { + return RoleImpl.class.isAssignableFrom(clazz); + } + }; + + /** + * XStream converter for a Map which contains Roles. Resulting format: + *
+     * <roles>
+     *     ...up to ROLECONVERTER...
+     *     ...up to ROLECONVERTER...
+     * </roles>
+     * 
+ * This converter will use the 'deserialized' context property to store the map + * of already deserialized roles, so ROLECONVERTER can use that.
+ * Furthermore, it uses a simple form of cycle detection when serializing. + */ + private final Converter ROLEMAPCONVERTER = new Converter() { + + @SuppressWarnings("unchecked") + public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) { + Map todo = new HashMap(); + todo.putAll(((Map) object)); + + /* + * We only serialize roles that have no dependencies on roles that have not yet been + * serialized. To do so, we check all dependencies of a role, and see whether any of these + * still has to be serialized. If so, we skip that role for now, and try to serialize it + * in a later run. We go over the list a number of times, until it stops shrinking. + */ + int removed = 1; + while (removed != 0) { + // We need to store the elements we have handled separately: we cannot remove them from todo directly. + Set done = new HashSet(); + for (RoleImpl role : todo.values()) { + String[] memberships = role.getMemberships(RepositoryUserAdminImpl.this); + if (!contains(memberships, todo.keySet())) { + context.convertAnother(role); + done.add(role.getName()); + } + } + for (String s : done) { + todo.remove(s); + } + removed = done.size(); + } + if (!todo.isEmpty()) { + // removed has to be 0, so no elements have been removed from todo in the previous run. However, + // if todo now is not empty, we know we have a circular dependency. + throw new IllegalArgumentException("The role tree contains a circular dependency, and cannot be serialized."); + } + } + + /** + * @return false if none of the elements from subset appear in + * set, true otherwise. + */ + private boolean contains(String[] subset, Set set) { + for (String s : subset) { + if (set.contains(s)) { + return true; + } + } + return false; + } + + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + Map result = new HashMap(); + context.put("deserialized", result); + while (reader.hasMoreChildren()) { + reader.moveDown(); + RoleImpl role = (RoleImpl) context.convertAnother(reader, RoleImpl.class); + result.put(role.getName(), role); + reader.moveUp(); + } + return result; + } + + @SuppressWarnings("unchecked") + public boolean canConvert(Class clazz) { + return Map.class.isAssignableFrom(clazz); + } + }; + +} \ No newline at end of file Added: ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RoleImpl.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RoleImpl.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RoleImpl.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/src/org/apache/ace/client/repositoryuseradmin/impl/RoleImpl.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repositoryuseradmin.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; + +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +/** + * RoleImpl works as an implements of Role, User and Group for external purposes, and + * as a value object for use by the {@link org.apache.ace.client.repositoryuseradmin.impl.RepositoryUserAdminImpl}. + */ +public class RoleImpl implements Role, User, Group { + private final String m_name; + private final int m_type; + private final StringOnlyDictionary m_properties = new StringOnlyDictionary(); + private final StringOnlyDictionary m_credentials = new StringOnlyDictionary(); + private final Set m_members = new HashSet(); + + public RoleImpl(String name, int type) { + if (name == null) { + throw new IllegalArgumentException("Name can not be null"); + } + m_name = name; + m_type = type; + } + + public String getName() { + return m_name; + } + + @SuppressWarnings("unchecked") + public Dictionary getProperties() { + return m_properties; + } + + public int getType() { + return m_type; + } + + @SuppressWarnings("unchecked") + public Dictionary getCredentials() { + return m_credentials; + } + + public boolean hasCredential(String key, Object value) { + if (value == null) { + return false; + } + + // Credentials can be both Strings or byte[] s. + Object credential = m_credentials.get(key); + if (credential instanceof String) { + return ((String) credential).equals(value); + } + else if (credential instanceof byte[]) { + return Arrays.equals((byte[]) value, (byte[]) credential); + } + + return false; + } + + public boolean addMember(Role role) { + return m_members.add(role); + } + + public boolean addRequiredMember(Role role) { + throw new UnsupportedOperationException("addRequiredMember is not supported by RepositoryUserAdmin."); + } + + public Role[] getMembers() { + List result = new ArrayList(); + for (Role role : m_members) { + result.add(role); + } + return result.toArray(new Role[result.size()]); + } + + public Role[] getRequiredMembers() { + throw new UnsupportedOperationException("getRequiredMembers is not supported by RepositoryUserAdmin."); + } + + public boolean removeMember(Role role) { + return m_members.remove(role); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof RoleImpl)) { + return false; + } + return m_name.equals(((RoleImpl) other).m_name) && (m_type == ((RoleImpl) other).m_type); + } + + /** + * A specialization of the dictionary that only supports String keys, + * and String or byte[] values. + */ + @SuppressWarnings("unchecked") + private static final class StringOnlyDictionary extends Dictionary { + private final Dictionary m_dict = new Hashtable(); + + @Override + public Enumeration elements() { + return m_dict.elements(); + } + + @Override + public Object get(Object key) { + return m_dict.get(key); + } + + @Override + public boolean isEmpty() { + return m_dict.isEmpty(); + } + + @Override + public Enumeration keys() { + return m_dict.keys(); + } + + @Override + public Object put(Object key, Object value) { + if (!(key instanceof String)) { + throw new IllegalArgumentException("key should be of type String, not " + key.getClass().getName()); + } + if (!(value instanceof String) && !(value instanceof byte[])) { + throw new IllegalArgumentException("value should be of type String or byte[], not " + value.getClass().getName()); + } + return m_dict.put(key, value); + } + + @Override + public Object remove(Object key) { + if (!(key instanceof String)) { + throw new IllegalArgumentException("key should be of type String, not " + key.getClass().getName()); + } + return m_dict.remove(key); + } + + @Override + public int size() { + return m_dict.size(); + } + } + + /** + * Determines the names of the groups that this role is a member of. + */ + String[] getMemberships(RepositoryUserAdminImpl parent) { + // TODO For performance reasons, we could cache this list in the future. + List result = new ArrayList(); + try { + for (Role role : parent.getRoles(null)) { + if (role instanceof Group) { + for (Role member : ((Group) role).getMembers()) { + if (equals(member)) { + result.add(role.getName()); + } + } + } + } + } + catch (InvalidSyntaxException e) { + // will not happen, since we pass in a null filter + } + return result.toArray(new String[result.size()]); + } + +} \ No newline at end of file Added: ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminSerializationTest.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminSerializationTest.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminSerializationTest.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminSerializationTest.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,277 @@ +/* + * 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.ace.client.repositoryuseradmin.impl; + +import static org.apache.ace.test.utils.TestUtils.UNIT; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.apache.ace.range.SortedRangeSet; +import org.apache.ace.repository.ext.CachedRepository; +import org.apache.ace.test.utils.TestUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.log.LogService; +import org.osgi.service.prefs.Preferences; +import org.osgi.service.prefs.PreferencesService; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class RepositoryUserAdminSerializationTest { + + private RepositoryUserAdminImpl m_impl; + + private MockCachedRepository m_cachedRepository; + + @BeforeMethod(alwaysRun = true) + public void setUp() { + m_impl = new RepositoryUserAdminImpl(); + TestUtils.configureObject(m_impl, BundleContext.class, TestUtils.createMockObjectAdapter(BundleContext.class, new Object() { + @SuppressWarnings("unused") + public Filter createFilter(String s) throws InvalidSyntaxException { + return FrameworkUtil.createFilter(s); + } + })); + + // We configure the CachedRepository ourselves, so there is no need to login + m_cachedRepository = new MockCachedRepository(); + TestUtils.configureObject(m_impl, CachedRepository.class, m_cachedRepository); + TestUtils.configureObject(m_impl, PreferencesService.class); + TestUtils.configureObject(m_impl, LogService.class); + TestUtils.configureObject(m_impl, Preferences.class); // A Preferences is cached for storing the version. + } + + @SuppressWarnings("unchecked") + @Test(groups = { UNIT }) + public void testRepositoryUserAdminSerialization() throws Exception { + // Create some data + User user = (User) m_impl.createRole("me", Role.USER); + user.getProperties().put("fullname", "Mr. M. Me"); + user.getCredentials().put("password", "swordfish"); + user.getCredentials().put("certificate", new byte[] {'4', '2'}); + Group group = (Group) m_impl.createRole("myGroup", Role.GROUP); + group.getProperties().put("description", "One group to rule them all."); + group.addMember(user); + + // Write it to the store + new Thread("RepositoryUserAdmin committer") { + @Override + public void run() { + try { + m_impl.commit(); + } + catch (Exception e) { + System.err.println("Error writing data"); + e.printStackTrace(System.err); + } + } + }.start(); + + // wait for impl to be ready, and retrieve what he has written + Object[] request = m_cachedRepository.getRequest(true); + assert request[0].equals("writeLocal"); + InputStream input = (InputStream) request[1]; + request = m_cachedRepository.getRequest(true); + assert request[0].equals("commit"); + + String data1 = getInputStreamAsString(input); + + // alter the contents + m_impl.createRole("otherme", Role.USER); + m_impl.removeRole("myGroup"); + + m_cachedRepository.addResponse(new ByteArrayInputStream(data1.getBytes())); + + final Semaphore sem = new Semaphore(0); + + // make impl read what it has just written + new Thread("RepositoryUserAdmin committer") { + @Override + public void run() { + try { + m_impl.checkout(); + } + catch (Exception e) { + System.err.println("Error reading data"); + e.printStackTrace(System.err); + } + sem.release(); + } + }.start(); + + // wait for the reading to be done + sem.tryAcquire(5, TimeUnit.SECONDS); + + request = m_cachedRepository.getRequest(true); + assert request[0].equals("checkout"); + + // inspect the current contents of impl + Role[] roles = m_impl.getRoles(null); + assert roles.length == 2 : "Found " + roles.length + " roles in stead of 2."; + for (Role role : roles) { + if (role.equals(user)) { + assert user.hasCredential("password", "swordfish"); + assert user.hasCredential("certificate", new byte[] {'4', '2'}); + } + else if (role.equals(group)) { + assert ((Group) role).getMembers().length == 1 : "We expect one member in the group in stream of " + ((Group) role).getMembers().length; + assert ((Group) role).getMembers()[0].equals(user); + } + else { + assert false : "Found an unknown role: " + role.toString() + " (" + role.getName() + ")"; + } + } + } + + @Test(groups = { UNIT }) + public void testCircularDependency() throws Exception { + Group g1 = (Group) m_impl.createRole("group1", Role.GROUP); + Group g2 = (Group) m_impl.createRole("group2", Role.GROUP); + g1.addMember(g2); + g2.addMember(g1); + + try { + m_impl.commit(); + assert false : "There is a circular dependency, this should be detected and reason for failure."; + } + catch (IllegalArgumentException iae) { + // expected + } + } + + /** + * A mock cached repository, used for checking calls and staging responses to impl. + */ + private static class MockCachedRepository extends MockResponder implements CachedRepository { + + public InputStream checkout(boolean fail) throws IOException { + handleRequest("checkout", fail); + return (InputStream) handleResponse(); + } + + public boolean commit() throws IOException { + handleRequest("commit"); + return false; + } + + public InputStream getLocal(boolean fail) throws IOException { + return null; + } + + public long getMostRecentVersion() { + return 0; + } + + public boolean isCurrent() throws IOException { + return false; + } + + public boolean revert() throws IOException { + return false; + } + + public void writeLocal(InputStream data) throws IOException { + handleRequest("writeLocal", data); + } + + public InputStream checkout(long version) throws IOException, IllegalArgumentException { + return null; + } + + public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException { + return false; + } + + public SortedRangeSet getRange() throws IOException { + return null; + } + + } + + /** + * Base class responder, used for inspecting calls and staging responses. + */ + private static class MockResponder { + protected BlockingQueue m_responses = new LinkedBlockingQueue(); + protected BlockingQueue m_requests = new LinkedBlockingQueue(); + + public void addResponse(Object response) { + m_responses.add(response); + } + + public Object[] getRequest(boolean wait) { + if (wait) { + Object[] result = null; + try { + result = m_requests.poll(5, TimeUnit.SECONDS); + } + catch (Exception e) { + System.err.println("Interrupted while waiting for blocked queue."); + Thread.currentThread().interrupt(); + } + if (result == null) { + assert false : "Even after 5 seconds, no request was ready for us."; + } + return result; + } + else { + return m_requests.poll(); + } + } + + public boolean moreRequests() { + return !m_requests.isEmpty(); + } + + protected void handleRequest(Object... objs) { + m_requests.add(objs); + } + + protected Object handleResponse() { + return m_responses.poll(); + } + } + + /** + * Helper method that gets the contents of a stream into a single string. + */ + private static String getInputStreamAsString(InputStream in) throws IOException { + char[] buf = new char[1]; + StringBuilder found = new StringBuilder(); + InputStreamReader bf = new InputStreamReader(in); + while (bf.read(buf) > 0) { + found.append(buf); + } + bf.close(); + return found.toString(); + } + +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminTest.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminTest.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminTest.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.useradmin/test/org/apache/ace/client/repositoryuseradmin/impl/RepositoryUserAdminTest.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,154 @@ +/* + * 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.ace.client.repositoryuseradmin.impl; + +import static org.apache.ace.test.utils.TestUtils.UNIT; + +import org.apache.ace.test.utils.TestUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class RepositoryUserAdminTest { + + private RepositoryUserAdminImpl m_impl; + + @BeforeMethod(alwaysRun = true) + public void setUp() { + m_impl = new RepositoryUserAdminImpl(); + TestUtils.configureObject(m_impl, BundleContext.class, TestUtils.createMockObjectAdapter(BundleContext.class, new Object() { + @SuppressWarnings("unused") + public Filter createFilter(String s) throws InvalidSyntaxException { + return FrameworkUtil.createFilter(s); + } + })); + } + + /** + * Tests basic creation and membership of groups. + */ + @Test(groups = { UNIT }) + public void testCreation() { + User user = (User) m_impl.createRole("me", Role.USER); + Group group = (Group) m_impl.createRole("myGroup", Role.GROUP); + group.addMember(user); + assert group.getMembers().length == 1 : "We expect to find one member, not " + group.getMembers().length; + } + + @SuppressWarnings("unchecked") + @Test(groups = { UNIT }) + public void testUserProperties() { + User user = (User) m_impl.createRole("me", Role.USER); + user.getProperties().put("fullname", "Mr. M. Me"); + assert m_impl.getUser("fullname", "Mr. M. Me").equals(user); + + Group group = (Group) m_impl.createRole("theGroup", Role.GROUP); + assert m_impl.getUser("fullname", "Mr. M. Me").equals(user); // We should not find the group we just created + + m_impl.removeRole("me"); + assert m_impl.getUser("fullname", "Mr. M. Me") == null; // We should not find the group we just created + } + + @Test(groups = { UNIT }) + public void testGetRoles() throws InvalidSyntaxException { + User user = (User) m_impl.createRole("me", Role.USER); + user.getProperties().put("fullname", "Mr. M. Me"); + Group group = (Group) m_impl.createRole("myGroup", Role.GROUP); + Role[] roles = m_impl.getRoles(null); + assert roles.length == 2; + roles = m_impl.getRoles("(fullname=Mr. M. Me)"); + assert roles.length == 1; + roles = m_impl.getRoles("(fullname=Mr. U. Me)"); + assert roles == null; // Spec requires us to return null in stead of an empty array + } + + @Test(groups = { UNIT }) + public void testCreateDoubleRole() throws InvalidSyntaxException { + User user = (User) m_impl.createRole("test", Role.USER); + Group group = (Group) m_impl.createRole("test", Role.GROUP); + assert group == null; + assert m_impl.getRole("test").equals(user); + assert m_impl.getRoles(null).length == 1; + } + + @Test(groups = { UNIT }) + public void testCredentials() throws InvalidSyntaxException { + User user = (User) m_impl.createRole("me", Role.USER); + user.getCredentials().put("password", "swordfish"); + assert user.hasCredential("password", "swordfish"); + assert !user.hasCredential("pet", "swordfish"); + assert !user.hasCredential("password", "barracuda"); + } + + @Test(groups = { UNIT }) + public void testStringOnlyDictionary() throws InvalidSyntaxException { + User user = (User) m_impl.createRole("me", Role.USER); + try { + user.getProperties().put("clearanceLevel", new Integer(5)); + assert false : "Only String or byte[] values should be allowed."; + } + catch (IllegalArgumentException iae) { + // expected + } + + try { + user.getProperties().put("clearanceLevel", '5'); + assert false : "Only String or byte[] values should be allowed."; + } + catch (IllegalArgumentException iae) { + // expected + } + + try { + user.getProperties().put("clearanceLevel", "5"); + } + catch (IllegalArgumentException iae) { + assert false : "String values should be allowed."; + } + + try { + user.getProperties().put("clearanceLevel", new byte[] {'5'}); + } + catch (IllegalArgumentException iae) { + assert false : "byte[] values should be allowed."; + } + + try { + user.getProperties().put(new String[] {"clearanceLevel"}, "5"); + assert false : "String[] keys should not be allowed."; + } + catch (IllegalArgumentException iae) { + // expected + } + + try { + user.getProperties().put(new byte[] {'c','l','e','a','r','a','n','c','e','L','e','v','e','l'}, "5"); + assert false : "byte[] keys should not be allowed."; + } + catch (IllegalArgumentException iae) { + // expected + } + } +} Added: ace/sandbox/marrs/org.apache.ace.client.rest/.classpath URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/.classpath?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/.classpath (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/.classpath Thu Jul 5 12:09:30 2012 @@ -0,0 +1,9 @@ + + + + + + + + + Added: ace/sandbox/marrs/org.apache.ace.client.rest/.project URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/.project?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/.project (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/.project Thu Jul 5 12:09:30 2012 @@ -0,0 +1,23 @@ + + + org.apache.ace.client.rest + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + Added: ace/sandbox/marrs/org.apache.ace.client.rest/bnd.bnd URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/bnd.bnd?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/bnd.bnd (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/bnd.bnd Thu Jul 5 12:09:30 2012 @@ -0,0 +1,19 @@ +-buildpath: osgi.core,\ + osgi.cmpn,\ + org.apache.felix.dependencymanager,\ + org.apache.ace.connectionfactory;version=latest,\ + org.apache.ace.authentication.api;version=latest,\ + org.apache.ace.client.repository.api;version=latest,\ + org.apache.ace.repository.api;version=latest,\ + org.apache.ace.range.api;version=latest,\ + org.apache.ace.util;version=latest,\ + javax.servlet,\ + ../cnf/lib/gson-1.7.1.jar;version=file,\ + org.mockito.mockito-all + +Private-Package: org.apache.ace.client.rest,\ + com.google.gson,\ + com.google.gson.annotations,\ + com.google.gson.internal,\ + com.google.gson.reflect,\ + com.google.gson.stream \ No newline at end of file Added: ace/sandbox/marrs/org.apache.ace.client.rest/build.xml URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/build.xml?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/build.xml (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/build.xml Thu Jul 5 12:09:30 2012 @@ -0,0 +1,4 @@ + + + + Added: ace/sandbox/marrs/org.apache.ace.client.rest/pom.xml URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/pom.xml?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/pom.xml (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/pom.xml Thu Jul 5 12:09:30 2012 @@ -0,0 +1,111 @@ + + + + + + 4.0.0 + + + org.apache.ace + ace-pom + 0.8.1-SNAPSHOT + ../pom/pom.xml + + + 0.8.1-SNAPSHOT + org.apache.ace.client.rest + bundle + + Apache ACE :: Client :: REST API + + + + scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-client-rest + scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-client-rest + http://svn.apache.org/repos/asf/ace/trunk/ace-client-rest + + + + + * + + + org.apache.ace.client.rest, + com.google.gson, + com.google.gson.* + + org.apache.ace.client.rest.Activator + + + + + org.apache.ace + org.apache.ace.authentication.api + + + org.apache.ace + org.apache.ace.connectionfactory + + + org.apache.ace + org.apache.ace.client.repository.api + + + org.apache.ace + org.apache.ace.repository.api + + + org.apache.ace + org.apache.ace.repository.ext + + + org.apache.ace + org.apache.ace.server.log.store + + + org.apache.ace + org.apache.ace.range.api + + + org.apache.ace + org.apache.ace.util + + + org.apache.ace + org.apache.ace.client.repository.helper.bundle + + + org.osgi + org.osgi.core + + + org.osgi + org.osgi.compendium + + + org.apache.felix + org.apache.felix.dependencymanager + + + com.google.code.gson + gson + 1.7.1 + + + Added: ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Activator.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Activator.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Activator.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Activator.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,55 @@ +/* + * 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.ace.client.rest; + +import javax.servlet.Servlet; + +import org.apache.ace.client.repository.SessionFactory; +import org.apache.felix.dm.DependencyActivatorBase; +import org.apache.felix.dm.DependencyManager; +import org.osgi.framework.BundleContext; +import org.osgi.service.log.LogService; + +public class Activator extends DependencyActivatorBase { + public static final String RESTCLIENT_PID = "org.apache.ace.client.rest"; + + @Override + public void init(BundleContext context, DependencyManager manager) throws Exception { + manager.add(createComponent() + .setInterface(Servlet.class.getName(), null) + .setImplementation(RESTClientServlet.class) + .add(createServiceDependency() + .setService(SessionFactory.class) + .setRequired(true) + ) + .add(createConfigurationDependency() + .setPropagate(true) + .setPid(RESTCLIENT_PID) + ) + .add(createServiceDependency() + .setService(LogService.class) + .setRequired(false) + ) + ); + } + + @Override + public void destroy(BundleContext context, DependencyManager manager) throws Exception { + } +} Added: ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/LogEventSerializer.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/LogEventSerializer.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/LogEventSerializer.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/LogEventSerializer.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.rest; + +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Dictionary; +import java.util.Enumeration; + +import org.apache.ace.log.AuditEvent; +import org.apache.ace.log.LogEvent; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class LogEventSerializer implements JsonSerializer { + + public JsonElement serialize(LogEvent e, Type typeOfSrc, JsonSerializationContext context) { + DateFormat format = SimpleDateFormat.getDateTimeInstance(); + JsonObject event = new JsonObject(); + event.addProperty("logId", e.getLogID()); + event.addProperty("id", e.getID()); + event.addProperty("time", format.format(new Date(e.getTime()))); + event.addProperty("type", toAuditEventType(e.getType())); + JsonObject eventProperties = new JsonObject(); + Dictionary p = e.getProperties(); + Enumeration keyEnumeration = p.keys(); + while (keyEnumeration.hasMoreElements()) { + Object key = keyEnumeration.nextElement(); + eventProperties.addProperty(key.toString(), p.get(key).toString()); + } + event.add("properties", eventProperties); + return event; + } + + private String toAuditEventType(int type) { + switch (type) { + case AuditEvent.BUNDLE_INSTALLED: return "bundle installed"; + case AuditEvent.BUNDLE_RESOLVED: return "bundle resolved"; + case AuditEvent.BUNDLE_STARTED: return "bundle started"; + case AuditEvent.BUNDLE_STOPPED: return "bundle stopped"; + case AuditEvent.BUNDLE_UNRESOLVED: return "bundle unresolved"; + case AuditEvent.BUNDLE_UPDATED: return "bundle updated"; + case AuditEvent.BUNDLE_UNINSTALLED: return "bundle uninstalled"; + case AuditEvent.BUNDLE_STARTING: return "bundle starting"; + case AuditEvent.BUNDLE_STOPPING: return "bundle stopping"; + case AuditEvent.FRAMEWORK_INFO: return "framework info"; + case AuditEvent.FRAMEWORK_WARNING: return "framework warning"; + case AuditEvent.FRAMEWORK_ERROR: return "framework error"; + case AuditEvent.FRAMEWORK_REFRESH: return "framework refresh"; + case AuditEvent.FRAMEWORK_STARTED: return "framework started"; + case AuditEvent.FRAMEWORK_STARTLEVEL: return "framework startlevel"; + case AuditEvent.DEPLOYMENTADMIN_INSTALL: return "deployment admin install"; + case AuditEvent.DEPLOYMENTADMIN_UNINSTALL: return "deployment admin uninstall"; + case AuditEvent.DEPLOYMENTADMIN_COMPLETE: return "deployment admin complete"; + case AuditEvent.DEPLOYMENTCONTROL_INSTALL: return "deployment control install"; + default: return Integer.toString(type); + } + } +}