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

org.ogema.impl.persistence.ResourceDBImpl Maven / Gradle / Ivy

/**
 * Copyright 2011-2018 Fraunhofer-Gesellschaft zur Förderung der angewandten Wissenschaften e.V.
 *
 * Licensed 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.ogema.impl.persistence;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

import org.ogema.core.model.ModelModifiers;
import org.ogema.core.model.Resource;
import org.ogema.core.model.units.ColourResource;
import org.ogema.core.resourcemanager.InvalidResourceTypeException;
import org.ogema.core.resourcemanager.ResourceAlreadyExistsException;
import org.ogema.core.resourcemanager.ResourceNotFoundException;
import org.ogema.persistence.DBConstants;
import org.ogema.persistence.PersistencePolicy;
import org.ogema.persistence.ResourceDB;
import org.ogema.persistence.PersistencePolicy.ChangeInfo;
import org.ogema.resourcetree.TreeElement;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;

/**
 * This class implements the interface ResourceDB supporting OGEMA Resource Management with persistent data storage. The
 * data base consists of two files resourcesDB and resourceTypesDB which are persistently stored. At the boot time the
 * stored data is read into tables which are provided for read and write operations through the OGEMA framework.
 * 
 */
public class ResourceDBImpl implements ResourceDB, BundleActivator {

	private static final int INITIAL_MAP_SIZE = 64;

	private ServiceRegistration registration;

	final Logger logger = org.slf4j.LoggerFactory.getLogger("persistence");

	/**
	 * Map of all top level resources as Proxy instances with resource name as key.
	 */
	final ConcurrentHashMap root;
	private final Collection topElementsUnmodifiable;

	/**
	 * These counter help determining unique IDs for resource types resources and sub resources. At the boot time the
	 * stored ids are observed and the counter are initialized with maximum detected id plus 1. nexttypeID starts at
	 * 1024 so the range 0-1024 could be used for standard types constantly.
	 */
	int nextresourceID = 1;

	/**
	 * Like the resource types the data of the resources are organized in a random access file.
	 */
	DBResourceIO resourceIO;

	/**
	 * Monitor objects for synchronized access to the types and resources tables.
	 */
	final Object tablesLock;

	// FIXME make these final?
	ConcurrentHashMap> typeClassByName;
	ConcurrentHashMap resIDByName; // actually by path
	ConcurrentHashMap resNodeByID;
	ConcurrentHashMap> resIDsByType; // TODO replace Vector?

	final boolean activatePersistence;

	private boolean dbReady;

	PersistencePolicy persistence;

	String name;

	private Object storageLock;

	private boolean inited;

	/**
	 * Get the archive instances for the types and resources. If the files already exist then they are opened as
	 * RandomAccessFile otherwise a new file is created.
	 */
	public ResourceDBImpl() {
		root = new ConcurrentHashMap<>();
		topElementsUnmodifiable = Collections. unmodifiableCollection(root.values());
		// Allocate enough memory for all entries in the archive and some memory
		// as reserves.
		typeClassByName = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);

		// Allocate enough memory for all entries in the archive and some memory
		// as reserves.
		resIDByName = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);
		resNodeByID = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);
		resIDsByType = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);

		tablesLock = new Object();

		// check if the property to activate persistence is set
		String persActive = System.getProperty(DBConstants.PROP_NAME_PERSISTENCE_ACTIVE,
				DBConstants.PROP_VALUE_PERSISTENCE_INACTIVE);
		if (persActive.equals(DBConstants.PROP_VALUE_PERSISTENCE_ACTIVE))
			activatePersistence = true;
		else
			activatePersistence = false;
		init();
	}

	synchronized void init() {
		if (inited)
			return;
		if (activatePersistence) {
			persistence = new TimedPersistence(this);

			resourceIO = new DBResourceIO(this);
			resourceIO.initFiles();

			this.storageLock = persistence.getStorageLock();
			resourceIO.parseResources();
			persistence.startStorage();

			/*
			 * Remove ResourceLists, their types is not yet specified
			 */
			// for (TreeElementImpl te : resourceIO.resourceLists) {
			// if (te.type == ResourceList.class) {
			// removeTree(te, true);
			// if (activatePersistence)
			// persistence.store(te.resID, ChangeInfo.DELETED);
			// }
			// }
		}
		else
			this.storageLock = new Object();
		dbReady = true;
		this.inited = true;
	}

	int getNextresourceID() {
		return nextresourceID++;
	}

	/**
	 * The method argument type is an interface describing a data model. It could be a basic Resource (a Resource from
	 * this type is a Tree consisting of one leaf) or a complex one (in this case the Tree could have any structure with
	 * branches and leafs). The branches and leafs are on the way as detected that the return types of the interface
	 * methods are checked. This is done for all branches recursively until a basic type (an interface implementing
	 * SimpleResopurce is found.
	 */
	@Override
	public Class addOrUpdateResourceType(Class type)
			throws InvalidResourceTypeException {
		// check if its a valid type
		if (!isValidType(type))
			throw new InvalidResourceTypeException("Type definition couldn't be verified as valid: " + type.getName());
		// check if the type already registered
		String typeName = type.getName();
		Class regType = typeClassByName.get(typeName);
		if (regType == null) {
			typeClassByName.put(typeName, type);

		}
		return type.asSubclass(Resource.class);
	}

	/**
	 * Initialize a (sub)tree for the given resource type. Some of the node information are set by the caller and some
	 * of them are determined in the context of this method and set.
	 * 
	 * @param type
	 * @param node
	 * @throws InvalidResourceTypeException
	 */
	void createTree(TreeElementImpl node) throws InvalidResourceTypeException {
		Class type = node.type;
		if (type == null) {
			if (node.getPath() == null)
				throw new IllegalArgumentException("Node type and name were null.");
			else
				throw new IllegalArgumentException("Node type of node " + node.getPath() + " is null.");
		}

		Class[] ifaces = type.getInterfaces();
		boolean simple = isSimple(type);
		// For a simple type interfaces consist of {Resource, SimpleResource}
		// For a complex type interfaces consist of {Resource} or
		// {anyComplexResource}

		// save the type class
		int typekey = node.typeKey = getTypeKeyFromClass(type);

		// If neither SimpleResource nor Resource nor another complex resource
		// is inherited by the data model
		// its an invalid one.
		if (typekey == DBConstants.TYPE_KEY_INVALID)
			throw new InvalidResourceTypeException(type.getName());

		/*
		 * If the type is the ResourceList set the complexArray flag
		 */
		if (typekey == DBConstants.TYPE_KEY_COMPLEX_ARR)
			node.complexArray = true;

		// For simple resources and resources that hold values, add a data container
		if (simple || (type == DBConstants.CLASS_COMPLEX_ARR_TYPE)) {
			if (node.simpleValue == null)
				initSimpleNode(node);
		}

		// Create entries for defined sub-resources (recursively)
		Class superModel = type;
		while ((superModel != DBConstants.CLASS_BASIC_TYPE) && (superModel != DBConstants.CLASS_SIMPLE_TYPE)
				&& (superModel != DBConstants.CLASS_VALUE_RESOURCE)) {
			// Create the nodes for the direct children of the type
			parseComplex(superModel, node);
			// iterate over all of the non-optional direct children and
			// create a
			// sub tree each child and hook it on the parent tree.
			Set> tlrs = node.getOrCreateRequireds(false).entrySet();
			for (Map.Entry entry : tlrs) {

				TreeElementImpl res = entry.getValue();
				createTree(res);
			}
			ifaces = superModel.getInterfaces();
			superModel = ifaces[0];
		}
		node.flagsChildren = getChildFlags(node.type);
		node.typeChildren = getChildTypes(node.type);
	}

	/*
	 * Called during the creation of the resource tree to initialize SimpleResourceData for Leaf nodes.
	 */
	private void initSimpleNode(TreeElementImpl node) {
		node.initDataContainer();
	}

	/*
	 * kypeKey is a field in treeElementImpl which says if the represented resource is a complex one or a simple one and
	 * which simple type it is. This method can not recognize non Resource class.
	 */
	int getTypeKeyFromClass(Class cls) {
		// Is the type itself a basic or complex one
		// Basic types implement SimpleResource
		if (isSimple(cls)) {
			// FloatValues can be different resource types (TemperatureResource, ...). Persistence only needs to store
			// the basis type FloatResource.
			if (DBConstants.CLASS_FLOAT_TYPE.isAssignableFrom(cls)) {
				return DBConstants.TYPE_KEY_FLOAT;
			}
			else if (cls == DBConstants.CLASS_BOOL_ARR_TYPE) {
				return DBConstants.TYPE_KEY_BOOLEAN_ARR;
			}
			else if (cls == DBConstants.CLASS_BOOL_TYPE) {
				return DBConstants.TYPE_KEY_BOOLEAN;
			}
			else if (cls == DBConstants.CLASS_FLOAT_ARR_TYPE) {
				return DBConstants.TYPE_KEY_FLOAT_ARR;
			}
			else if (cls == DBConstants.CLASS_INT_ARR_TYPE) {
				return DBConstants.TYPE_KEY_INT_ARR;
			}
			else if (cls == DBConstants.CLASS_INT_TYPE) {
				return DBConstants.TYPE_KEY_INT;
			}
			else if (cls == DBConstants.CLASS_STRING_ARR_TYPE) {
				return DBConstants.TYPE_KEY_STRING_ARR;
			}
			else if (cls == DBConstants.CLASS_STRING_TYPE) {
				return DBConstants.TYPE_KEY_STRING;
			}
			else if (cls == DBConstants.CLASS_TIME_ARR_TYPE) {
				return DBConstants.TYPE_KEY_LONG_ARR;
			}
			else if (cls == DBConstants.CLASS_TIME_TYPE) {
				return DBConstants.TYPE_KEY_LONG;
			}
			else if (cls == DBConstants.CLASS_OPAQUE_TYPE) {
				return DBConstants.TYPE_KEY_OPAQUE;
			}
			else if (cls == DBConstants.CLASS_BYTE_ARR_TYPE) {
				return DBConstants.TYPE_KEY_OPAQUE;
			}
			else if (cls == ColourResource.class) {
				return DBConstants.TYPE_KEY_FLOAT_ARR;
			}
			else
				return DBConstants.NONSPECIFIC_VALUE;
		}
		else {
			/*
			 * The type is not simple. It must be any complex resource or the ResourceList.
			 */
			if (cls == DBConstants.CLASS_COMPLEX_ARR_TYPE)
				return DBConstants.TYPE_KEY_COMPLEX_ARR;
			else
				return DBConstants.TYPE_KEY_COMPLEX;
		}
	}

	/*
	 * Parse all direct children of a type each in an instance of TreeElementImpl as optionals of this TreeElement.
	 */
	private void parseComplex(final Class type, TreeElementImpl node) {
		typeClassByName.put(node.typeName, node.type);

		node.typeChildren = getChildTypes(type);
		node.flagsChildren = getChildFlags(type);
	}

	/*
	 * Put all class definitions of the direct children of the type in a Vector instance.
	 */
	Collection> getTypeChildren0(Class type) {
		Class clazz;
		Method[] methods = type.getDeclaredMethods();
		int len = methods.length;
		Collection> result = new ArrayList<>(len);

		for (Method m : methods) {

			/*
			 * Type elements are detected as return type of a method which is derived from Resource
			 */
			clazz = m.getReturnType();
			if (isValidType(clazz)) {
				result.add(clazz.asSubclass(Resource.class));
			}
			else {
				// method doesn't represent a type element but its a
				// regular interface method. Such methods are ignored.
				if (Configuration.LOGGING)
					logger.debug("Invalid sub resource type ignored " + clazz.getName());
			}
		}
		return result;
	}

	/**
	 * Check if the type is a simple one or its a complex one extending Resource or its a complex one extending another
	 * complex type.
	 * 
	 * @param type
	 * @return
	 */
	boolean isValidType(Class type) {
		return Resource.class.isAssignableFrom(type);
	}

	/**
	 * Check if a type is a simple one. A simple type is a type extending the marker interface SimpleResource.
	 * 
	 * @param type
	 * @return
	 */
	@SuppressWarnings("deprecation")
	boolean isSimple(Class type) {
		return (org.ogema.core.model.SimpleResource.class.isAssignableFrom(type));
	}

	/**
	 * At first we have to check the compatibility of the data base with the new type definition. if resources of the
	 * type exist and the new model demands to add non-optional elements and if resources exist that are from the type
	 * to be updated or demands to remove elements that are present in an existing resource we have to reject the type
	 * registration.
	 */

	@Override
	public boolean hasResourceType(String name) {
		Class res = typeClassByName.get(name);
		return (res != null);
	}

	Class getResourceType(String name) {
		return typeClassByName.get(name);
	}

	@Override
	public List> getAllResourceTypesInstalled() {
		// List> rval = new ArrayList<>(typeClassByName.size());
		// for (Class clazz : typeClassByName.values()) {
		// rval.add(clazz.asSubclass(Resource.class));
		// }
		Collection> col = typeClassByName.values();
		@SuppressWarnings({ "unchecked", "rawtypes" })
		List> result = new ArrayList(col);
		return Collections.unmodifiableList(result);
	}

	@Override
	public List> getResourceTypesInstalled(Class cls) {
		List> rval = new ArrayList<>(typeClassByName.size());
		for (Class clazz : typeClassByName.values()) {
			if (cls == null || cls.isAssignableFrom(clazz))
				rval.add(clazz.asSubclass(Resource.class));
		}
		return rval;
	}

	/**
	 * Setup a tree for a top level resource and register it and its sub resources in the dynamic tables. Generate an ID
	 * for the resource and all of the nodes in the tree.
	 * 
	 * @throws InvalidResourceTypeException
	 * @throws ResourceAlreadyExistsException
	 * @throws TypeNotPresentException
	 * 
	 */
	@Override
	public TreeElement addResource(String name, Class type, String appID)
			throws ResourceAlreadyExistsException, InvalidResourceTypeException {
		// check if a top level resource exists with the demanded name
		TreeElement tmp;
		tmp = getToplevelResource(name);
		if (tmp != null)
			throw new ResourceAlreadyExistsException("top level resource already exists: " + name);

		// check if the Type is already registered
		// ResourceList doesn't need to be registered, instead of its
		// type the annotated Type of the elements of the array is registered.
		if (!typeClassByName.containsValue(type) && (type != DBConstants.CLASS_COMPLEX_ARR_TYPE)) {
			addOrUpdateResourceType(type);
		}
		// init a node object for the top level resource
		TreeElementImpl e = new TreeElementImpl(this);
		/*
		 * If the new resource is of type ResourceList than the type info stay null until it gets a child which sets the
		 * type of all children added later.
		 */
		if (type != DBConstants.CLASS_COMPLEX_ARR_TYPE) {
			e.type = type;
			e.typeName = type.getName();
		}
		else {
			e.complexArray = true;
			e.typeKey = DBConstants.TYPE_KEY_COMPLEX_ARR;
		}
		e.appID = appID;
		e.name = name;
		e.path = name;
		e.parent = null;
		e.parentID = DBConstants.INVALID_ID;
		e.resRef = null;
		e.topLevelParent = e;
		e.toplevel = true;
		e.active = false;
		e.nonpersistent = false;
		e.decorator = false;

		// get a resourceID for this node
		int id = getNextresourceID();
		e.resID = id;

		// setup the tree for this type only if itsn't a ComplexArrayResourse
		if (type != DBConstants.CLASS_COMPLEX_ARR_TYPE)
			createTree(e);
		// put the generated tree in the table of the top level resources.
		root.put(e.name, e);
		registerRes(e);

		// inform persistence policy about the change
		// The first creation of a ResourceList is not relevant for persistence. ResourceLists's are persisted only if
		// the list type is set.
		if (activatePersistence && !e.complexArray)
			persistence.store(id, ChangeInfo.NEW_RESOURCE);

		return e;
	}

	/*
	 * ResourceDBImpl registers all resources in a set of tables for different access strategies. This method registers
	 * each resource created via addResource or addChild in these tables.
	 */
	synchronized void registerRes(TreeElementImpl e) {
		// register in table of id's by name as key
		resIDByName.put(e.path, e.resID);

		/*
		 * If e is a node of type ResourceList the type info is not yet known.
		 */
		Class type;
		if (e.complexArray)
			type = DBConstants.CLASS_COMPLEX_ARR_TYPE;
		else
			type = e.type;
		if (type != null) {
			String name = type.getName();
			// register in table of id's by type as key
			Vector v = resIDsByType.get(name);
			if (v == null) {
				v = new Vector();
				resIDsByType.put(name, v);
			}

			if (!v.contains(e.resID))
				v.add(e.resID);
		}

		// register in table of nodes by id as type
		resNodeByID.put(e.resID, e);
	}

	/*
	 * The resources registered via registerResource have to be unregistered in case of deleteResource via calling of
	 * this method.
	 */
	void unRegisterRes(TreeElementImpl e) {
		// commit deletion of the resource to the storage policy.
		if (activatePersistence)
			persistence.store(e.resID, ChangeInfo.DELETED);

		// unregister in table of id's by name as key
		boolean exist = resIDByName.remove(e.path, e.resID);
		if (!exist)
			logger.error("Registration table resIDByName is corrupted!");
		/*
		 * If e is a node of type ResourceList the type info is not yet known.
		 */
		Class type;
		if (e.complexArray)
			type = DBConstants.CLASS_COMPLEX_ARR_TYPE;
		else
			type = e.type;

		if (type != null) {
			// register in table of id's by type as key
			Vector v = resIDsByType.get(type.getName());
			if (v == null) {
				logger.error("Registration table resIDsByType is corrupted!");
			}
			else {
				exist = v.remove(new Integer(e.resID));
				if (!exist)
					logger.error("Registration table resIDByName is corrupted!");
			}
		}
		synchronized (storageLock) {
			// register in table of nodes by id as type
			exist = resNodeByID.remove(e.resID, e);
		}
		if (!exist)
			logger.error("Registration table resNodeByID is corrupted!");
	}

	@Override
	public Collection> getTypeChildren(String name) {
		Class cls = typeClassByName.get(name);
		if (cls == null)
			return null;

		Vector v = resIDsByType.get(name);
		if (v == null || v.size() == 0) {
			return getTypeChildren0(cls);
		}
		else {
			int id = v.iterator().next();
			TreeElementImpl e = resNodeByID.get(id);
			return Collections.unmodifiableCollection(e.typeChildren.values());
		}
	}

	@Override
	public void deleteResource(TreeElement elem) {
		TreeElementImpl node = (TreeElementImpl) elem;
		// check if the resource exists
		if (!hasResource0(node))
			throw new ResourceNotFoundException(elem.toString());
		// check if the top level resource is known and remove it from the root
		// table
		if (node.toplevel) {
			root.remove(node.name);
		}

		/*
		 * If the node to be deleted is a top level one or a child of a ResourceList, the node and all of its sub
		 * resources are garbage. In other cases the nodes are moved from requireds to the optionals except in case that
		 * the sub resource is a decorator.
		 */
		boolean delete = node.toplevel || node.complexArray;
		removeTree(node, delete);
	}

	boolean hasResource0(TreeElementImpl elem) {
		if (elem == null)
			return false;
		TreeElementImpl e = root.get(elem.name);
		if (e != null && e.equals(elem))
			return true;
		else if ((e != null) && Configuration.LOGGING)
			logger.debug("A top level Resource exists with the same name: " + elem.name);
		e = resNodeByID.get(elem.resID);
		if (e != null && e.equals(elem))
			return true;
		else if ((e != null) && Configuration.LOGGING)
			logger.debug("A sub level Resource exists with the same name: " + elem.name);
		return false;
	}

	void removeTree(TreeElementImpl node, boolean delete) {

		/*
		 * If a part of a top level resource is deleted recycle the nodes and keeps them in the optionals of the
		 * parents. If isDecorating just remove it from requireds of the parent and drop it.
		 */
		TreeElementImpl parent = node.parent;
		if (parent != null)
			parent.removeRequired(node.name);

		unRegisterRes(node);

		if (!node.getOrCreateRequireds(false).isEmpty()) {
			// iterate over all of the children (optionals and requireds) and
			// delete their nodes.
			Set> tlrs = node.getOrCreateRequireds(false).entrySet();
			for (Map.Entry entry : tlrs) {
				TreeElementImpl res = entry.getValue();
				removeTree(res, delete);
			}
		}

		if (!delete && !node.decorator && parent != null) {
			node.reset();
		}
	}

	@Override
	public boolean hasResource(String name) {
		// 1. check if there is a top level resource with the given name
		if (root.containsKey(name))
			return true;
		// 2. check if there is a sub resource with the given name
		// Integer id = resIDByName.get(name);
		// if (id != null)
		// return true;
		return false;
	}

	@Override
	public TreeElement getToplevelResource(String name) {
		// Check if an unloadable custom resource (UCR) is pending
		TreeElementImpl e;
		if (activatePersistence) {
			e = resourceIO.handleUCR(name, null);
			if (e != null)
				return e;
		}
		return root.get(name);
	}

	@Override
	public Collection getAllToplevelResources() {
		return topElementsUnmodifiable;	
//		Vector result = new Vector(root.values());
//		return (Collection) result.clone();
	}

	@Override
	public Collection getFilteredNodes(Map dict) {
		HashSet result = new HashSet();
		String type = dict.get("type");
		String path = dict.get("path");
		String owner = dict.get("owner");
		String residStr = dict.get("id");

		boolean isRoot = "#".equals(residStr);
		if (residStr != null && !isRoot) {

			int residInt = Integer.valueOf(residStr);
			TreeElementImpl element = resNodeByID.get(residInt);
			if (element != null)
				return element.getChildren();
			else
				return result;
		}

		// In case of root node id but not a top level path
		if (isRoot && path != null) {
			int firstslash = path.indexOf('/');
			if (firstslash == 0 && path.length() > 1) {
				path = path.substring(1);
				firstslash = path.indexOf('/');
			}
			int lastslash = path.lastIndexOf('/');
			if (firstslash != -1 && firstslash != lastslash)
				path = path.substring(0, firstslash);
		}

		// 1. filter by path
		if (path != null) {
			// normalize path info
			int end, lastindex = path.length(), begin = 0;
			end = lastindex;
			boolean wc = false;
			if (path.equals("*") || path.equals("/*") || path.equals("/")) {
				path = "";
				wc = true;
			}
			if (path.endsWith("/*")) {
				end -= 2;
				wc = true;
			}
			else if (path.endsWith("*")) {
				end -= 1;
				wc = true;
			}
			if (path.startsWith("/"))
				begin = 1;
			if (end != lastindex || begin != 0)
				path = path.substring(begin, end);

			// Handle the case if the path is '*' or '/*' only
			if (path.equals("/") || (path.equals("") && wc)) {
				Collection tops = resNodeByID.values();
				result.addAll(tops);
				// if a type specified additionally filter the results
				if (type != null)
					filterByType(tops, type, result);
				if (owner != null)
					filterByOwner(result, owner);
				return result;
			}

			// path has an unique value
			Integer id = resIDByName.get(path);
			TreeElementImpl te = null;
			if (id != null) {
				te = resNodeByID.get(id);
				if (!te.reference)
					result.add(te);
				if (owner != null) {
					if (!te.appID.equals(owner)) {
						result.remove(te);
						return result;
					}
				}
				if (type != null) {
					if (!te.typeName.equals(type)) {
						result.remove(te);
					}
				}
				return result;
			}
		}
		else

		// 2. filter by type (path is null)
		if (type != null) {
			Vector ids = resIDsByType.get(type);
			if (ids != null)
				for (int id : ids) {
					TreeElementImpl e = resNodeByID.get(id);
					if (owner != null) {
						if (e.appID.equals(owner))
							if (!e.reference) {
								if (!e.isToplevel())
                                    result.add(e);
									// ??? result.add(e.topLevelParent);
								else
									result.add(e);
							}
					}
					else if (!e.reference) {
						if (!e.isToplevel())
                            result.add(e);
							// ??? result.add(e.topLevelParent);
						else
							result.add(e);
					}
				}
			return result;
		}
		else
		// 3. filter by owner (path and type is null)
		if (owner != null) {
			Set> tops = root.entrySet();
			for (Entry entry : tops) {
				TreeElementImpl te = entry.getValue();
				if (te.appID.equals(owner))
					if (!te.reference)
						result.add(te);
			}
		}
		return result;
	}

	private void filterByOwner(HashSet result, String owner) {
		Iterator it = result.iterator();
		while (it.hasNext()) {
			TreeElement te = it.next();
			if (!te.getAppID().equals(owner))
				result.remove(te);
		}
	}

	private void filterByType(Collection tops, String type, Collection result) {
		Class filtercls = null;
		try {
			filtercls = Class.forName(type);
		} catch (ClassNotFoundException e) {
			return;
		}
		Iterator it = tops.iterator();
		while (it.hasNext()) {
			TreeElementImpl te = it.next();
			Class cls = te.getType();
			if (!filtercls.isAssignableFrom(cls))
				result.remove(te);
		}
	}

	@Override
	public void finishTransaction() {
		if (activatePersistence)
			persistence.finishTransaction(0);
	}

	@Override
	public void startTransaction() {
		if (activatePersistence)
			persistence.startTransaction(0);
	}

	@Override
	public boolean isDBReady() {
		if (!activatePersistence)
			return true;
		else
			return dbReady;
	}

	@Override
	public synchronized void start(BundleContext context) throws Exception {
		try {
			init();
		} catch (Exception e) {
			e.printStackTrace();
		}
		registration = context.registerService(ResourceDB.class, this, null);
	}

	@Override
	public synchronized void stop(BundleContext context) throws Exception {
		final ServiceRegistration registration = this.registration;
		this.registration = null;
		final CountDownLatch latch;
		if (registration != null) {
			latch = new CountDownLatch(1);
			// this will trigger ApplicationTracker#stop, and hence enter apps' custom code
			// we must not hold the synchronized lock here -> leads to deadlock; hence we start a new thread
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					registration.unregister();
					latch.countDown();
				}
			}).start();
		}
		else
			latch = null;
		if (latch != null) {
			for (int i=0; i < 30; i++) {
				if (latch.getCount() <= 0)
					break;
				try {
					// wait for apps to shutdown; if we did not wait here, apps would not be able to 
					// execute their stop method any more, because persistence would be deactivated already
					// furthermore, it is important to release the synchronized lock, so that the other synchronized methods
					// in this class remain accessible; hence we cannot simply wait on latch, but need to call this.wait
					// If this times out (30 times, e.g. because some app simply does not return from its stop method), 
					// then we'll get a lot of ugly log output from apps' stop methods; but still a restart works fine
					// TODO implement a blacklisting for apps that do not return timely from their stop method?
					this.wait(1000);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}
		if (activatePersistence) {
			if (persistence != null) {
				persistence.triggerStorage();
				persistence.stopStorage();
			}
			if (resourceIO != null)
				resourceIO.closeAll();
		}

		if (persistence != null) {
			for (int i = 0; i < 30; i++) {
				if (!(((TimedPersistence) persistence).running))
					break;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
			if (((TimedPersistence) persistence).running) {
				logger.error("Could not shut down timed persistence");
			}
		}
		persistence = null;
		resourceIO = null;
		storageLock = null;
		root.clear();
		typeClassByName.clear();
		resIDByName.clear();
		resNodeByID.clear();
		resIDsByType.clear();
		// resTable.clear();
	}

	/*
	 * Used by the tests only
	 */
	synchronized void stopStorage() {
		logger.debug(((TimedPersistence) persistence).storageTask.toString());
		persistence.stopStorage();
		while (((TimedPersistence) persistence).running) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}
	}

	/*
	 * Used by the tests only
	 */
	public synchronized void restart() {
		if (activatePersistence)
			stopStorage();
		logger.debug("Restart DB!");
		if (resourceIO != null)
			resourceIO.closeAll();
		root.clear();
		this.inited = false;
		typeClassByName = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);
		resIDByName = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);
		resNodeByID = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);
		resIDsByType = new ConcurrentHashMap<>(INITIAL_MAP_SIZE);
		init();
	}

	/*
	 * Used by the tests only
	 */
	public void removeFiles() {
		File dir = new File(resourceIO.dbPathName);
		if (!dir.exists())
			return;
		if (resourceIO != null)
			resourceIO.closeAll();
		String files[] = dir.list();
		for (String file : files) {
			new File(dir, file).delete();
		}
	}

	protected void setName(String name) {
		this.name = name;
	}

	@Override
	public TreeElement getByID(int id) {
		return resNodeByID.get(id);
	}

	@Override
	public Map> getModelDeclaredChildren(String name) {
		Class type = null;
		HashMap> result = new HashMap<>();
		try {
			type = Class.forName(name);
		} catch (ClassNotFoundException e) {
			return result;
		}
		Class clazz;
		String typeName;
		Method[] methods = type.getDeclaredMethods();

		for (Method m : methods) {

			/*
			 * Type elements are detected as return type of a method which is derived from Resource
			 */
			clazz = m.getReturnType();
			typeName = m.getName();
			if (isValidType(clazz)) {
				result.put(typeName, clazz.asSubclass(Resource.class));
			}
			else {
				// method doesn't represent a type element but its a
				// regular interface method. Such methods are ignored.
				if (Configuration.LOGGING)
					System.err.println("Invalid sub resource type ignored " + clazz.getName());
			}
		}
		return result;
	}

	@Override
	public TreeElement getFilteredNodesByPath(String path, boolean isRoot) {

		// In case of root node id but not a top level path
		if (isRoot && path != null) {
			int firstslash = path.indexOf('/');
			if (firstslash == 0 && path.length() > 1) {
				path = path.substring(1);
				firstslash = path.indexOf('/');
			}
			int lastslash = path.lastIndexOf('/');
			if (firstslash != -1 && firstslash != lastslash)
				path = path.substring(0, firstslash);
		}

		TreeElementImpl te = null;
		// 1. filter by path
		if (path != null) {
			// normalize path info
			@SuppressWarnings("unused")
			int end, lastindex = path.length(), begin = 0;
			end = lastindex;

			// path has an unique value
			Integer id = resIDByName.get(path);
			if (id != null) {
				te = resNodeByID.get(id);
				if (!te.reference)
					return te;
			}
		}
		return te;
	}

	static final Class[] emptyParams = {};

	Class getListType(TreeElementImpl parent, final String chName) {
		Class clazz;
		final Class type = parent.type;
		Method m = AccessController.doPrivileged(new PrivilegedAction() {
			public Method run() {
				try {
					return type.getDeclaredMethod(chName, emptyParams);
				} catch (NoSuchMethodException e) {
					return null;
				}
			}
		});

		/*
		 * Type elements are detected as return type of a method which is derived from Resource
		 */
		if (m == null || (clazz = m.getReturnType()) != DBConstants.CLASS_COMPLEX_ARR_TYPE) {
			return null;
		}
		else {
			Type genericType = m.getGenericReturnType();
			if (genericType instanceof ParameterizedType) {
				Type[] actualTypes = ((ParameterizedType) genericType).getActualTypeArguments();
				if (actualTypes.length > 0) {
					clazz = (Class) actualTypes[0];
				}
			}
			return clazz;
		}
	}

	Map, Map>> childTypes = new ConcurrentHashMap<>();
	Map, Map> childFlags = new ConcurrentHashMap<>();

	protected Map> getChildTypes(Class baseType) {
		Map> rval = childTypes.get(baseType);
		if (rval == null) {
			initChildMaps(baseType);
			rval = childTypes.get(baseType);
		}
		assert rval != null;
		return rval;
	}

	protected Map getChildFlags(Class baseType) {
		Map rval = childFlags.get(baseType);
		if (rval == null) {
			initChildMaps(baseType);
			rval = childFlags.get(baseType);
		}
		assert rval != null;
		return rval;
	}

	private void initChildMaps(final Class type) {
		Class clazz;
		String name;
		Method[] methods = AccessController.doPrivileged(new PrivilegedAction() {
			public Method[] run() {
				return type.getMethods();
			}
		});

		Map> typesMap = new HashMap<>();
		Map flagsMap = new HashMap<>();

		Annotation an;
		for (Method m : methods) {
			if (m.getDeclaringClass().equals(Resource.class)) {
				continue;
			}
			/*
			 * if (m.isBridge() || m.isSynthetic()) { continue; // skip overridden methods. }
			 */
			/*
			 * Type elements are detected as return type of a method which is derived from Resource
			 */
			clazz = m.getReturnType();
			name = m.getName();
			if (isValidType(clazz)) {
				// save the type class
				int typekey = getTypeKeyFromClass(clazz);
				// If neither SimpleResource nor Resource nor another complex
				// resource
				// is inherited by the data model
				// its an invalid one.
				if (typekey == DBConstants.TYPE_KEY_INVALID)
					throw new InvalidResourceTypeException(type.getName());

				/*
				 * if its a ComplexResourceType set the type of the elements as the type of the node.
				 */
				if (typekey == DBConstants.TYPE_KEY_COMPLEX_ARR) {

					Type genericType = m.getGenericReturnType();
					if (genericType instanceof ParameterizedType) {
						Type[] actualTypes = ((ParameterizedType) genericType).getActualTypeArguments();
						if (actualTypes.length > 0) {
							clazz = (Class) actualTypes[0];
						}
					}
				}
				Class cls = typesMap.get(name);
				if (cls == null)
					typesMap.put(name, clazz);

				Integer flags = flagsMap.get(name);

				an = m.getAnnotation(ModelModifiers.NonPersistent.class);
				if (an != null) {

					if (flags == null)
						flags = DBConstants.RES_NONPERSISTENT;
					else
						flags |= DBConstants.RES_NONPERSISTENT;
				}
				if (flags == null)
					flags = DBConstants.RES_ISCHILD;
				else
					flags |= DBConstants.RES_ISCHILD;
				flagsMap.put(name, flags);
				// register type definition of the child in the table of known
				// model definitions
				typeClassByName.put(clazz.getName(), clazz);
				// register type definition of the child in the table of known
				// model definitions
				typeClassByName.put(clazz.getName(), clazz);
			}
			else {
				// method doesn't represent a type element but its a
				// regular interface method. Such methods are ignored.
				if (Configuration.LOGGING)
					logger.debug("Invalid sub resource type ignored " + clazz.getName());
			}
		}
		childTypes.put(type, typesMap);
		childFlags.put(type, flagsMap);
	}

	@Override
	public void doStorage() {
		if (activatePersistence)
			persistence.triggerStorage();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy