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

org.zoodb.jdo.internal.client.session.ClientSessionCache Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2013 Tilmann Zaeschke. All rights reserved.
 * 
 * This file is part of ZooDB.
 * 
 * ZooDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ZooDB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ZooDB.  If not, see .
 * 
 * See the README and COPYING files for further information. 
 */
package org.zoodb.jdo.internal.client.session;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import javax.jdo.JDOFatalDataStoreException;
import javax.jdo.ObjectState;

import org.zoodb.api.ZooInstanceEvent;
import org.zoodb.api.impl.ZooPCImpl;
import org.zoodb.jdo.internal.GenericObject;
import org.zoodb.jdo.internal.Node;
import org.zoodb.jdo.internal.Session;
import org.zoodb.jdo.internal.ZooClassDef;
import org.zoodb.jdo.internal.client.AbstractCache;
import org.zoodb.jdo.internal.util.CloseableIterator;
import org.zoodb.jdo.internal.util.DBLogger;
import org.zoodb.jdo.internal.util.PrimLongMapLI;

public class ClientSessionCache implements AbstractCache {
	
	//Do not use list to indicate properties! Instead of 1 bit it, lists require 20-30 bytes per entry!
	//ArrayList is better than ObjIdentitySet, because the latter does not support Iterator.remove
	//ArrayList may allocate to large of an array! Implement BucketedList instead ! TODO!
	//Also: ArrayList.remove is expensive!! TODO
	//TODO Optimize PrimLongTreeMap further? -> HashMaps don't scale!!! (because of the internal array)
	//private HashMap _objs = new HashMap();
    private final PrimLongMapLI objs = 
    	new PrimLongMapLI();
	
	private final PrimLongMapLI schemata = 
		new PrimLongMapLI();
	//TODO move into node-cache
	private final HashMap, ZooClassDef>> nodeSchemata = 
		new HashMap, ZooClassDef>>();
	
	/**
	 * Set of dirty objects. This has two advantages.
	 * 1) It is much faster if only few objects need to be committed while there are many
	 *    clean objects.
	 * 2) It allows some kind of clustering (EXPERIMENTAL!!!) by sorting objects by OID.
	 * 
	 * dirtyObject may include deleted objects!
	 */
	private final ArrayList dirtyObjects = new ArrayList();
	private final PrimLongMapLI deletedObjects = new PrimLongMapLI();

	private final ArrayList dirtyGenObjects = new ArrayList();
	private final PrimLongMapLI genericObjects = new PrimLongMapLI();
	
	private final Session session;

	private ZooClassDef metaSchema;
	
	public ClientSessionCache(Session session) {
		this.session = session;
		ZooClassDef zpc = ZooClassDef.bootstrapZooPCImpl();
		metaSchema = ZooClassDef.bootstrapZooClassDef();
		metaSchema.associateFields();
		metaSchema.associateJavaTypes();
		metaSchema.initProvidedContext(session, session.getPrimaryNode());
		schemata.put(zpc.getOid(), zpc);
		schemata.put(metaSchema.getOid(), metaSchema);
	}
	
	public Session getSession() {
		return session;
	}


	@Override
	public void rollback() {
		//TODO refresh cleans?  may have changed in DB?
		//Maybe set them all to hollow instead? //TODO

	    //refresh schemata
        //Reloading needs to be in a separate loop. We first need to remove all from the cache
        //before reloading them. Reloading may implicitly load dirty super-classes, which would
        //fail if they are still in the cache and marked as dirty.
        ArrayList schemaToRefresh = new ArrayList();
        ArrayList schemaToRemove = new ArrayList();
        for (ZooClassDef cs: schemata.values()) {
        	if (cs.jdoZooIsDirty()) {
        		if (cs.jdoZooIsNew()) {
        		    schemaToRemove.add(cs);
        		} else {
        			schemaToRefresh.add(cs);
        		}
        	}
        	
        }
        for (ZooClassDef cs: schemaToRemove) {
            schemata.remove(cs.jdoZooGetOid());
            nodeSchemata.get(cs.jdoZooGetNode()).remove(cs.getJavaClass());
        }
        for (ZooClassDef cs: schemaToRefresh) {
            session.getSchemaManager().refreshSchema(cs);
        }
        
	    //TODO Maybe we should simply refresh the whole cache instead of setting them to hollow.
        //This doesn't matter for embedded databases, but for client/server, we could benefit from
        //group-refreshing(loading) all dirty objects 
        for (ZooPCImpl co: dirtyObjects) {
	    	if (co.jdoZooIsDirty()) { // i.e. not refreshed
	    		if (co.jdoZooIsNew()) {
	    			//remove co
	    			objs.remove(co.jdoZooGetOid());
	    		} else {
	    			co.jdoZooMarkHollow();
	    		}
	    	}
        }
        for (ZooPCImpl co: deletedObjects.values()) {
	    	if (co.jdoZooIsDirty()) { // i.e. not refreshed
	    		if (co.jdoZooIsNew()) {
	    			//remove co
	    			objs.remove(co.jdoZooGetOid());
	    		} else {
	    			co.jdoZooMarkHollow();
	    		}
	    	}
        }
	    
		dirtyObjects.clear();
		deletedObjects.clear();
		
        //generic objects
        for (GenericObject go: dirtyGenObjects) {
        	if (go.isNew()) {
        		go.setDeleted(true); //prevent further access to it through existing references
        		genericObjects.remove(go.getOid());
        		continue;
        	}
        	go.setHollow();
        }
        for (GenericObject go: genericObjects.values()) {
        	go.setHollow();
        }
        dirtyGenObjects.clear();
	}


