package org.neo4j.meta.model; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.Transaction; import org.neo4j.index.IndexService; import org.neo4j.util.GraphDatabaseUtil; /** * The access point of a meta model. Is given a root node where all the * namespaces, properties and classes are stored/read underneath. */ public class MetaModelImpl implements MetaModel { private GraphDatabaseService graphDb; private GraphDatabaseUtil graphDbUtil; private IndexService indexService; private Map<String, MetaModelNamespace> namespaceCache = Collections.synchronizedMap( new HashMap<String, MetaModelNamespace>() ); /** * @param graphDB the {@link GraphDatabaseService} used for this meta model. */ public MetaModelImpl( GraphDatabaseService graphDB, IndexService indexService ) { this.graphDb = graphDB; this.graphDbUtil = new GraphDatabaseUtil( graphDB ); this.indexService = indexService; } /** * @return the {@link GraphDatabaseService} given in the constructor. */ public GraphDatabaseService graphDb() { return this.graphDb; } /** * @return the {@link IndexService} given in the constructor. */ public IndexService indexService() { return this.indexService; } protected GraphDatabaseUtil graphDbUtil() { return this.graphDbUtil; } protected Node rootNode() { return graphDbUtil().getOrCreateSubReferenceNode( MetaModelRelTypes.REF_TO_META_SUBREF ); } public MetaModelNamespace getNamespace( String name, boolean allowCreate ) throws DuplicateNameException { assert name != null; return findOrCreateInCollection( getNamespaces(), name, allowCreate, MetaModelNamespace.class, namespaceCache ); } public MetaModelNamespace getGlobalNamespace() { MetaModelNamespace ns = null; try{ ns = findOrCreateInCollection( getNamespaces(), null, true, MetaModelNamespace.class, namespaceCache ); }catch(DuplicateNameException dne){ //Do nothing. Global namesepace has no name, so no duplication can occur } return ns; } protected <T extends MetaModelObject> T findOrCreateInCollection( Collection<T> collection, String nameOrNullForGlobal, boolean allowCreate, Class<T> theClass, Map<String, T> cacheOrNull ) throws DuplicateNameException { Transaction tx = graphDb().beginTx(); try { T foundItem = safeGetFromCache( cacheOrNull, nameOrNullForGlobal ); if ( foundItem != null ) { tx.success(); return foundItem; } for ( T item : collection ) { String theName = item.getName(); if ( nameOrNullForGlobal == null || theName == null ) { if ( nameOrNullForGlobal == null && theName == null ) { foundItem = item; break; } } else if ( theName.equals( nameOrNullForGlobal ) ) { foundItem = item; break; } } if ( foundItem != null ) { if ( cacheOrNull != null ) { cacheOrNull.put( nameOrNullForGlobal, foundItem ); } return foundItem; } if ( !allowCreate ) { return null; } Node node = graphDb().createNode(); T item = null; try { item = theClass.getConstructor( MetaModel.class, Node.class ).newInstance( this, node ); } catch ( Exception e ) { throw new RuntimeException( e ); } if ( nameOrNullForGlobal != null ) { item.setName( nameOrNullForGlobal ); } collection.add( item ); tx.success(); return item; } finally { tx.finish(); } } private <T extends MetaModelObject> T safeGetFromCache( Map<String, T> cacheOrNull, String key ) { T result = null; if ( cacheOrNull != null ) { result = cacheOrNull.get( key ); if ( result != null ) { try { graphDb().getNodeById( result.node().getId() ); } catch ( NotFoundException e ) { cacheOrNull.remove( result ); result = null; } } } return result; } public Collection<MetaModelNamespace> getNamespaces() { return new ObjectCollection<MetaModelNamespace>( rootNode(), MetaModelRelTypes.META_NAMESPACE, Direction.OUTGOING, this, MetaModelNamespace.class ); } public <T> T lookup( MetaModelProperty property, PropertyLookerUpper<T> finder, MetaModelClass... classes ) { // TODO Maybe add some form of caching here since this method will be // HEAVILY used. It's the main way of looking things up in the meta // model, f.ex. validation and conversion of values a.s.o. Transaction tx = graphDb().beginTx(); try { T result = LookupUtil.lookup( property, finder, classes ); tx.success(); return result; } finally { tx.finish(); } } public <T> T lookup( MetaModelRelationship relationshipType, RelationshipLookerUpper<T> finder, MetaModelClass... classes ) { // TODO Maybe add some form of caching here since this method will be // HEAVILY used. It's the main way of looking things up in the meta // model, f.ex. validation and conversion of values a.s.o. Transaction tx = graphDb().beginTx(); try { T result = LookupUtil.lookup( relationshipType, finder, classes ); tx.success(); return result; } finally { tx.finish(); } } }