All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.servicerocket.confluence.randombits.storage.ChainedStorage Maven / Gradle / Ivy

There is a newer version: 2.5.12
Show newest version
/*
 * Copyright (c) 2006, David Peterson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of "randombits.org" nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package com.servicerocket.confluence.randombits.storage;

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * This implementation of Storage allows several other concrete storage
 * instances to be chained together so that if one source does not contain the
 * specified value, the next will be queried and so on.
 *
 * 

* A chained storage instance will be writable if any of the contained * Storage instances is writable. The first writable storage in the list will be * where any saved values are put. *

* * @author David Peterson */ public class ChainedStorage extends AbstractStorage implements Aliasable { private static final Accessor BOOLEAN_ACCESSOR = new Accessor() { public Boolean get( Storage store, String name ) { return store.getBoolean( name, null ); } public void set( Storage store, String name, Boolean value ) { store.setBoolean( name, value ); } }; private static final Accessor DATE_ACCESSOR = new Accessor() { public Date get( Storage store, String name ) { return store.getDate( name, null ); } public void set( Storage store, String name, Date value ) { store.setDate( name, value ); } }; private static final Accessor DOUBLE_ACCESSOR = new Accessor() { public Double get( Storage store, String name ) { return store.getDouble( name, null ); } public void set( Storage store, String name, Double value ) { store.setDouble( name, value ); } }; private static final Accessor INTEGER_ACCESSOR = new Accessor() { public Integer get( Storage store, String name ) { return store.getInteger( name, null ); } public void set( Storage store, String name, Integer value ) { store.setInteger( name, value ); } }; private static final Accessor LONG_ACCESSOR = new Accessor() { public Long get( Storage store, String name ) { return store.getLong( name, null ); } public void set( Storage store, String name, Long value ) { store.setLong( name, value ); } }; private static final Accessor NUMBER_ACCESSOR = new Accessor() { public Number get( Storage store, String name ) { return store.getNumber( name, null ); } public void set( Storage store, String name, Number value ) { // Do nothing - there is no 'setNumber' method. } }; private static final Accessor> OBJECT_LIST_ACCESSOR = new Accessor>() { public List get( Storage store, String name ) { return store.getObjectList( name, null ); } public void set( Storage store, String name, List value ) { store.setObjectList( name, value ); } }; private static final Accessor STRING_ACCESSOR = new Accessor() { public String get( Storage store, String name ) { return store.getString( name, null ); } public void set( Storage store, String name, String value ) { store.setString( name, value ); } }; private static final Accessor STRING_ARRAY_ACCESSOR = new Accessor() { public String[] get( Storage store, String name ) { return store.getStringArray( name, null ); } public void set( Storage store, String name, String[] value ) { store.setStringArray( name, value ); } }; private static final Accessor OBJECT_ACCESSOR = new Accessor() { public Object get( Storage store, String name ) { return store.getObject( name, null ); } public void set( Storage store, String name, Object value ) { store.setObject( name, value ); } }; private Storage[] stores; private Storage writableStore; private Set writableSet; private boolean forceReadOnly; private interface Accessor { T get( Storage store, String name ); void set( Storage store, String name, T value ); } private static class ObjectAccessor implements Accessor { private final Class type; public ObjectAccessor( Class type ) { this.type = type; } public T get( Storage store, String name ) { return store.getObject( name, null, type ); } public void set( Storage store, String name, T value ) { store.setObject( name, value ); } } /** * Creates a chained storage instance, specifying whether it should be * forced to be read-only. If read-only is set to false, the * storage may still be read-only - it depends if any writable storage * instances are passed in. If it's set to true, any * writable storage instances will be ignored. * * @param forceReadOnly forceReadOnly * @param stores stores */ public ChainedStorage( boolean forceReadOnly, Storage... stores ) { this.forceReadOnly = forceReadOnly; setStores( stores ); } /** * Constructs a new chained storage. Read-only status will be determined by * whether there are any writable storage instances in the provided list. * * @param stores * the list of stores to chain together. */ public ChainedStorage( Storage... stores ) { this( false, stores ); } /** * Constructs a new ChainedStorage object with no backing stores. Subclasses * are expected to call {@link #setStores(Storage[])} in their constructors * to set the stores. */ protected ChainedStorage() { } /** * Sets the underlying storage object list. * * @param stores * The array of storage objects. */ protected void setStores( Storage... stores ) { this.stores = new Storage[stores.length]; System.arraycopy( stores, 0, this.stores, 0, this.stores.length ); // Check to see if any of the stores are not read-only. writableStore = null; writableSet = null; if ( !forceReadOnly ) { for (Storage store : stores) { if (writableStore == null && !store.isReadOnly()) { writableStore = store; writableSet = new HashSet<>(); } } } } @Override public void closeBox() { for (Storage store : stores) { store.closeBox(); } super.closeBox(); } /** * Removes the named box from all chained store. Only returns * true if all chained stores return true. If any * stores throw an exception, it is returned immediately and all subsequent * stores in the chain are not asked to remove the box. * * @param name * The name of the box. * @return true if all chained store remove the box. */ public boolean removeBox( String name ) { checkReadOnly(); boolean allRemoved = true; for (Storage store : stores) if (!store.removeBox(name)) { allRemoved = false; } return allRemoved; } /** * Iterates through the chained stores to retrieve a value of a specific * type. * * @param * The type to return. * @param name * The name of the value to retrieve. * @param def * The default if the value is not available. * @param getter * The getter to use to retrieve the specific type. * @return The value. */ private T get( String name, T def, Accessor getter ) { for ( int i = 0; i < stores.length; i++ ) { Storage store = stores[i]; T value = getter.get( store, name ); if ( value != null || ( store == writableStore && writableSet.contains( writableStore.makePath( name ) ) ) ) return value; } return def; } @Override public Boolean getBoolean( String name, Boolean def ) { return get( name, def, BOOLEAN_ACCESSOR ); } @Override public Date getDate( String name, Date def ) { return get( name, def, DATE_ACCESSOR ); } @Override public Double getDouble( String name, Double def ) { return get( name, def, DOUBLE_ACCESSOR ); } @Override public Integer getInteger( String name, Integer def ) { return get( name, def, INTEGER_ACCESSOR ); } @Override public Long getLong( String name, Long def ) { return get( name, def, LONG_ACCESSOR ); } /** * Returns the named value as a {@link Number}. What the actual type of * Number returned is depends on both the stored value, type of * {@link Storage} and the default value. Assume nothing. * * @param name * The name of the value to return. * @param def * The value to return if no value is stored. * @return The stored value, or the default if nothing is stord with that. */ public Number getNumber( String name, Number def ) { return get( name, def, NUMBER_ACCESSOR ); } @Override public T getObject( String name, T def, Class type ) { return get( name, def, new ObjectAccessor( type ) ); } @Override public List getObjectList( String name, List def ) { return get( name, def, OBJECT_LIST_ACCESSOR ); } @Override public String getString( String name, String def ) { return get( name, def, STRING_ACCESSOR ); } @Override public String[] getStringArray( String name, String[] def ) { return get( name, def, STRING_ARRAY_ACCESSOR ); } @Override public boolean isReadOnly() { return forceReadOnly || writableStore == null; } @Override public Set nameSet() { Set names = new HashSet<>(); for (Storage store : stores) { Set sNames = store.nameSet(); if (sNames != null) { names.addAll(sNames); } } return names; } @Override public void openBox( String name ) { super.openBox( name ); for ( int i = 0; i < stores.length; i++ ) { stores[i].openBox( name ); } } private void set( String name, T value, Accessor accessor ) { checkReadOnly(); accessor.set( writableStore, name, value ); writableSet.add( writableStore.makePath( name ) ); } @Override public void setBoolean( String name, Boolean value ) { set( name, value, BOOLEAN_ACCESSOR ); } @Override public void setDate( String name, Date value ) { set( name, value, DATE_ACCESSOR ); } @Override public void setDouble( String name, Double value ) { set( name, value, DOUBLE_ACCESSOR ); } @Override public void setInteger( String name, Integer value ) { set( name, value, INTEGER_ACCESSOR ); } @Override public void setLong( String name, Long value ) { set( name, value, LONG_ACCESSOR ); } @Override public void setObject( String name, Object value ) { set( name, value, OBJECT_ACCESSOR ); } @Override public void setObjectList( String name, List value ) { set( name, value, OBJECT_LIST_ACCESSOR ); } @Override public void setString( String name, String value ) { set( name, value, STRING_ACCESSOR ); } @Override public void setStringArray( String name, String[] value ) { set( name, value, STRING_ARRAY_ACCESSOR ); } /** * Returns the number of chained stores. * * @return The store count. */ public int getStoreCount() { return ( stores != null ) ? stores.length : 0; } /** * Returns the chained store at the specified index. * * @param index * The index. * @return The store. * @throws IndexOutOfBoundsException * if the index is less than 0 or greater or * equal to the value returned by {@link #getStoreCount()}. * */ public Storage getStore( int index ) { try { if ( stores != null ) return stores[index]; } catch ( ArrayIndexOutOfBoundsException e ) { } throw new IndexOutOfBoundsException( String.valueOf( index ) ); } public void addAlias( String alias, Class aliasedClass ) { if ( stores != null ) { for (Storage store : stores) { if (store instanceof Aliasable) { ((Aliasable) store).addAlias(alias, aliasedClass); } } } } }