	@Override
	public final void markPersistent(ZooPCImpl pc, long oid, Node node, ZooClassDef clsDef) {
		if (pc.jdoZooIsDeleted()) {
			throw new UnsupportedOperationException("Make it persistent again");
			//TODO implement
		}
		if (pc.jdoZooIsPersistent()) {
			//ignore
			return;
		}
		
		addToCache(pc, clsDef, oid, ObjectState.PERSISTENT_NEW);
	}


	public final void makeTransient(ZooPCImpl pc) {
		//remove it
		if (objs.remove(pc.jdoZooGetOid()) == null) {
			throw new JDOFatalDataStoreException("Object is not in cache.");
		}
		//update
		pc.jdoZooMarkTransient();
	}


	@Override
	public final void addToCache(ZooPCImpl obj, ZooClassDef classDef, long oid, 
			ObjectState state) {
    	obj.jdoZooInit(state, classDef.getProvidedContext(), oid);
		//TODO call newInstance elsewhere
		//obj.jdoReplaceStateManager(co);
		objs.put(obj.jdoZooGetOid(), obj);
	}
	
	
	@Override
	public final ZooPCImpl findCoByOID(long oid) {
		return objs.get(oid);
	}

	/**
	 * TODO Fix this. Schemata should be kept in a separate cache
	 * for each node!
	 * @param cls
	 * @param node
	 * @return Schema object for a given Java class.
	 */
	@Override
	public ZooClassDef getSchema(Class cls, Node node) {
		ZooClassDef ret = nodeSchemata.get(node).get(cls);
		if (ret == null) {
			if (cls == null) {
				return null;
			}
			//Try virtual/generic schemata
			ret = getSchema(cls.getName());
			if (ret != null) {
				//check (associate also checks compatibility)
				ret.associateJavaTypes(true);
				nodeSchemata.get(node).put(cls, ret);
			}
		}
		return ret;
	}

	@Override
	public ZooClassDef getSchema(String clsName) {
		for (ZooClassDef def: schemata.values()) {
			if (def.getNextVersion() == null && def.getClassName().equals(clsName)) {
				return def;
			}
		}
		return null;
	}

	@Override
	public ZooClassDef getSchema(long schemaOid) {
		return schemata.get(schemaOid);
	}

	/**
	 * Clean out the cache after commit.
	 * TODO keep hollow objects? E.g. references to correct, e.t.c!
	 */
	public void postCommit(boolean retainValues) {
		//TODO later: empty cache (?)
		
		for (ZooPCImpl co: deletedObjects.values()) {
			if (co.jdoZooIsDeleted()) {
				objs.remove(co.jdoZooGetOid());
				co.jdoZooGetContext().notifyEvent(co, ZooInstanceEvent.POST_DELETE);
			}
		}
		
		if (retainValues) {
			for (ZooPCImpl co: dirtyObjects) {
				if (!co.jdoZooIsDeleted()) {
					co.jdoZooMarkClean();
				}
			}
		} else {
			if (objs.size() > 100000) {
				DBLogger.debugPrintln(0, "Cache is getting large. Consider retainValues=true"
						+ " to speed up and avoid expensive eviction.");
			}
            for (ZooPCImpl co: objs.values()) {
                if (retainValues || co instanceof ZooClassDef) {
                    co.jdoZooMarkClean();
                } else {
                    co.jdoZooEvict();
                }
                co.jdoZooGetContext().notifyEvent(co, ZooInstanceEvent.POST_STORE);
            }
		}
		dirtyObjects.clear();
		deletedObjects.clear();
		
		//generic objects
        for (GenericObject go: dirtyGenObjects) {
        	if (go.isDeleted()) {
        		genericObjects.remove(go.getOid());
        		continue;
        	}
        	go.setClean();
        }
        for (GenericObject go: genericObjects.values()) {
        	if (!retainValues) {
        		go.setHollow();
        	}
        }
        dirtyGenObjects.clear();

        //schema
		Iterator iterS = schemata.values().iterator();
		for (; iterS.hasNext(); ) {
			ZooClassDef cs = iterS.next();
			if (cs.jdoZooIsDeleted()) {
				iterS.remove();
        		nodeSchemata.get(cs.jdoZooGetNode()).remove(cs.getJavaClass());
				continue;
			}
			//keep in cache???
			cs.jdoZooMarkClean();  //TODO remove if cache is flushed -> retainValues!!!!!
		}
	}

