| /* |
| * 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(); |
| } |
| } |