blob: ccd0ad419b71477dd1910ab1410702b264c0f909 [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.prefs.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.apache.felix.prefs.BackingStore;
import org.apache.felix.prefs.PreferencesDescription;
import org.apache.felix.prefs.PreferencesImpl;
import org.osgi.framework.BundleContext;
import org.osgi.service.prefs.BackingStoreException;
/**
* This is an abstract implementation of a backing store
* which uses streams to read/write the preferences and
* stores a complete preferences tree in a single stream.
*/
public abstract class StreamBackingStoreImpl implements BackingStore {
/** The bundle context. */
protected final BundleContext bundleContext;
public StreamBackingStoreImpl(BundleContext context) {
this.bundleContext = context;
}
/**
* This method is invoked to check if the backing store is accessible right now.
* @throws BackingStoreException
*/
protected abstract void checkAccess() throws BackingStoreException;
/**
* Get the output stream to write the preferences.
*/
protected abstract OutputStream getOutputStream(PreferencesDescription desc)
throws IOException;
/**
* @see org.apache.felix.prefs.BackingStore#store(org.apache.felix.prefs.PreferencesImpl)
*/
public void store(PreferencesImpl prefs) throws BackingStoreException {
// do we need to store at all?
if ( !this.hasChanges(prefs) ) {
return;
}
this.checkAccess();
// load existing data
final PreferencesImpl savedData = this.load(prefs.getBackingStoreManager(), prefs.getDescription());
if ( savedData != null ) {
// merge with saved version
final PreferencesImpl n = savedData.getOrCreateNode(prefs.absolutePath());
n.applyChanges(prefs);
prefs = n;
}
final PreferencesImpl root = prefs.getRoot();
try {
final OutputStream os = this.getOutputStream(root.getDescription());
this.write(root, os);
os.close();
} catch (IOException ioe) {
throw new BackingStoreException("Unable to store preferences.", ioe);
}
}
/**
* Has the tree changes?
*/
protected boolean hasChanges(PreferencesImpl prefs) {
if ( prefs.getChangeSet().hasChanges() ) {
return true;
}
final Iterator i = prefs.getChildren().iterator();
while ( i.hasNext() ) {
final PreferencesImpl current = (PreferencesImpl) i.next();
if ( this.hasChanges(current) ) {
return true;
}
}
return false;
}
/**
* @see org.apache.felix.prefs.BackingStore#update(org.apache.felix.prefs.PreferencesImpl)
*/
public void update(PreferencesImpl prefs) throws BackingStoreException {
final PreferencesImpl root = this.load(prefs.getBackingStoreManager(), prefs.getDescription());
if ( root != null ) {
// and now update
if ( root.nodeExists(prefs.absolutePath()) ) {
final PreferencesImpl updated = (PreferencesImpl)root.node(prefs.absolutePath());
prefs.update(updated);
}
}
}
/**
* Write the preferences recursively to the output stream.
* @param prefs
* @param os
* @throws IOException
*/
protected void write(PreferencesImpl prefs, OutputStream os)
throws IOException {
this.writePreferences(prefs, os);
final ObjectOutputStream oos = new ObjectOutputStream(os);
final Collection<PreferencesImpl> children = prefs.getChildren();
oos.writeInt(children.size());
oos.flush();
final Iterator i = children.iterator();
while ( i.hasNext() ) {
final PreferencesImpl child = (PreferencesImpl) i.next();
final byte[] name = child.name().getBytes("utf-8");
oos.writeInt(name.length);
oos.write(name);
oos.flush();
this.write(child, os);
}
}
/**
* Read the preferences recursively from the input stream.
* @param prefs
* @param is
* @throws IOException
*/
protected void read(PreferencesImpl prefs, InputStream is)
throws IOException {
this.readPreferences(prefs, is);
final ObjectInputStream ois = new ObjectInputStream(is);
final int numberOfChilren = ois.readInt();
for(int i=0; i<numberOfChilren; i++) {
int length = ois.readInt();
final byte[] name = new byte[length];
ois.readFully(name);
final PreferencesImpl impl = (PreferencesImpl)prefs.node(new String(name, "utf-8"));
this.read(impl, is);
}
}
/**
* Load this preferences from an input stream.
* Currently the prefs are read from an object input stream and
* the serialization is done by hand.
* The changeSet is neither updated nor cleared in order to provide
* an update/sync functionality. This has to be done at a higher level.
*/
protected void readPreferences(PreferencesImpl prefs, InputStream in) throws IOException {
final ObjectInputStream ois = new ObjectInputStream(in);
final int size = ois.readInt();
for(int i=0; i<size; i++) {
int keyLength = ois.readInt();
int valueLength = ois.readInt();
final byte[] key = new byte[keyLength];
final byte[] value = new byte[valueLength];
ois.readFully(key);
ois.readFully(value);
prefs.getProperties().put(new String(key, "utf-8"), new String(value, "utf-8"));
}
}
/**
* Save this preferences to an output stream.
* Currently the prefs are written through an object output
* stream with handmade serialization of strings.
* The changeSet is neither updated nor cleared in order to provide
* an update/sync functionality. This has to be done at a higher level.
*/
protected void writePreferences(PreferencesImpl prefs, OutputStream out) throws IOException {
final ObjectOutputStream oos = new ObjectOutputStream(out);
final int size = prefs.getProperties().size();
oos.writeInt(size);
final Iterator i = prefs.getProperties().entrySet().iterator();
while ( i.hasNext() ) {
final Map.Entry entry = (Map.Entry)i.next();
final byte[] key = entry.getKey().toString().getBytes("utf-8");
final byte[] value = entry.getValue().toString().getBytes("utf-8");
oos.writeInt(key.length);
oos.writeInt(value.length);
oos.write(key);
oos.write(value);
}
oos.flush();
}
}