| /** |
| * 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.useradmin.filestore; |
| |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| 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.Map.Entry; |
| import java.util.Set; |
| |
| |
| import org.apache.felix.useradmin.RoleFactory; |
| import org.osgi.service.useradmin.Group; |
| import org.osgi.service.useradmin.Role; |
| import org.osgi.service.useradmin.User; |
| |
| /** |
| * Provides a serializer for a role repository. |
| */ |
| final class RoleRepositorySerializer { |
| |
| private static final int VALUE_TYPE_STRING = 1; |
| private static final int VALUE_TYPE_BARRAY = 2; |
| |
| /** |
| * Deserializes a given input stream. |
| * |
| * @param is the input stream to deserialize, cannot be <code>null</code>. |
| * @return a {@link Map} representing the role repository. It provides a |
| * mapping between the name of the role as key and the associated |
| * role as value. |
| * @throws IOException in case of I/O problems; |
| * @throws IllegalArgumentException in case the given stream was <code>null</code>. |
| */ |
| public Map deserialize(InputStream is) throws IOException { |
| if (is == null) { |
| throw new IllegalArgumentException("InputStream cannot be null!"); |
| } |
| return readRepository(new DataInputStream(is)); |
| } |
| |
| /** |
| * Serializes a given map to the given output stream. |
| * |
| * @param roleRepository the repository to serialize, cannot be <code>null</code>; |
| * @param os the output stream to serialize to, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems; |
| * @throws IllegalArgumentException in case the given parameter was <code>null</code>. |
| */ |
| public void serialize(Map roleRepository, OutputStream os) throws IOException { |
| if (roleRepository == null) { |
| throw new IllegalArgumentException("Map cannot be null!"); |
| } |
| if (os == null) { |
| throw new IllegalArgumentException("OutputStream cannot be null!"); |
| } |
| writeRepository(roleRepository, new DataOutputStream(os)); |
| } |
| |
| /** |
| * Adds all groups, based on the given stub groups. |
| * |
| * @param repository the repository to add the groups to, cannot be <code>null</code>; |
| * @param stubGroups the list with stub groups to replace, cannot be <code>null</code>. |
| * @throws IOException in case a referenced role was not found in the repository. |
| */ |
| private void addGroups(Map repository, List stubGroups) throws IOException { |
| // First create "empty" groups in the repository; we'll fill them in later on... |
| Iterator sgIter = stubGroups.iterator(); |
| while (sgIter.hasNext()) { |
| StubGroupImpl stubGroup = (StubGroupImpl) sgIter.next(); |
| |
| Group group = (Group) RoleFactory.createRole(Role.GROUP, stubGroup.getName()); |
| copyDictionary(stubGroup.getProperties(), group.getProperties()); |
| copyDictionary(stubGroup.getCredentials(), group.getCredentials()); |
| |
| repository.put(group.getName(), group); |
| } |
| |
| int origSize = stubGroups.size(); |
| while (!stubGroups.isEmpty()) { |
| List copy = new ArrayList(stubGroups); |
| |
| int size = copy.size(); |
| for (int i = 0; i < size; i++) { |
| StubGroupImpl stubGroup = (StubGroupImpl) copy.get(i); |
| |
| Group group = (Group) repository.get(stubGroup.getName()); |
| if (group != null) { |
| resolveGroupMembers(stubGroup, group, repository); |
| stubGroups.remove(stubGroup); |
| } |
| } |
| |
| // In case we didn't resolve any groups; we should fail... |
| if (origSize == stubGroups.size()) { |
| throw new IOException("Failed to resolve groups: " + stubGroups); |
| } |
| |
| origSize = stubGroups.size(); |
| } |
| } |
| |
| /** |
| * Converts a given {@link Dictionary} implementation to a {@link Map} implementation. |
| * |
| * @param dictionary the dictionary to convert, cannot be <code>null</code>. |
| * @return a {@link Map} instance with all the same key-value pairs as the given dictionary, never <code>null</code>. |
| */ |
| private Map convertToMap(Dictionary dictionary) { |
| Map result = new HashMap(); |
| if (dictionary instanceof Map) { |
| result.putAll((Map) dictionary); |
| } else { |
| Enumeration keyEnum = dictionary.keys(); |
| while (keyEnum.hasMoreElements()) { |
| Object key = keyEnum.nextElement(); |
| result.put(key, dictionary.get(key)); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Copies the contents of a given dictionary to a given other dictionary. |
| * |
| * @param source the dictionary to copy from; |
| * @param dest the dictionary to copy to. |
| */ |
| private void copyDictionary(Dictionary source, Dictionary dest) { |
| Enumeration keys = source.keys(); |
| while (keys.hasMoreElements()) { |
| Object key = keys.nextElement(); |
| Object value = source.get(key); |
| dest.put(key, value); |
| } |
| } |
| |
| /** |
| * Returns the role with the given name from the given repository. |
| * |
| * @param repository the repository to obtain the roles from, cannot be <code>null</code>; |
| * @param name the name of the role to retrieve, cannot be <code>null</code>. |
| * @return a role matching the given name, or <code>null</code> if no such role exists. |
| */ |
| private Role getRoleFromRepository(Map repository, String name) { |
| Role role; |
| if (Role.USER_ANYONE.equals(name)) { |
| role = RoleFactory.createRole(Role.USER_ANYONE); |
| } else { |
| role = (Role) repository.get(name); |
| } |
| return role; |
| } |
| |
| /** |
| * Reads and fills a given dictionary. |
| * |
| * @param dict the dictionary to read & fill, cannot be <code>null</code>; |
| * @param dis the input stream to read the data from, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private void readDictionary(Dictionary dict, DataInputStream dis) throws IOException { |
| // Read the number of entries... |
| int count = dis.readInt(); |
| while (count-- > 0) { |
| // Read the name of the key... |
| String key = dis.readUTF(); |
| // Read the type of the value... |
| int type = dis.read(); |
| // Read the value & add the actual entry... |
| if (VALUE_TYPE_BARRAY == type) { |
| int length = dis.readInt(); |
| byte[] value = new byte[length]; |
| if (dis.read(value, 0, length) != length) { |
| throw new IOException("Invalid repository; failed to correctly read dictionary!"); |
| } |
| dict.put(key, value); |
| } else if (VALUE_TYPE_STRING == type) { |
| dict.put(key, dis.readUTF()); |
| } |
| } |
| } |
| |
| /** |
| * Reads a (stub) group from the given input stream. |
| * |
| * @param dis the input stream to read the data from, cannot be <code>null</code>. |
| * @return the read (stub) group, never <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private StubGroupImpl readGroup(DataInputStream dis) throws IOException { |
| StubGroupImpl group = new StubGroupImpl(dis.readUTF()); |
| |
| readDictionary(group.getProperties(), dis); |
| readDictionary(group.getCredentials(), dis); |
| |
| // Read the number of basic members... |
| int count = dis.readInt(); |
| while (count-- > 0) { |
| group.addMember(dis.readUTF()); |
| } |
| |
| // Read the number of required members... |
| count = dis.readInt(); |
| while (count-- > 0) { |
| group.addRequiredMember(dis.readUTF()); |
| } |
| |
| return group; |
| } |
| |
| /** |
| * Reads the entire repository from the given input stream. |
| * |
| * @param dis the input stream to read the data from, cannot be <code>null</code>. |
| * @return the repository {@link Map}, never <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private Map readRepository(DataInputStream dis) throws IOException { |
| Map repository = new HashMap(); |
| |
| int entryCount = dis.readInt(); |
| |
| List stubGroups = new ArrayList(); |
| |
| // Keep reading until no more types can be read... |
| while (entryCount-- > 0) { |
| int type = dis.readInt(); |
| |
| Role role = null; |
| if (Role.GROUP == type) { |
| stubGroups.add(readGroup(dis)); |
| } else if (Role.USER == type) { |
| role = readUser(dis); |
| } else { |
| role = readRole(dis); |
| } |
| |
| if (role != null) { |
| repository.put(role.getName(), role); |
| } |
| } |
| |
| // Post processing stage: replace all stub groups with real group implementations... |
| addGroups(repository, stubGroups); |
| |
| return repository; |
| } |
| |
| /** |
| * Reads a role from the given input stream. |
| * |
| * @param dis the input stream to read the data from, cannot be <code>null</code>. |
| * @return the read role, never <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private Role readRole(DataInputStream dis) throws IOException { |
| Role role = RoleFactory.createRole(Role.ROLE, dis.readUTF()); |
| |
| readDictionary(role.getProperties(), dis); |
| |
| return role; |
| } |
| |
| /** |
| * Reads a user from the given input stream. |
| * |
| * @param dis the input stream to read the data from, cannot be <code>null</code>. |
| * @return the read user, never <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private User readUser(DataInputStream dis) throws IOException { |
| User user = (User) RoleFactory.createRole(Role.USER, dis.readUTF()); |
| |
| readDictionary(user.getProperties(), dis); |
| readDictionary(user.getCredentials(), dis); |
| |
| return user; |
| } |
| |
| /** |
| * Resolves all basic and required group members for a given group, based on the names from the given stub group. |
| * |
| * @param stubGroup the stub group to convert, cannot be <code>null</code>; |
| * @param repository the repository to take the roles from, cannot be <code>null</code>. |
| * @return a concrete {@link Group} instance with all members resolved, or <code>null</code> if not all members could be resolved. |
| * @throws IOException in case a referenced role was not found in the repository. |
| */ |
| private void resolveGroupMembers(StubGroupImpl stubGroup, Group group, Map repository) throws IOException { |
| List names = stubGroup.getMemberNames(); |
| int size = names.size(); |
| |
| for (int i = 0; i < size; i++) { |
| String name = (String) names.get(i); |
| Role role = getRoleFromRepository(repository, name); |
| if (role == null) { |
| throw new IOException("Unable to find referenced basic member: " + name); |
| } |
| group.addMember(role); |
| } |
| |
| names = stubGroup.getRequiredMemberNames(); |
| size = names.size(); |
| |
| for (int i = 0; i < size; i++) { |
| String name = (String) names.get(i); |
| Role role = getRoleFromRepository(repository, name); |
| if (role == null) { |
| throw new IOException("Unable to find referenced required member: " + name); |
| } |
| group.addRequiredMember(role); |
| } |
| } |
| |
| /** |
| * Writes a given dictionary to the given output stream. |
| * |
| * @param dict the dictionary to write, cannot be <code>null</code>; |
| * @param dos the output stream to write the data to, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private void writeDictionary(Dictionary dict, DataOutputStream dos) throws IOException { |
| Map properties = convertToMap(dict); |
| |
| Set entries = properties.entrySet(); |
| int size = entries.size(); |
| |
| // Write the number of entries... |
| dos.writeInt(size); |
| |
| Iterator entriesIter = entries.iterator(); |
| while (entriesIter.hasNext()) { |
| Map.Entry entry = (Entry) entriesIter.next(); |
| |
| dos.writeUTF((String) entry.getKey()); |
| |
| Object value = entry.getValue(); |
| if (value instanceof String) { |
| dos.write(VALUE_TYPE_STRING); |
| dos.writeUTF((String) value); |
| } else if (value instanceof byte[]) { |
| dos.write(VALUE_TYPE_BARRAY); |
| dos.writeInt(((byte[]) value).length); |
| dos.write((byte[]) value); |
| } |
| } |
| } |
| |
| /** |
| * Writes a given group to the given output stream. |
| * |
| * @param group the group to write, cannot be <code>null</code>. |
| * @param dos the output stream to write the data to, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private void writeGroup(Group group, DataOutputStream dos) throws IOException { |
| dos.writeUTF(group.getName()); |
| |
| writeDictionary(group.getProperties(), dos); |
| writeDictionary(group.getCredentials(), dos); |
| |
| Role[] m = group.getMembers(); |
| |
| if (m == null) { |
| dos.writeInt(0); |
| } else { |
| // Write the number of basic members... |
| dos.writeInt(m.length); |
| // Write the names of the basic members... |
| for (int i = 0; i < m.length; i++) { |
| dos.writeUTF(m[i].getName()); |
| } |
| } |
| |
| m = group.getRequiredMembers(); |
| |
| if (m == null) { |
| dos.writeInt(0); |
| } else { |
| // Write the number of required members... |
| dos.writeInt(m.length); |
| // Write the names of the required members... |
| for (int i = 0; i < m.length; i++) { |
| dos.writeUTF(m[i].getName()); |
| } |
| } |
| } |
| |
| /** |
| * Writes the given repository to the given output stream. |
| * |
| * @param repository the repository to write, cannot be <code>null</code>; |
| * @param dos the output stream to write the data to, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private void writeRepository(Map repository, DataOutputStream dos) throws IOException { |
| Collection values = repository.values(); |
| Iterator valuesIter = values.iterator(); |
| |
| // Write the total number of entries in our repository first... |
| dos.writeInt(values.size()); |
| |
| while (valuesIter.hasNext()) { |
| Role role = (Role) valuesIter.next(); |
| |
| int type = role.getType(); |
| |
| dos.writeInt(type); |
| |
| if (Role.GROUP == type) { |
| writeGroup((Group) role, dos); |
| } else if (Role.USER == type) { |
| writeUser((User) role, dos); |
| } else { |
| writeRole(role, dos); |
| } |
| } |
| } |
| |
| /** |
| * Writes a given role to the given output stream. |
| * |
| * @param role the role to write, cannot be <code>null</code>. |
| * @param dos the output stream to write the data to, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private void writeRole(Role role, DataOutputStream dos) throws IOException { |
| dos.writeUTF(role.getName()); |
| |
| writeDictionary(role.getProperties(), dos); |
| } |
| |
| /** |
| * Writes a given user to the given output stream. |
| * |
| * @param user the user to write, cannot be <code>null</code>. |
| * @param dos the output stream to write the data to, cannot be <code>null</code>. |
| * @throws IOException in case of I/O problems. |
| */ |
| private void writeUser(User user, DataOutputStream dos) throws IOException { |
| dos.writeUTF(user.getName()); |
| |
| writeDictionary(user.getProperties(), dos); |
| writeDictionary(user.getCredentials(), dos); |
| } |
| } |