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

org.cristalise.kernel.persistency.RemoteMap Maven / Gradle / Ivy

/**
 * This file is part of the CRISTAL-iSE kernel.
 * Copyright (c) 2001-2015 The CRISTAL Consortium. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * http://www.fsf.org/licensing/licenses/lgpl.html
 */
package org.cristalise.kernel.persistency;

import java.util.AbstractSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;

import org.cristalise.kernel.common.ObjectNotFoundException;
import org.cristalise.kernel.common.PersistencyException;
import org.cristalise.kernel.entity.C2KLocalObject;
import org.cristalise.kernel.entity.proxy.ItemProxy;
import org.cristalise.kernel.entity.proxy.MemberSubscription;
import org.cristalise.kernel.entity.proxy.ProxyObserver;
import org.cristalise.kernel.lookup.ItemPath;
import org.cristalise.kernel.process.Gateway;
import org.cristalise.kernel.utils.Logger;


/**
 * Maps a storage cluster onto a java.util.Map
 *
 * @param  the C2KLocalObject stored by this Map
 */
public class RemoteMap extends TreeMap implements C2KLocalObject  {

    private static final long serialVersionUID = -2356840109407419763L;

    private int mID=-1;
    private String mName;
    protected ItemPath mItemPath;
    private String mPath = "";
    Object keyLock = null;
    TransactionManager storage;
    Comparator comp;

    /**
     * for remote client processes to receive updates, disables puts. @check activate()
     */
    ItemProxy source;

    ProxyObserver listener;

    /**
     * if this remote map will participate in a transaction
     */
    Object mLocker;

    public RemoteMap(ItemPath itemPath, String path, Object locker) {
        super(new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                Integer i1 = null, i2 = null;
                try {
                    i1 = Integer.valueOf(o1);
                    i2 = Integer.valueOf(o2);
                    return i1.compareTo(i2);
                }
                catch (NumberFormatException ex) { }

                return o1.compareTo(o2);
            }
        });

        mItemPath = itemPath;
        mLocker = locker;

        // split the path into path/name
        int lastSlash = path.lastIndexOf("/");
        mName = path.substring(lastSlash+1);
        if (lastSlash>0) mPath = path.substring(0,lastSlash);

        // see if the name is also a suitable id
        try {
            mID = Integer.parseInt(mName);
        }
        catch (NumberFormatException e) {}

        storage = Gateway.getStorage();
    }

    public void activate() {
        if (listener != null) {
            Logger.debug(8, "RemoteMap.activate() - ALREADY active name:%s", mItemPath);
            return;
        }

        listener = new ProxyObserver() {
            @Override
            public void add(V obj) {
                synchronized (this) {
                    Logger.msg(7, "RemoteMap:ProxyObserver.add() - id:"+obj.getName());
                    putLocal(obj.getName(), obj);
                }
            }

            @Override
            public void remove(String id) {
                synchronized (this) {
                    Logger.msg(7, "RemoteMap:ProxyObserver.remove() - id:"+id);
                    removeLocal(id);
                }
            }

            @Override
            public void control(String control, String msg) { }
        };


        try {
            source = Gateway.getProxyManager().getProxy(mItemPath);
            source.subscribe(new MemberSubscription(listener, mPath+mName, false));

            Logger.debug(5, "RemoteMap.activate() - name:"+mName+" "+mItemPath);
        }
        catch (Exception ex) {
            Logger.error("Error subscribing to remote map. Changes will NOT be received");
            Logger.error(ex);
        }
    }

    public void deactivate() {
        if (source != null) source.unsubscribe(listener);
    }

    @Override
    public void finalize() {
        deactivate();
        Gateway.getStorage().clearCache(mItemPath, mPath+mName);
    }

    protected void loadKeys() {
        if (keyLock != null) return;
        clear();
        keyLock = new Object();

        synchronized(this) {
            String[] keys;
            try {
                keys = storage.getClusterContents(mItemPath, mPath+mName);
                for (String key : keys) super.put(key, null);
            }
            catch (PersistencyException e) {
                Logger.error(e);
            }
        }
    }

    public synchronized int getLastId() {
        loadKeys();
        if (size() == 0) return -1;
        try {
            return Integer.parseInt(lastKey());
        }
        catch (NumberFormatException ex) {
            return -1;
        }
    }

    public void setID(int id) { mID = id; }

    public int getID() { return mID; }

    @Override
    public void setName(String name) { mName = name; }

    @Override
    public String getName() { return mName; }

    /**
     * Returns null so it cannot be stored
     */
    @Override
    public ClusterType getClusterType() {
        return null;
    }

    @Override
    public String getClusterPath() {
        return null;
    }

    @Override
    public synchronized void clear() {
        synchronized (this) {
            super.clear();
        }
        keyLock = null;
    }

    @Override
    public synchronized boolean containsKey(Object key) {
        if (keyLock == null) loadKeys();
        return super.containsKey(key);
    }

    /**
     * This must retrieve all the values until a match is made.
     * Very expensive, but if you must, you must.
     * @see java.util.Map#containsValue(Object)
     */
    @Override
    public synchronized boolean containsValue(Object value) {
        loadKeys();
        synchronized(this) {
            for (String key: keySet()) {
                if (get(key).equals(value)) return true;
            }
        }
        return false;
    }


    @Override
    @SuppressWarnings("unchecked")
    public synchronized V get(Object objKey) {
        loadKeys();
        String key;

        if (objKey instanceof Integer)     key = ((Integer)objKey).toString();
        else if (objKey instanceof String) key = (String)objKey;
        else                               return null;

        synchronized(this) {
            try {
                V value = super.get(key);
                if (value == null) {
                    value = (V)storage.get(mItemPath, mPath+mName+"/"+key, mLocker);
                    super.put(key, value);
                }
                return value;
            }
            catch (PersistencyException e) {
                Logger.error(e);
            }
            catch (ObjectNotFoundException e) {
                Logger.error(e);
            }
        }
        return null;
    }

    @Override
    public synchronized boolean isEmpty() {
        loadKeys();
        return super.isEmpty();
    }

    /**
     * @see java.util.Map#keySet()
     */
    @Override
    public synchronized Set keySet() {
        loadKeys();
        return super.keySet();
    }

    /**
     * Inserts the given object into the storage
     * the key is ignored - it can be fetched from the value.
     * @see java.util.Map#put(Object, Object)
     */
    @Override
    public synchronized V put(String key, V value) {
        if (source != null) throw new UnsupportedOperationException("Cannot use an activated RemoteMap to write to storage.");
        try {
            synchronized(this) {
                storage.put(mItemPath, value, mLocker);
                return putLocal(key, value);
            }
        }
        catch (PersistencyException e) {
            Logger.error(e);
            return null;
        }
    }

    protected synchronized V putLocal(String key, V value) {
        return super.put(key, value);
    }

    /**
     * @see java.util.Map#remove(Object)
     */
    @Override
    public synchronized V remove(Object key) {
        if (source != null) throw new UnsupportedOperationException("Cannot use an activated RemoteMap to write to storage.");
        loadKeys();
        if (containsKey(key)) try {
            synchronized(keyLock) {
                storage.remove(mItemPath, mPath+mName+"/"+key, mLocker);
                return removeLocal(key);
            }
        }
        catch (PersistencyException e) {
            Logger.error(e);
        }
        return null;
    }

    protected synchronized V removeLocal(Object key) {
        return super.remove(key);
    }

    /**
     * @see java.util.Map#size()
     */
    @Override
    public synchronized int size() {
        loadKeys();
        return super.size();
    }

    /**
     * @see java.util.Map#values()
     */
    @Override
    public synchronized Collection values() {
        return new RemoteSet(this);
    }

    /**
     * Basic implementation of Set and Collection to bridge to the Iterator
     * Disallows all writes.
     */
    private class RemoteSet extends AbstractSet {
        RemoteMap mParent;

        public RemoteSet(RemoteMap parent) {
            mParent = parent;
        }

        // no modifications allowed
        @Override
        public boolean add(E o) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean addAll(Collection c) {
            throw new UnsupportedOperationException();
        }
        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean removeAll(Collection c) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean retainAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Iterator iterator() {
            return new RemoteIterator(mParent);
        }

        @Override
        public int size() {
            return mParent.size();
        }

    }
    /**
     * Iterator view on RemoteMap data. Doesn't preload anything.
     * REVISIT: Will go strange if the RemoteMap is modified. Detect this and throw ConcurrentMod ex
     */
    private class RemoteIterator implements Iterator {
        RemoteMap mParent;
        Iterator iter;
        String currentKey;

        public RemoteIterator(RemoteMap parent) {
            mParent = parent;
            iter = mParent.keySet().iterator();
        }

        @Override
        public boolean hasNext() {
            return iter.hasNext();
        }

        @Override
        public C next() {
            currentKey = iter.next();
            return mParent.get(currentKey);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy