/* * This file is part of the HyperGraphDB source distribution. This is copyrighted * 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.type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.hypergraphdb.HGException; import org.hypergraphdb.HGHandle; import org.hypergraphdb.HGPersistentHandle; import org.hypergraphdb.HGSearchResult; import org.hypergraphdb.HGValueLink; import org.hypergraphdb.HyperGraph; import org.hypergraphdb.algorithms.DefaultALGenerator; import org.hypergraphdb.algorithms.HGDepthFirstTraversal; import org.hypergraphdb.atom.HGSubsumes; import org.hypergraphdb.query.AtomTypeCondition; import org.hypergraphdb.query.TypePlusCondition; import org.hypergraphdb.transaction.HGTransactionException; import org.hypergraphdb.util.HGUtils; /** * <p> * A set of static utility methods operating on types. * </p> * * @author Borislav Iordanov */ public final class TypeUtils { /** * <p>Split a dimension path in dot notation to a <code>String[]</code>.</p> * * @param formatted The dimension path in the format "a.b.c...d" * @return The <code>String[]</code> {"a", "b", "c", ..., "d"} */ public static String [] parseDimensionPath(String formatted) { StringTokenizer tok = new StringTokenizer(formatted, "."); String [] result = new String[tok.countTokens()]; for (int i = 0; i < result.length; i++) result[i] = tok.nextToken(); return result; } public static String formatDimensionPath(String [] dimPath) { StringBuffer result = new StringBuffer(); for (int i = 0; i < dimPath.length; i++) { result.append(dimPath[i]); if (i < dimPath.length - 1) result.append("."); } return result.toString(); } public static HGProjection getProjection(HyperGraph hg, HGAtomType type, String [] dimPath) { HGProjection proj = null; for (int j = 0; j < dimPath.length; j++) { if (! (type instanceof HGCompositeType)) return null; proj = ((HGCompositeType)type).getProjection(dimPath[j]); if (proj == null) return null; type = (HGAtomType)hg.get(proj.getType()); } return proj; } public static HGTypedValue project(HyperGraph hg, HGHandle type, Object value, String [] dimPath, boolean throwExceptionOnNotFound) { Object topvalue = value; for (int j = 0; j < dimPath.length; j++) { HGAtomType tinstance = (HGAtomType)hg.get(type); if (! (tinstance instanceof HGCompositeType)) if (throwExceptionOnNotFound) throw new HGException("Only composite types can be indexed by value parts: " + value + ", during projection of " + formatDimensionPath(dimPath) + " in type " + tinstance); else return null; HGProjection proj = ((HGCompositeType)tinstance).getProjection(dimPath[j]); if (proj == null) if (throwExceptionOnNotFound) throw new HGException("Dimension " + dimPath[j] + " does not exist in type " + tinstance); else return null; if (value == null) throw new IllegalArgumentException("The value " + value + " doesn't have a property at " + Arrays.asList(dimPath)); if (value instanceof HGValueLink) value = ((HGValueLink)value).getValue(); value = proj.project(value); type = proj.getType(); } return new HGTypedValue(value, type); } // ------------------------------------------------------------------------ // The following part deals solely with the management of circular references // during storage and retrieval of aggregate values. Type implementations // would normally use those utility methods to track circular references. // // The implementation is straightforward: two maps and a set are maintained: // // - JAVA_REF_MAP maps pure Java references (Objects) to HGPersistentHandles. // It is used during storage of aggregate values. It is attached to the // current transaction as an attribute. // // - HANDLE_REF_MAP maps HGPersistentHandles to pure Java references. It is // attached to the current thread (since reads are not necessarily enclosed // in a transaction) and it is used during retrieval of aggregate values. // // - HANDLE_REF_SET contains the set of value handles that are being released. // It is attached to the current transaction as an attribute. // // In all cases, type implementations are expected to consult the relevant // map in order to see whether the operation (read, write, remove) has already been // performed in the current context (transaction write or thread-local read). // // Obviously, this mechanism doesn't cross thread boundaries. As always, // this limitation will be reexamined as the need arises. // // Another note: The duality of Record-JavaBeans forces a little hack. We really // want to associate the Java bean instance (and not the temporary Record created // just so that it is stored by the RecordType implementation) to the // HGPersistentHandle of the value. This information, that we are actually tracking // a bean reference, not a Record reference, must be passed from the wrapping type // (a JavaBeanBinding) to the underlying type (RecordType) to finally the // TypeUtils.getNewHandleFor method. The interface WrappedRuntimeInstance was // created to solve this issue. Couldn't find a more elegant way that doesn't // involve changing the HGAtomType interface. The problem is that it is the // responsibility of the HGAtomType.store method to create the handle of the // value. But RecordType.store is called with a Record instance, not a bean // instance and therefore cannot do the mapping Object->HGPersistentHandle // that we need to track circular references. Got it? // // // [Borislav Iordanov] // // Addendum 10/24/2009 - Switched to using thread locals instead of transaction attributes // for those because of the need to save separate copies of values for different atoms. // Suppose we need to save atoms A and B both of which hold the same Java reference 'x'. // If A and B are saved in a single transaction, the value of 'x' will be stored only // once and shared b/w A and B. However, when one of A and B is subsequently deleted, the // other will be left with a dangling UUID pointing to a removed 'x'. Another solution to // this problem would have been reference counting across the board for everything in // the HGStore, but that opens its own can of worms. Using thread locals is ok given the // HGDB transactions API that encourages single-threaded transactions. // private static ThreadLocal<Map<HGPersistentHandle, Object>> HANDLE_REF_MAP = new ThreadLocal<Map<HGPersistentHandle, Object>>(); private static ThreadLocal<Map<Object, HGPersistentHandle>> JAVA_REF_MAP = new ThreadLocal<Map<Object, HGPersistentHandle>>();//hg.getTransactionManager().getContext().getCurrent().getAttribute(JAVA_REF_MAP); private static ThreadLocal<Set<HGPersistentHandle>> HANDLE_REF_SET = new ThreadLocal<Set<HGPersistentHandle>>(); public interface WrappedRuntimeInstance { Object getRealInstance(); } // private static final String JAVA_REF_MAP = "JAVA_REF_VALUE_MAP".intern(); // private static final String HANDLE_REF_SET = "HANDLE_REF_SET".intern(); // private static final String HANDLE_REF_MAP = "HANDLE_REF_MAP".intern(); // public static Map<Object, HGPersistentHandle> getTransactionObjectRefMap(HyperGraph hg) // { // ThreadLocal<Map<Object, HGPersistentHandle>> refMap = // (ThreadLocal<Map<Object, HGPersistentHandle>>)hg.getTransactionManager().getContext().getCurrent().getAttribute(JAVA_REF_MAP); // if (refMap == null) // { // refMap = new ThreadLocal<Map<Object, HGPersistentHandle>>(); // hg.getTransactionManager().getContext().getCurrent().setAttribute(JAVA_REF_MAP, refMap); // } // if (refMap.get() == null) // refMap.set(new IdentityHashMap<Object, HGPersistentHandle>()); // return refMap.get(); // } // public static Map<HGPersistentHandle, Object> getThreadHandleRefMap(HyperGraph graph) // { // Map<HGPersistentHandle, Object> refMap = // (Map<HGPersistentHandle, Object>)graph.getTransactionManager().getContext().getCurrent().getAttribute(HANDLE_REF_MAP); // // HANDLE_REF_MAP.get(); // if (refMap == null) // { // refMap = new HashMap<HGPersistentHandle, Object>(); // graph.getTransactionManager().getContext().getCurrent().setAttribute(HANDLE_REF_MAP, refMap); // } // return refMap; // } // public static Set<HGPersistentHandle> getTransactionHandleSet(HyperGraph hg) // { // Set<HGPersistentHandle> set = // (Set<HGPersistentHandle>)hg.getTransactionManager().getContext().getCurrent().getAttribute(HANDLE_REF_SET); // if (set == null) // { // set = new HashSet<HGPersistentHandle>(); // hg.getTransactionManager().getContext().getCurrent().setAttribute(HANDLE_REF_SET, set); // } // return set; // } /** * <b>Internal method - do not call.</b> */ public static boolean initThreadLocals() { if (JAVA_REF_MAP.get() == null) { JAVA_REF_MAP.set(new IdentityHashMap<Object, HGPersistentHandle>()); HANDLE_REF_MAP.set(new HashMap<HGPersistentHandle, Object>()); HANDLE_REF_SET.set(new HashSet<HGPersistentHandle>()); return true; } else return false; } /** * <b>Internal method - do not call.</b> */ public static void releaseThreadLocals(boolean topCall) { if (topCall) { JAVA_REF_MAP.set(null); HANDLE_REF_MAP.set(null); HANDLE_REF_SET.set(null); } } public static HGPersistentHandle getNewHandleFor(HyperGraph hg, Object value) { boolean topCall = initThreadLocals(); try { HGPersistentHandle result = hg.getHandleFactory().makeHandle(); if (value instanceof WrappedRuntimeInstance) value = ((WrappedRuntimeInstance)value).getRealInstance(); JAVA_REF_MAP.get().put(value, result); return result; } finally { releaseThreadLocals(topCall); } } public static boolean isValueReleased(HyperGraph graph, HGPersistentHandle h) { boolean topCall = initThreadLocals(); try { return HANDLE_REF_SET.get().contains(h); } finally { releaseThreadLocals(topCall); } } public static void releaseValue(HyperGraph graph, HGAtomType type, HGPersistentHandle h) { boolean topCall = initThreadLocals(); try { // If this was previously added in the course of the current transaction, // remove it from the relevant maps. Object instance = HANDLE_REF_MAP.get().remove(h); if (instance != null) JAVA_REF_MAP.get().remove(instance); if (! (type instanceof HGRefCountedType)) HANDLE_REF_SET.get().add(h); type.release(h); } finally { releaseThreadLocals(topCall); } } public static HGPersistentHandle storeValue(HyperGraph graph, Object value, HGAtomType type) { boolean topCall = initThreadLocals(); try { HGPersistentHandle result = null; if (! (type instanceof HGRefCountedType)) result = JAVA_REF_MAP.get().get(value); if (result == null) { result = type.store(value); JAVA_REF_MAP.get().put(value, result); } HANDLE_REF_MAP.get().put(result, value); return result; } finally { releaseThreadLocals(topCall); } } public static HGPersistentHandle getHandleFor(HyperGraph graph, Object value) { boolean topCall = initThreadLocals(); try { return JAVA_REF_MAP.get().get(value); } finally { releaseThreadLocals(topCall); } } public static Object getValueFor(HyperGraph graph, HGPersistentHandle h) { boolean topCall = initThreadLocals(); try { return HANDLE_REF_MAP.get().get(h); } finally { releaseThreadLocals(topCall); } } public static void setValueFor(HyperGraph graph, HGPersistentHandle h, Object value) { boolean topCall = initThreadLocals(); try { Map<HGPersistentHandle, Object> refMap = HANDLE_REF_MAP.get(); if (!refMap.containsKey(h)) refMap.put(h, value); } finally { releaseThreadLocals(topCall); } } public static Object makeValue(HyperGraph graph, HGPersistentHandle h, HGAtomType type) { boolean topCall = initThreadLocals(); try { Map<HGPersistentHandle, Object> refMap = HANDLE_REF_MAP.get(); Object result = refMap.get(h); if (result == null) { result = type.make(h, null, null); refMap.put(h, result); } return result; } finally { releaseThreadLocals(topCall); } } public static List<HGHandle> subsumesClosure(HyperGraph graph, HGHandle baseType) { DefaultALGenerator alGenerator = new DefaultALGenerator(graph, new AtomTypeCondition(HGSubsumes.class), null, false, true, false); HGDepthFirstTraversal traversal = new HGDepthFirstTraversal(baseType, alGenerator); ArrayList<HGHandle> subTypes = new ArrayList<HGHandle>(); while (traversal.hasNext()) subTypes.add(traversal.next().getSecond()); subTypes.add(baseType); return subTypes; } public static boolean deleteType(HyperGraph graph, HGHandle th, boolean recursive) { if (recursive) { List<HGHandle> L = subsumesClosure(graph, th); for (HGHandle h : L) if (!deleteType(graph, h)) return false; return true; } else return deleteType(graph, th); } public static boolean deleteType(HyperGraph graph, Class<?> type, boolean recursive) { HGHandle th = graph.getTypeSystem().getTypeHandleIfDefined(type); if (th == null) return true; else return deleteType(graph, th, recursive); } public static boolean deleteType(HyperGraph graph, HGHandle type) { if (deleteInstances(graph, type)) return graph.remove(type); else return false; } public static boolean deleteInstances(HyperGraph graph, HGHandle type) { int batchSize = 100; // perhaps this should become a parameter HGHandle [] batch = new HGHandle[batchSize]; for (boolean done = false; !done; ) { HGSearchResult<HGHandle> rs = null; try { int fetched = 0; graph.getTransactionManager().beginTransaction(); try { rs = graph.find(new TypePlusCondition(type)); while (fetched < batch.length && rs.hasNext()) batch[fetched++] = rs.next(); rs.close(); } finally { try { graph.getTransactionManager().endTransaction(true); } catch (HGTransactionException tex) { throw new HGException(tex); } } done = fetched == 0; graph.getTransactionManager().beginTransaction(); try { while (--fetched >= 0) if (!graph.remove(batch[fetched])) return false; graph.getTransactionManager().endTransaction(true); } catch (Throwable t) { try { graph.getTransactionManager().endTransaction(false); } catch (Throwable t1) { t1.printStackTrace(System.err); } done = false; HGUtils.wrapAndRethrow(t); } } finally { HGUtils.closeNoException(rs); } } return true; } }