blob: e4ecaad8013d2179423a2e4e41495058282ec4c8 [file] [log] [blame]
/**
* 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);
}
}