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

org.nustaq.reallive.server.StorageDriver Maven / Gradle / Ivy

There is a newer version: 5.2.1
Show newest version
package org.nustaq.reallive.server;

import org.nustaq.kontraktor.Callback;
import org.nustaq.kontraktor.IPromise;
import org.nustaq.kontraktor.Promise;
import org.nustaq.kontraktor.util.Log;
import org.nustaq.reallive.api.*;
import org.nustaq.reallive.messages.*;
import org.nustaq.reallive.records.PatchingRecord;
import org.nustaq.reallive.records.RecordWrapper;
import org.nustaq.reallive.api.Record;
import org.nustaq.reallive.server.storage.ClusterTableRecordMapping;

/**
 * Created by moelrue on 03.08.2015.
 *
 * implements transaction processing on top of a physical storage
 */
public class StorageDriver implements ChangeReceiver {

    public static boolean PROPAGATE_EMPTY_DIFFS = true;

    RecordStorage store;
    ChangeReceiver listener = change -> {};

    public StorageDriver(RecordStorage store) {
        this.store = store;
        Log.Info(this,""+store.getStats());
    }

    public static Record unwrap(Record r) {
        if ( r instanceof PatchingRecord ) {
            return unwrap(((PatchingRecord) r).unwrapOrCopy());
        }
        if ( r instanceof RecordWrapper ) {
            return unwrap(r.getRecord());
        }
        return r;
    }

    /**
     * Semantics:
     * We expect the change has NOT YET been applied.
     * In case of updates we expect the diff to be correct. In case the diff is null, we compute it right here.
     * @param change
     */
    @Override
    public void receive(ChangeMessage change) {
        switch (change.getType()) {
            case ChangeMessage.QUERYDONE:
                break;
            case ChangeMessage.PUT:
            {
                Record prevRecord = store.get(change.getKey());
                if ( prevRecord == null ) {
                    if ( change.updateLastModified() )
                        store.put(change.getKey(),unwrap(change.getRecord()));
                    else
                        store._rawPut(change.getKey(),unwrap(change.getRecord()));
                    if ( change.generateChangeBroadcast() )
                        receive( new AddMessage(change.getSenderId(),true,change.getRecord()));
                } else {
                    Record newRecord = change.getRecord();
                    Diff diff = ChangeUtils.computeDiff(prevRecord,newRecord);
                    if ( !diff.isEmpty() || PROPAGATE_EMPTY_DIFFS ) {
                        if ( change.updateLastModified() )
                            store.put(change.getKey(), newRecord);
                        else
                            store._rawPut(change.getKey(), newRecord);
                        if ( change.generateChangeBroadcast() )
                            listener.receive(new UpdateMessage(change.getSenderId(), diff, newRecord));
                    }
                }
                break;
            }
            case ChangeMessage.REMOVE:
            {
                RemoveMessage removeMessage = (RemoveMessage) change;
                String key = removeMessage.getKey();
                Record v = store.remove(key);
                if ( v != null ) {
                    if ( change.generateChangeBroadcast() )
                        listener.receive(new RemoveMessage(change.getSenderId(),unwrap(v)));
                } else {
//                    System.out.println("*********** failed remove "+change.getKey());
//                    store.putRecord(change.getKey(), new MapRecord(change.getKey()).putRecord("url", "POK"));
//                    System.out.println("  reput and get:" + store.get(change.getKey()));
//                    store.remove(change.getKey());
//                    System.out.println("  re-rem and get:" + store.get(change.getKey()));
//                    store.filter( rec -> rec.getKey().equals(change.getKey()), (r,e) -> {
//                        System.out.println("  "+r);
//                    });
                }
                break;
            }
            case ChangeMessage.ADD:
            {
                AddMessage addMessage = (AddMessage) change;
                String key = addMessage.getKey();
                Record prevRecord = store.get(key);
                if ( prevRecord != null && ! addMessage.isUpdateIfExisting() ) {
                    return;
                }
                if ( prevRecord == null ) { // add
                    store.put(change.getKey(), addMessage.getRecord());
                    if ( change.generateChangeBroadcast() )
                        listener.receive(addMessage);
                    return;
                }
                // break; fall through to update
                change = new UpdateMessage(addMessage.getSenderId(),null,addMessage.getRecord());
            }
            case ChangeMessage.UPDATE:
            {
                UpdateMessage updateMessage = (UpdateMessage) change;
                Record previousRec = store.get(updateMessage.getKey());
                if ( previousRec == null && updateMessage.isAddIfNotExists() ) {
                    if ( updateMessage.getNewRecord() == null ) {
                        throw new RuntimeException("updated record does not exist, cannot fall back to 'Add' as UpdateMessage.newRecord is null");
                    }
                    store.put(change.getKey(),updateMessage.getNewRecord());
                    if ( change.generateChangeBroadcast() )
                        listener.receive( new AddMessage(change.getSenderId(), updateMessage.getNewRecord()) );
                } else {
                    Diff diff = updateMessage.getDiff();
                    Record updatedRecord = change.getRecord();
                    if ( diff == null ) {
                        diff = ChangeUtils.computeDiff(previousRec, updatedRecord);
                    }
                    // old values are actually not needed inside the diff
                    // however they are needed in a change notification for filter processing (need to reconstruct prev record)
                    if ( ! diff.isEmpty() || PROPAGATE_EMPTY_DIFFS ) {
                        diff.applyToOldRecord(previousRec,updatedRecord);
                        store.put(change.getKey(), previousRec);
                        if ( change.generateChangeBroadcast() )
                            listener.receive(new UpdateMessage(change.getSenderId(), diff, previousRec ));
                    }
                }
                break;
            }
            default:
                throw new RuntimeException("unknown change type "+change.getType());
        }
    }

    public RecordStorage getStore() {
        return store;
    }

    public ChangeReceiver getListener() {
        return listener;
    }

    public StorageDriver store(final RecordStorage store) {
        this.store = store;
        return this;
    }

    public StorageDriver setListener(final ChangeReceiver listener) {
        this.listener = listener;
        return this;
    }

    public void resizeIfLoadFactorLarger( double loadFactor, long maxGrowBytes ) {
        store.resizeIfLoadFactorLarger(loadFactor, maxGrowBytes);
    }

    public void put(int senderId, String key, Object ... keyVals) {
        receive(RLUtil.get().put(senderId, key,keyVals));
    }

    /**
     * apply the function to the record with given key and return the result inside a promise
     *
     * changes to the record inside the function are applied to the real record and a change message
     * is generated.
     *
     * In case the function returns a changemessage (add,putRecord,remove ..), the change message is applied
     * to the original record and the change is broadcasted
     *
     * @param key
     * @param action
     * @return the result of function.
     */
    public IPromise atomic(int senderId, String key, RLFunction action) {
        Record rec = getStore().get(key);
        if ( rec == null ) {
            final Object apply = action.apply(rec);
            if ( apply instanceof ChangeMessage )
            {
                receive( (ChangeMessage) apply ) ;
            }
            return new Promise(apply);
        } else {
            Record pr = rec.deepCopy();
            final Object res = action.apply(pr);
            if ( res instanceof ChangeMessage )
            {
                ((ChangeMessage) res).senderId(senderId);
                receive( (ChangeMessage) res ) ;
            } else {
                Diff diff = ChangeUtils.computeDiff(rec, pr);
                if ( !diff.isEmpty())
                    receive(new UpdateMessage(senderId,diff,pr));
            }
            return new Promise(res);
        }
    }

    public void atomicQuery(int senderId, RLPredicate filter, RLFunction action) {
        store.forEach(filter, (r,e) -> {
            if ( r != null ) {
                Record pr = r.deepCopy();
                Boolean res = action.apply(pr);
                if (res==Boolean.FALSE) {
                    receive(RLUtil.get().remove(senderId,pr.getKey()));
                } else {
                    Diff diff = ChangeUtils.computeDiff(r, pr);
                    if ( ! diff.isEmpty() )
                        receive(new UpdateMessage(senderId,diff,pr));
                }
            }
        });
    }

    public void add(int senderId, String key, Object... keyVals) {
        receive(RLUtil.get().add(senderId,key, keyVals));
    }

    public void add(int senderId, Record rec) {
        receive(new AddMessage(senderId,rec));
    }

    public void put(int senderId, Record rec) {
        receive( new PutMessage(senderId,rec) );
    }

    public void update(int senderId, String key, Object... keyVals) {
        Record currentRecord = getStore().get(key);
        if ( currentRecord == null ) {
            receive(RLUtil.get().add(senderId,key,keyVals));
        } else {
            Record modifiedRecord = currentRecord.deepCopy();
            for (int i = 0; i < keyVals.length; i+=2) {
                Object k = keyVals[i];
                Object v = keyVals[i+1];
                modifiedRecord.put((String)k,v);
            }
            receive(new UpdateMessage(senderId,null,modifiedRecord));
        }
    }

    public void remove(int senderId, String key) {
        RemoveMessage remove = RLUtil.get().remove(senderId,key);
        receive(remove);
    }

    public void _saveMapping(ClusterTableRecordMapping mapping) {
        store._saveMapping(mapping);
    }

    public ClusterTableRecordMapping _loadMapping() {
        return store._loadMapping();
    }

    public void queryRemoveLog(long from, long to, Callback cb) {
        RemoveLog removeLog = store.getRemoveLog();
        if ( removeLog == null ) {
            cb.finish();
        } else {
            removeLog.query(from,to,cb);
        }
    }

    public void pruneRemoveLog( long maxAge ) {
        RemoveLog removeLog = store.getRemoveLog();
        if ( removeLog != null ) {
            removeLog.prune(maxAge);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy