/* * software. For permitted uses, licensing options and redistribution, please see * the LicensingInformation file at the root level of the distribution. * * Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved. */ package org.hypergraphdb; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.hypergraphdb.HGQuery.hg; import org.hypergraphdb.indexing.HGIndexer; import org.hypergraphdb.indexing.HGValueIndexer; import org.hypergraphdb.maintenance.ApplyNewIndexer; import org.hypergraphdb.storage.BAtoHandle; import org.hypergraphdb.storage.ByteArrayConverter; import org.hypergraphdb.type.HGAtomType; /** * <p> * The <code>HGIndexManager</code> allows you to create atom indices. Such indices * allow quick lookup of atoms depending on their value and/or target sets. * </p> * <p> * Atom indexing in {@link HyperGraph} relies on the concept of a {@link HGIndexer}. Indexers * are always bound to an atom type and they are responsible for producing an index <em>key</em> * given an atom instance. The index manager interacts with the {@link HGStore} to create * and update indices at the storage level based on an implementation of the {@link HGIndexer} * interface. * </p> * <p> * When an implementation of a {@link HGIndexer} does not produce keys of <code>byte[]</code> * type, it is required to return a non-null {@link ByteArrayConverter} from its * <code>getConverter</code> method. In addition, if a regular byte ordering is not appropriate * for a given key type, the {@link HGIndexer} implementation must return a non-null * <code>Comparator</code> from its <code>getComparator</code> method. * </p> * <p> * To create a new index for a given atom type, call the <code>register(HGIndexer)</code> method. * To later remove it, call the <code>unregister(HGIndexer)</code> method. Registering an * indexer will store the indexer as a HyperGraph atom and it will request a low-level storage * index from the {@link HGStore}. Atom instances of the type for which a new indexer * is being registered will be automatically indexed henceforth. If there are already atoms of that * type in the database, they be indexed the next time the database is opened. You can force this * indexing of existing data to happen right away by calling the <code>runMaintenance</code> of * the {@link HyperGraph} instance. * </p> * <p> * <strong>NOTE</strong>: this class is not thread safe and its methods do not participate in database * transactions. Modification of the database indexing schema are meant to be used during initialization * or other times when there's no other database activity. * </p> * @author Borislav Iordanov */ @SuppressWarnings("unchecked") public class HGIndexManager { private HyperGraph graph; private HashMap<HGIndexer, HGIndex<? extends Object, ? extends Object>> indices = new HashMap<HGIndexer, HGIndex<? extends Object, ? extends Object>>(); private HashMap<HGHandle, List<HGIndexer>> indexers = new HashMap<HGHandle, List<HGIndexer>>(); private String getIndexName(HGIndexer indexer) { return graph.getPersistentHandle(indexer.getType()) + "_" + graph.getPersistentHandle(graph.getHandle(indexer)); } private HGIndexer toAtomIndexer(HGIndexer indexer) { List<HGIndexer> L = indexers.get(indexer.getType()); int i = L.indexOf(indexer); return i >= 0 ? L.get(i) : null; } private <KeyType extends Object, ValueType extends Object> HGIndex<KeyType, ValueType> getOrCreateIndex(HGIndexer indexer) { HGIndex<KeyType, ValueType> result = (HGIndex<KeyType, ValueType>)indices.get(indexer); if (result == null) { String name = getIndexName(indexer); ByteArrayConverter<ValueType> converter = null; if (indexer instanceof HGValueIndexer) converter = (ByteArrayConverter<ValueType>)((HGValueIndexer)indexer).getValueConverter(graph); else converter = (ByteArrayConverter<ValueType>)BAtoHandle.getInstance(graph.getHandleFactory()); result = graph.getStore().getIndex(name, (ByteArrayConverter<KeyType>)indexer.getConverter(graph), converter, indexer.getComparator(graph), true); // if (result == null) // result = graph.getStore().createIndex(name, // (ByteArrayConverter<KeyType>)indexer.getConverter(graph), // converter, // indexer.getComparator(graph)); indices.put(indexer, result); } return result; } private void removeFromSubtypes(HGIndexer indexer) { for (HGHandle currentType : hg.typePlus(indexer.getType()).getSubTypes(graph)) { if (currentType.equals(indexer.getType())) continue; List<HGIndexer> forType = indexers.get(currentType); if (forType != null) { forType.remove(indexer); if (forType.isEmpty()) indexers.remove(currentType); } } } private void deleteIndex(HGIndexer indexer) { indexer = toAtomIndexer(indexer); if (indexer == null) return; indices.remove(indexer); String name = getIndexName(indexer); graph.getStore().removeIndex(name); } public HGIndexManager(HyperGraph graph) { this.graph = graph; } void loadIndexers() { HGHandle indexerBaseType = graph.getTypeSystem().getTypeHandle(HGIndexer.class); // First get call HGIndexer types. List<HGHandle> indexerTypes = hg.findAll(graph, hg.subsumed(indexerBaseType)); for (HGHandle indexerType : indexerTypes) { List<HGHandle> indexerAtoms = hg.findAll(graph, hg.type(indexerType)); for (HGHandle hindexer : indexerAtoms) { HGIndexer indexer = graph.get(hindexer); // While an indexer is defined for a specific type T, we have // to also index all atoms with a subtype of T. for (HGHandle currentType : hg.typePlus(indexer.getType()).getSubTypes(graph)) { // the lookup of the indexer by subtype doesn't work because the HGIndexer.equals // method compare the type handles, so there's no point in associated the indexer // with the sub-types here. List<HGIndexer> forType = indexers.get(currentType); if (forType == null) { forType = new ArrayList<HGIndexer>(); indexers.put(currentType, forType); } forType.add(indexer); } } } } /** * <p> * Cleanup reference to external resources for this indexer. * </p> */ public void close() { // NOP } /** * <p> * Remove an existing index. If there's no index as specified by * the <code>indexer</code> parameter, nothing is done. * </p> * * @param indexer The indexer to be removed. * @return <code>true</code> if the indexer was removed and * <code>false</code> if it didn't exist. */ public boolean unregister(HGIndexer indexer) { removeFromSubtypes(indexer); List<HGIndexer> forType = indexers.get(indexer.getType()); if (forType == null) return false; int i = forType.indexOf(indexer); if (i < 0) return false; HGHandle hIndexer = graph.getHandle(forType.get(i)); // Make sure there's no MaintenanceOperation based on this indexer // currently scheduled. HGHandle maintenanceOp = hg.findOne(graph, hg.and(hg.type(ApplyNewIndexer.class), hg.eq("hindexer", hIndexer))); if (maintenanceOp != null) graph.remove(maintenanceOp); deleteIndex(forType.get(i)); graph.remove(hIndexer); forType.remove(i); if (forType.isEmpty()) indexers.remove(indexer.getType()); return true; } /** * <p> * Remove all indexers for the given type. This is normally called * only when the type is being from the HyperGraph instance. * </p> * * @param typeHandle The handle of the atom type whose indexers are to be * deleted. */ public void unregisterAll(HGHandle typeHandle) { List<HGIndexer> forType = indexers.get(typeHandle); if (forType != null) { for (Iterator<HGIndexer> i = forType.iterator(); i.hasNext(); ) { HGIndexer indexer = i.next(); removeFromSubtypes(indexer); deleteIndex(indexer); graph.remove(graph.getHandle(indexer)); i.remove(); } if (forType.isEmpty()) indexers.remove(typeHandle); } } /** * <p>Return <code>true</code> if the given <code>HGIndexer</code> is registered * with the index manager and <code>false</code> otherwise. * @param indexer The possibly registered <code>HGIndexer</code>. */ public boolean isRegistered(HGIndexer indexer) { List<HGIndexer> forType = indexers.get(indexer.getType()); return forType == null ? false : forType.contains(indexer); } /** * <p> * Possibly create a new index based on the specified <code>IndexDescriptor</code>. * If an index corresponding to the descriptor already exists, the method * does nothing and returns <code>false</code>. Otherwise it creates a new index which * will become active right away if there's no data with the specified <code>HGIndexer</code>'s * type. If there is already some data with the type (or sub-types) being indexed, the index * will become active the next time the database is opened when a potentially long indexing * operation will be triggered. If you want to do the indexing right after creating an index * on existing data, call the <code>HyperGraph.runMaintenance</code> method. * </p> * * @param desc The descriptor of the index to be created. * @return <code>true</code> if a new index was created and <code>false</code> * otherwise. */ public <KeyType, ValueType> HGIndex<KeyType, ValueType> register(HGIndexer indexer) { boolean createNewIndex = false; boolean activate = hg.count(graph, hg.typePlus(indexer.getType())) == 0; for (HGHandle currentType : hg.typePlus(indexer.getType()).getSubTypes(graph)) { List<HGIndexer> forType = indexers.get(currentType); if (forType == null) { forType = new ArrayList<HGIndexer>(); indexers.put(currentType, forType); } if (!forType.contains(indexer)) { if (currentType.equals(indexer.getType())) createNewIndex = true; if (activate) forType.add(indexer); } } if (createNewIndex) { HGHandle hIndexer = graph.add(indexer); HGIndex<KeyType, ValueType> idx = getOrCreateIndex(indexer); if (!activate) graph.add(new ApplyNewIndexer(hIndexer)); return idx; } else { return getIndex(indexer); } } /** * <p> * Register a newly created sub-type for indexing along with a super-type. All indexers * of the parent type will also apply to the sub-type. This binding of indexers to * the sub-types of a type for which they were originally defined is normally done at * startup time. However, when a sub-type is created, of a type that is already somehow * indexed, one needs to explicitly bind the indexers at least until the next startup time. * </p> * * @param superType The handle of the base type. * @param subType The handle of the sub-type. */ void registerSubtype(HGHandle superType, HGHandle subType) { List<HGIndexer> forSuperType = indexers.get(superType); if (forSuperType == null) return; else if (forSuperType.isEmpty()) indexers.remove(forSuperType); List<HGIndexer> forSubType = indexers.get(subType); if (forSubType == null) { forSubType = new ArrayList<HGIndexer>(); indexers.put(subType, forSubType); } for (HGIndexer idx : forSuperType) if (!forSubType.contains(idx)) forSubType.add(idx); } /** * <p> * Retrieve the storage <code>HGIndex</code> associated to the passed-in * <code>HGIndexer</code>. * </p> * @param indexer The <code>HGIndexer</code> whose associated <code>HGIndex</code> * is desired. * @return The method will return <code>null</code> if the passed in <code>HGIndexer</code> * hasn't been registered with the index manager. */ public <KeyType, ValueType> HGIndex<KeyType, ValueType> getIndex(HGIndexer indexer) { HGIndex<KeyType, ValueType> result = (HGIndex<KeyType, ValueType>)indices.get(indexer); if (result == null) { List<HGIndexer> L = indexers.get(indexer.getType()); if (L != null) { int i = L.indexOf(indexer); if (i >= 0) result = getOrCreateIndex(L.get(i)); } } return result; } /** * <p> * Return all registered <code>HGIndexer</code>s for a given HyperGraph type. * </p> * * @param type The <code>HGHandle</code> of the HyperGraph type whose indexers * are desired. * @return The list of indexers. May be <code>null</code> if no indexer is * currently registered for that type. */ public List<HGIndexer> getIndexersForType(HGHandle type) { return this.indexers.get(type); } /** * <p>Return the predefined index from types to atoms. That is, each key * in this index is a type handle and its values are all atoms with that type.</p> */ public HGIndex<HGPersistentHandle, HGPersistentHandle> getIndexByType() { return graph.indexByType; } /** * <p>Return the predefined index from values to atoms. That is, each key * in this index is a value handle and its <em> index values</em> are all atoms * with that value handle.</p> */ public HGIndex<HGPersistentHandle, HGPersistentHandle> getIndexByValue() { return graph.indexByValue; } /** * <p> * Called when an atom is being added to hypergraph to check and possibly * add index entries for the indexed dimensions of the atom's type. * </p> * * @param typeHandle * @param type * @param atomHandle * @param atom */ public void maybeIndex(HGPersistentHandle typeHandle, HGAtomType type, HGPersistentHandle atomHandle, Object atom) { List<HGIndexer> indList = (List)indexers.get(typeHandle); if (indList == null) return; for (HGIndexer indexer : indList) { HGIndex<Object, Object> idx = getOrCreateIndex(indexer); indexer.index(graph, atomHandle, atom, idx); // Object key = indexer.getKey(graph, atom); // Object value = (indexer instanceof HGValueIndexer) ? // ((HGValueIndexer)indexer).getValue(graph, atom) // : atomHandle; // idx.addEntry(key, value); } } /** * <p> * Called when an atom is being added to hypergraph to check and possibly * add index entries for the indexed dimensions of the atom's type. * </p> * * @param typeHandle * @param type * @param atom * @param atomHandle */ public void maybeUnindex(HGPersistentHandle typeHandle, HGAtomType type, HGPersistentHandle atomHandle, Object atom) { List<HGIndexer> indList = (List)indexers.get(typeHandle); if (indList == null) return; for (HGIndexer indexer : indList) { HGIndex<Object, Object> idx = getOrCreateIndex(indexer); indexer.unindex(graph, atomHandle, atom, idx); // Object key = indexer.getKey(graph, atom); // Object value = (indexer instanceof HGValueIndexer) ? ((HGValueIndexer)indexer).getValue(graph, atom) // : atomHandle; // idx.removeEntry(key, value); } } }