	/**
	 * @return List of all cached schema objects (clean, new, deleted, dirty).
	 */
	public Collection getSchemata() {
		return schemata.values();
	}
	
	public void addSchema(ZooClassDef clsDef, boolean isLoaded, Node node) {
		ObjectState state;
		if (isLoaded) {
			state = ObjectState.PERSISTENT_CLEAN;
		} else {
			state = ObjectState.PERSISTENT_NEW;
		}
		//TODO avoid setting the OID here a second time, seems silly...
    	clsDef.jdoZooInit(state, metaSchema.getProvidedContext(), clsDef.getOid());
		clsDef.initProvidedContext(session, node);
		schemata.put(clsDef.getOid(), clsDef);
		if (clsDef.getNextVersion() == null && clsDef.getJavaClass() != null) {
			nodeSchemata.get(node).put(clsDef.getJavaClass(), clsDef);
		}
		objs.put(clsDef.getOid(), clsDef);
	}
	
	public void updateSchema(ZooClassDef clsDef, Class oldCls, Class newCls) {
		Node node = clsDef.jdoZooGetNode();
		//Removal may return null if class was previously stored a 'null', which is non-unique.
		nodeSchemata.get(node).remove(oldCls);
		if (newCls != null) {
			nodeSchemata.get(node).put(newCls, clsDef);
		}
	}

	public PrimLongMapLI.PrimLongValues getAllObjects() {
		return objs.values();
	}

    public void close() {
        objs.clear();
        schemata.clear();
        nodeSchemata.clear();
    }


    public void evictAll() {
        for (ZooPCImpl co: objs.values()) {
            if (!co.jdoZooIsDirty()) {
                co.jdoZooEvict();
            }
        }
    }

    public void evictAll(boolean subClasses, Class cls) {
        for (ZooPCImpl co: objs.values()) {
            if (!co.jdoZooIsDirty() && (co.jdoZooGetClassDef().getJavaClass() == cls || 
                    (subClasses && cls.isAssignableFrom(co.jdoZooGetClassDef().getJavaClass())))) {
                co.jdoZooEvict();
            }
        }
    }

	public void addNode(Node node) {
		nodeSchemata.put(node, new HashMap, ZooClassDef>());
		nodeSchemata.get(node).put(ZooClassDef.class, metaSchema);
	}

	public CloseableIterator iterator(ZooClassDef def, boolean subClasses, 
			ObjectState state) {
		return new CacheIterator(objs.values().iterator(), def, subClasses, state);
	}
	
	
	private static class CacheIterator implements CloseableIterator {

		private ZooPCImpl next = null;
		private final PrimLongMapLI.ValueIterator iter;
		private final ZooClassDef cls;
		private final boolean subClasses;
		private final ObjectState state;
		
		private CacheIterator(PrimLongMapLI.ValueIterator iter, 
				ZooClassDef cls, boolean subClasses, ObjectState state) {
			this.iter = iter;
			this.cls = cls;
			this.subClasses = subClasses;
			this.state = state;
			//find first object
			next();
		}

		@Override
		public void refresh() {
		    // nothing to do
		}
		
		@Override
		public boolean hasNext() {
			return next != null;
		}

		@Override
		public ZooPCImpl next() {
			ZooPCImpl ret = next;
			ZooPCImpl co = null;
			final boolean subClasses = this.subClasses;
			while (iter.hasNextEntry()) {
				co = iter.nextValue();
				ZooClassDef defCand = co.jdoZooGetClassDef();
				if (defCand == cls || (subClasses && cls.hasSuperClass(cls))) {
					if (co.jdoZooHasState(state)) {
						next = co;
						return ret;
					}
				}
			}
			next = null;
			return ret;
		}

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

		@Override
		public void close() {
			// nothing to do
		}
	}

	/**
	 * This sets the meta schema object for this session. It is the instance of
	 * ZooClassDef that represents its own schema.
	 * @param def
	 */
	public void setRootSchema(ZooClassDef def) {
		//TODO this is a bit funny, but we leave it for now.
		//Ideally, we would not have to reset the metaClass, but the second/loaded version
		//contains additional info such as node/session and is correctly referenced.
		//The original version is only used to initially load any schema from the database.
		metaSchema = def;
	}

	public void notifyDirty(ZooPCImpl pc) {
		dirtyObjects.add(pc);
	}
	
	public ArrayList getDirtyObjects() {
		return dirtyObjects;
	}

	public void notifyDelete(ZooPCImpl pc) {
		deletedObjects.put(pc.jdoZooGetOid(), pc);
	}
	
	public PrimLongMapLI.PrimLongValues getDeletedObjects() {
		return deletedObjects.values();
	}

    public void addGeneric(GenericObject genericObject) {
    	if (genericObject.isDirty()) {
    		dirtyGenObjects.add(genericObject);
    	}
    	genericObjects.put(genericObject.getOid(), genericObject);
    }

    public ArrayList getDirtyGenericObjects() {
        return dirtyGenObjects;
    }

    @Override
    public GenericObject getGeneric(long oid) {
    	return genericObjects.get(oid);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy