Index: dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java (revision 1382) +++ dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java (working copy) @@ -456,6 +456,13 @@ String nameOfWorkspaceToBeCloned = request.nameOfWorkspaceToBeCloned(); MapWorkspace original = repository.getWorkspace(nameOfWorkspaceToBeCloned); MapWorkspace target = repository.getWorkspace(targetWorkspaceName); + + if (target != null) { + String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(targetWorkspaceName, repository.getSourceName()); + request.setError(new InvalidWorkspaceException(msg)); + return; + } + if (original == null) { switch (request.cloneConflictBehavior()) { case DO_NOT_CLONE: @@ -465,14 +472,11 @@ return; case SKIP_CLONE: target = repository.createWorkspace(context, targetWorkspaceName, request.targetConflictBehavior()); - if (target == null) { - msg = GraphI18n.workspaceAlreadyExistsInRepository.text(targetWorkspaceName, repository.getSourceName()); - request.setError(new InvalidWorkspaceException(msg)); - } else { - MapNode root = target.getRoot(); - request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); - request.setActualWorkspaceName(target.getName()); - } + assert target != null; + + MapNode root = target.getRoot(); + request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); + request.setActualWorkspaceName(target.getName()); return; } } @@ -481,17 +485,11 @@ targetWorkspaceName, request.targetConflictBehavior(), nameOfWorkspaceToBeCloned); - if (target == null) { - // Since the original was there, the only reason the target wasn't created was because the workspace already existed - // ... - String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(targetWorkspaceName, repository.getSourceName()); - request.setError(new InvalidWorkspaceException(msg)); - } else { - MapNode root = target.getRoot(); - request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); - request.setActualWorkspaceName(target.getName()); - recordChange(request); - } + assert target != null; + MapNode root = target.getRoot(); + request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); + request.setActualWorkspaceName(target.getName()); + recordChange(request); } /** Index: dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java (revision 1382) +++ dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java (working copy) @@ -967,16 +967,24 @@ * @author Randall Hauch */ @Immutable - protected static class LocationWithDepth { + protected class LocationWithDepth { protected final Location location; protected final int depth; - protected LocationWithDepth( Location location, + public LocationWithDepth( Location location, int depth ) { this.location = location; this.depth = depth; } + public Location getLocation() { + return location; + } + + public int getDepth() { + return depth; + } + @Override public int hashCode() { return location.hashCode(); Index: extensions/dna-connector-store-jpa/pom.xml =================================================================== --- extensions/dna-connector-store-jpa/pom.xml (revision 1382) +++ extensions/dna-connector-store-jpa/pom.xml (working copy) @@ -45,7 +45,13 @@ hsqldb hsqldb 1.8.0.2 + test + + com.google.code.google-collections + google-collect + snapshot-20080530 + Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/JpaConnectorI18n.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/JpaConnectorI18n.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/JpaConnectorI18n.java (working copy) @@ -55,6 +55,7 @@ public static I18n connectionIsNoLongerOpen; public static I18n basicModelDescription; + public static I18n simpleModelDescription; static { try { Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/JpaSource.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/JpaSource.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/JpaSource.java (working copy) @@ -50,6 +50,7 @@ import org.jboss.dna.common.util.Logger; import org.jboss.dna.common.util.StringUtil; import org.jboss.dna.connector.store.jpa.model.basic.BasicModel; +import org.jboss.dna.connector.store.jpa.model.simple.SimpleModel; import org.jboss.dna.connector.store.jpa.util.StoreOptionEntity; import org.jboss.dna.connector.store.jpa.util.StoreOptions; import org.jboss.dna.graph.ExecutionContext; @@ -74,8 +75,8 @@ */ public static class Models { public static final Model BASIC = new BasicModel(); - // public static final Model SIMPLE = new SimpleModel(); - private static final Model[] ALL_ARRAY = new Model[] {BASIC /*, SIMPLE */}; + public static final Model SIMPLE = new SimpleModel(); + private static final Model[] ALL_ARRAY = new Model[] {BASIC, SIMPLE}; private static final List MODIFIABLE_MODELS = new ArrayList(Arrays.asList(ALL_ARRAY)); public static final Collection ALL = Collections.unmodifiableCollection(MODIFIABLE_MODELS); public static final Model DEFAULT = BASIC; @@ -169,7 +170,7 @@ private static final int DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE = 100; private static final int DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED = 1; private static final int DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS = 60 * 3; // 3 minutes - private static final int DEFAULT_LARGE_VALUE_SIZE_IN_BYTES = 2 ^ 10; // 1 kilobyte + private static final int DEFAULT_LARGE_VALUE_SIZE_IN_BYTES = 1 << 10; // 1 kilobyte private static final boolean DEFAULT_COMPRESS_DATA = true; private static final boolean DEFAULT_ENFORCE_REFERENTIAL_INTEGRITY = true; Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/basic/BasicJpaConnection.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/basic/BasicJpaConnection.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/basic/BasicJpaConnection.java (working copy) @@ -38,7 +38,7 @@ import org.jboss.dna.graph.request.processor.RequestProcessor; /** - * The repository connection to JPA repository sources. + * The repository connection to JPA repository sources that use the {@link BasicModel basic model}. */ class BasicJpaConnection implements RepositoryConnection { Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/basic/SubgraphQuery.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/basic/SubgraphQuery.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/basic/SubgraphQuery.java (working copy) @@ -71,6 +71,7 @@ assert subgraphRootUuid != null; assert workspaceId != null; assert maxDepth >= 0; + if (maxDepth == 0) maxDepth = Integer.MAX_VALUE; final String subgraphRootUuidString = subgraphRootUuid.toString(); // Create a new subgraph query, and add a child for the root ... @@ -110,6 +111,7 @@ } throw t; } + return new SubgraphQuery(context, entities, workspaceId, query, subgraphRootPath, maxDepth); } Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/LargeValueEntity.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/LargeValueEntity.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/LargeValueEntity.java (revision 0) @@ -0,0 +1,221 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.NamedQuery; +import javax.persistence.Query; +import javax.persistence.Table; +import org.jboss.dna.common.SystemFailureException; +import org.jboss.dna.common.util.SecureHash; +import org.jboss.dna.common.util.StringUtil; +import org.jboss.dna.graph.property.PropertyType; + +/** + * A single property value that is too large to be stored on the individual node, and which will be shared among all properties + * that have the same value. Note that the large values are stored independently of workspace, so one large value may be shared by + * properties of nodes in different workspaces. + */ +@Entity +@Table( name = "DNA_SIMPLE_LARGE_VALUES" ) +@NamedQuery( name = "LargeValueEntity.deleteUnused", query = "delete LargeValueEntity value where value.hash not in (select values.hash from NodeEntity node join node.largeValues values)" ) +public class LargeValueEntity { + + @Id + @Column( name = "SHA1", nullable = false, length = 40 ) + private String hash; + + /** + * The property type for this value. Typically, this is {@link PropertyType#STRING} or {@link PropertyType#BINARY}, although + * technically it could be any type. + */ + @Enumerated( value = EnumType.STRING ) + @Column( name = "TYPE", nullable = false ) + private PropertyType type; + + /** + * The number of bytes in this value. + */ + @Column( name = "LENGTH", nullable = false ) + private long length; + + /** + * Flag specifying whether the binary data is stored in a compressed format. + */ + @Column( name = "COMPRESSED", nullable = true ) + private Boolean compressed; + + /** + * Lazily-fetched value + */ + @Lob + @Column( name = "DATA", nullable = false ) + private byte[] data; + + public String getHash() { + return hash; + } + + public void setHash( String hash ) { + this.hash = hash; + } + + /** + * @return length + */ + public long getLength() { + return length; + } + + /** + * @param length Sets length to the specified value. + */ + public void setLength( long length ) { + this.length = length; + } + + /** + * @return type + */ + public PropertyType getType() { + return type; + } + + /** + * @param type Sets type to the specified value. + */ + public void setType( PropertyType type ) { + this.type = type; + } + + /** + * @return data + */ + public byte[] getData() { + return data; + } + + /** + * @param data Sets data to the specified value. + */ + public void setData( byte[] data ) { + this.data = data; + } + + /** + * @return compressed + */ + public boolean isCompressed() { + return compressed != null && compressed.booleanValue(); + } + + /** + * @param compressed Sets compressed to the specified value. + */ + public void setCompressed( boolean compressed ) { + this.compressed = Boolean.valueOf(compressed); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return hash.hashCode(); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof LargeValueEntity) { + LargeValueEntity that = (LargeValueEntity)obj; + if (this.getHash().equals(that.getHash())) return true; + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Large " + this.type + " value (hash=" + this.getHash() + ",compressed=" + isCompressed() + ")"; + } + + /** + * Delete all unused large value entities. + * + * @param manager the manager; never null + * @return the number of deleted large values + */ + public static int deleteUnused( EntityManager manager ) { + assert manager != null; + Query delete = manager.createNamedQuery("LargeValueEntity.deleteUnused"); + int result = delete.executeUpdate(); + manager.flush(); + return result; + } + + private static byte[] computeHash( byte[] value ) { + try { + return SecureHash.getHash(SecureHash.Algorithm.SHA_1, value); + } catch (NoSuchAlgorithmException e) { + throw new SystemFailureException(e); + } + } + + public static LargeValueEntity create( byte[] data, + PropertyType type, + boolean compressed ) { + try { + String hashStr = StringUtil.getHexString(computeHash(data)); + LargeValueEntity entity = new LargeValueEntity(); + + entity.setData(data); + entity.setType(type); + entity.setCompressed(compressed); + entity.setHash(hashStr); + return entity; + } catch (UnsupportedEncodingException uee) { + throw new IllegalStateException(uee); + } + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\LargeValueEntity.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/NodeEntity.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/NodeEntity.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/NodeEntity.java (revision 0) @@ -0,0 +1,530 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Query; +import javax.persistence.Table; +import org.hibernate.annotations.Index; +import org.jboss.dna.common.text.Inflector; +import org.jboss.dna.common.util.HashCode; +import org.jboss.dna.connector.store.jpa.model.common.NamespaceEntity; +import org.jboss.dna.connector.store.jpa.util.Serializer; + +/** + * An entity a node and its properties. In addition to the references to the parent and child nodes, this entity also maintains + * the indexInParent of the indexInParent within the parent node's list of all children, the child's name ( + * {@link #getChildName() local part} and {@link #getChildNamespace() namespace}), and the same-name-sibling indexInParent (if + * there is one). + */ +@Entity +@org.hibernate.annotations.Table( appliesTo = "DNA_SIMPLE_NODE", indexes = { + @Index( name = "NODEUUID_INX", columnNames = {"WORKSPACE_ID", "NODE_UUID"} ), + @Index( name = "CHILDINDEX_INX", columnNames = {"WORKSPACE_ID", "PARENT_ID", "CHILD_INDEX"} ), + @Index( name = "CHILDNAME_INX", columnNames = {"WORKSPACE_ID", "PARENT_ID", "CHILD_NAME_NS_ID", "CHILD_NAME_LOCAL", + "SNS_INDEX"} )} ) +@Table( name = "DNA_SIMPLE_NODE" ) +@NamedQueries( { + @NamedQuery( name = "NodeEntity.findByNodeUuid", query = "from NodeEntity as node where node.workspaceId = :workspaceId and node.nodeUuidString = :nodeUuidString" ), + @NamedQuery( name = "NodeEntity.findInWorkspace", query = "from NodeEntity as node where node.workspaceId = :workspaceId" ), + @NamedQuery( name = "NodeEntity.deleteAllInWorkspace", query = "delete from NodeEntity where workspaceId = :workspaceId" ), + @NamedQuery( name = "NodeEntity.withLargeValues", query = "from NodeEntity as node where node.workspaceId = :workspaceId and size(node.largeValues) > 0" )} ) +public class NodeEntity { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + @Column( name = "ID" ) + private long id; + + @Column( name = "WORKSPACE_ID", nullable = false ) + private long workspaceId; + + @ManyToOne( fetch = FetchType.LAZY ) + @JoinColumn( name = "PARENT_ID", referencedColumnName = "id", nullable = true ) + private NodeEntity parent; + + @Column( name = "NODE_UUID", nullable = false, length = 36 ) + private String nodeUuidString; + + /** The zero-based index */ + @Column( name = "CHILD_INDEX", nullable = false, unique = false ) + private int indexInParent = 0; + + @ManyToOne( fetch = FetchType.LAZY ) + @JoinColumn( name = "CHILD_NAME_NS_ID", nullable = true ) + private NamespaceEntity childNamespace; + + @Column( name = "CHILD_NAME_LOCAL", nullable = true, unique = false, length = 512 ) + private String childName; + + @Column( name = "SNS_INDEX", nullable = false, unique = false ) + private int sameNameSiblingIndex = 1; + + @OneToMany( fetch = FetchType.LAZY, mappedBy = "parent" ) + @OrderBy( "indexInParent" ) + private final List children = new ArrayList(); + /** + * Tracks whether this node allows or disallows its children to have the same names (to be same-name-siblings). The model uses + * this to know whether it can optimization the database operations when creating, inserting, or removing children. + */ + @Column( name = "ALLOWS_SNS", nullable = false, unique = false ) + private boolean allowsSameNameChildren; + + @Lob + @Column( name = "DATA", nullable = true, unique = false ) + private byte[] data; + + @Column( name = "NUM_PROPS", nullable = false ) + private int propertyCount; + + /** + * Flag specifying whether the binary data is stored in a compressed format. + */ + @Column( name = "COMPRESSED", nullable = true ) + private Boolean compressed; + + /** + * Flag specifying whether this node should be included in referential integrity enforcement. + */ + @Column( name = "ENFORCEREFINTEG", nullable = false ) + private boolean referentialIntegrityEnforced = true; + + // @org.hibernate.annotations.CollectionOfElements( fetch = FetchType.LAZY ) + // @JoinTable( name = "DNA_LARGEVALUE_USAGES", joinColumns = {@JoinColumn( name = "WORKSPACE_ID" ), + // @JoinColumn( name = "NODE_UUID" )} ) + @ManyToMany + @JoinTable( name = "DNA_LARGEVALUE_USAGES", joinColumns = {@JoinColumn( name = "ID" )} ) + private final Collection largeValues = new HashSet(); + + public NodeEntity() { + } + + public NodeEntity( long id, + NodeEntity parent, + String nodeUuidString, + long workspaceId, + int indexInParent, + NamespaceEntity ns, + String name ) { + this.id = id; + this.parent = parent; + this.nodeUuidString = nodeUuidString; + this.workspaceId = workspaceId; + this.indexInParent = indexInParent; + this.childNamespace = ns; + this.childName = name; + this.sameNameSiblingIndex = 1; + } + + public NodeEntity( long id, + NodeEntity parent, + String nodeUuidString, + long workspaceId, + int indexInParent, + NamespaceEntity ns, + String name, + int sameNameSiblingIndex ) { + this.id = id; + this.parent = parent; + this.nodeUuidString = nodeUuidString; + this.workspaceId = workspaceId; + this.indexInParent = indexInParent; + this.childNamespace = ns; + this.childName = name; + this.sameNameSiblingIndex = sameNameSiblingIndex; + } + + /** + * Returns this node's unique identifier + * + * @return this node's unique identifier + */ + public long getNodeId() { + return id; + } + + /** + * @param id Sets this node's unique identifier + */ + public void setNodeId( long id ) { + this.id = id; + } + + /** + * Returns the parent identifier + * + * @return the parent identifier + */ + public NodeEntity getParent() { + return parent; + } + + /** + * Sets the parent identifier + * + * @param parent the parent identifier + */ + public void setParent( NodeEntity parent ) { + this.parent = parent; + } + + /** + * Returns the node UUID string + * + * @return the node UUID string + */ + public String getNodeUuidString() { + return nodeUuidString; + } + + /** + * Sets the node UUID string + * + * @param nodeUuidString the node UUID string + */ + public void setNodeUuidString( String nodeUuidString ) { + this.nodeUuidString = nodeUuidString; + } + + /** + * Returns the identifier of the workspace containing this node + * + * @return the identifier of the workspace containing this node + */ + public long getWorkspaceId() { + return workspaceId; + } + + /** + * Sets the identifier of the workspace containing this node + * + * @param workspaceId the identifier of the workspace containing this node + */ + public void setWorkspaceId( long workspaceId ) { + this.workspaceId = workspaceId; + } + + /** + * Get the zero-based index of this child within the parent's list of children + * + * @return the zero-based index of this child + */ + public int getIndexInParent() { + return indexInParent; + } + + /** + * @param index Sets indexInParent to the specified value. + */ + public void setIndexInParent( int index ) { + this.indexInParent = index; + } + + /** + * @return childName + */ + public String getChildName() { + return childName; + } + + /** + * @param childName Sets childName to the specified value. + */ + public void setChildName( String childName ) { + this.childName = childName; + } + + /** + * @return childNamespace + */ + public NamespaceEntity getChildNamespace() { + return childNamespace; + } + + /** + * @param childNamespace Sets childNamespace to the specified value. + */ + public void setChildNamespace( NamespaceEntity childNamespace ) { + this.childNamespace = childNamespace; + } + + /** + * @return sameNameSiblingIndex + */ + public int getSameNameSiblingIndex() { + return sameNameSiblingIndex; + } + + /** + * @param sameNameSiblingIndex Sets sameNameSiblingIndex to the specified value. + */ + public void setSameNameSiblingIndex( int sameNameSiblingIndex ) { + this.sameNameSiblingIndex = sameNameSiblingIndex; + } + + /** + * @return allowsSameNameChildren + */ + public boolean getAllowsSameNameChildren() { + return allowsSameNameChildren; + } + + /** + * @param allowsSameNameChildren Sets allowsSameNameChildren to the specified value. + */ + public void setAllowsSameNameChildren( boolean allowsSameNameChildren ) { + this.allowsSameNameChildren = allowsSameNameChildren; + } + + /** + * Get the data that represents the {@link Serializer packed} properties. + * + * @return the raw data representing the properties + */ + public byte[] getData() { + return data; + } + + /** + * Set the data that represents the {@link Serializer packed} properties. + * + * @param data the raw data representing the properties + */ + public void setData( byte[] data ) { + this.data = data; + } + + /** + * @return propertyCount + */ + public int getPropertyCount() { + return propertyCount; + } + + /** + * @param propertyCount Sets propertyCount to the specified value. + */ + public void setPropertyCount( int propertyCount ) { + this.propertyCount = propertyCount; + } + + /** + * @return compressed + */ + public boolean isCompressed() { + return compressed != null && compressed.booleanValue(); + } + + /** + * @param compressed Sets compressed to the specified value. + */ + public void setCompressed( boolean compressed ) { + this.compressed = Boolean.valueOf(compressed); + } + + public List getChildren() { + return children; + } + + public void addChild( NodeEntity child ) { + children.add(child); + child.setIndexInParent(children.size() - 1); + } + + public void addChild( int index, + NodeEntity child ) { + for (NodeEntity existing : children.subList(index, children.size() - 1)) { + existing.setIndexInParent(existing.getIndexInParent() + 1); + } + + children.add(index, child); + child.setIndexInParent(index); + } + + public boolean removeChild( int index ) { + NodeEntity removedNode = children.remove(index); + if (removedNode == null) return false; + removedNode.setParent(null); + + if (index < children.size()) { + for (NodeEntity child : children.subList(index, children.size() - 1)) { + child.setIndexInParent(child.getIndexInParent() - 1); + } + } + return true; + } + + /** + * @return largeValues + */ + public Collection getLargeValues() { + return largeValues; + } + + /** + * @return referentialIntegrityEnforced + */ + public boolean isReferentialIntegrityEnforced() { + return referentialIntegrityEnforced; + } + + /** + * @param referentialIntegrityEnforced Sets referentialIntegrityEnforced to the specified value. + */ + public void setReferentialIntegrityEnforced( boolean referentialIntegrityEnforced ) { + this.referentialIntegrityEnforced = referentialIntegrityEnforced; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return HashCode.compute(id); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof NodeEntity) { + NodeEntity that = (NodeEntity)obj; + if (this.id != that.id) return false; + return true; + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (childNamespace != null) { + sb.append('{').append(childNamespace).append("}:"); + } + sb.append(childName); + if (sameNameSiblingIndex > 1) { + sb.append('[').append(sameNameSiblingIndex).append(']'); + } + sb.append(" (id=").append(getNodeUuidString()).append(")"); + if (parent != null) { + sb.append(" is "); + sb.append(Inflector.getInstance().ordinalize(indexInParent)); + sb.append(" child of "); + sb.append(parent.getNodeId()); + sb.append(" in workspace "); + sb.append(getWorkspaceId()); + } else { + sb.append(" is root in workspace "); + sb.append(getWorkspaceId()); + } + return sb.toString(); + } + + @SuppressWarnings( "unchecked" ) + public static void adjustSnsIndexesAndIndexesAfterRemoving( EntityManager entities, + Long workspaceId, + String uuidParent, + String childName, + long childNamespaceIndex, + int childIndex ) { + // Decrement the 'indexInParent' index values for all nodes above the previously removed sibling ... + Query query = entities.createNamedQuery("NodeEntity.findChildrenAfterIndexUnderParent"); + query.setParameter("workspaceId", workspaceId); + query.setParameter("parentUuidString", uuidParent); + query.setParameter("afterIndex", childIndex); + for (NodeEntity entity : (List)query.getResultList()) { + // Decrement the index in parent ... + entity.setIndexInParent(entity.getIndexInParent() - 1); + if (entity.getChildName().equals(childName) && entity.getChildNamespace().getId() == childNamespaceIndex) { + // The name matches, so decrement the SNS index ... + entity.setSameNameSiblingIndex(entity.getSameNameSiblingIndex() - 1); + } + } + } + + @SuppressWarnings( "unchecked" ) + public static int adjustSnsIndexesAndIndexes( EntityManager entities, + Long workspaceId, + String uuidParent, + int afterIndex, + int untilIndex, + long childNamespaceIndex, + String childName, + int modifier ) { + int snsCount = 0; + + // Decrement the 'indexInParent' index values for all nodes above the previously removed sibling ... + Query query = entities.createNamedQuery("NodeEntity.findChildrenAfterIndexUnderParent"); + query.setParameter("workspaceId", workspaceId); + query.setParameter("parentUuidString", uuidParent); + query.setParameter("afterIndex", afterIndex); + + int index = afterIndex; + for (NodeEntity entity : (List)query.getResultList()) { + if (++index > untilIndex) { + break; + } + + // Decrement the index in parent ... + entity.setIndexInParent(entity.getIndexInParent() + modifier); + if (entity.getChildName().equals(childName) && entity.getChildNamespace().getId() == childNamespaceIndex) { + // The name matches, so decrement the SNS index ... + entity.setSameNameSiblingIndex(entity.getSameNameSiblingIndex() + modifier); + snsCount++; + } + } + + return snsCount; + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\NodeEntity.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/package-info.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/package-info.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/package-info.java (revision 0) @@ -0,0 +1,29 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +/** + * The classes that define the "basic" storage model for the JPA connector. + */ + +package org.jboss.dna.connector.store.jpa.model.simple; + Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\package-info.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/ReferenceEntity.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/ReferenceEntity.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/ReferenceEntity.java (revision 0) @@ -0,0 +1,211 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.util.Collection; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.NoResultException; +import javax.persistence.Query; +import javax.persistence.Table; +import org.hibernate.annotations.Index; + +/** + * A record of a reference from one node to another. + */ +@Entity +@Table( name = "DNA_SIMPLE_REFERENCES" ) +@org.hibernate.annotations.Table( appliesTo = "DNA_SIMPLE_REFERENCES", indexes = { + @Index( name = "REFINDEX_INX", columnNames = {"WORKSPACE_ID", "FROM_UUID", "TO_UUID"} ), + @Index( name = "REFTOUUID_INX", columnNames = {"WORKSPACE_ID", "TO_UUID"} )} ) +@NamedQueries( { + @NamedQuery( name = "ReferenceEntity.removeReferencesFrom", query = "delete ReferenceEntity where id.workspaceId = :workspaceId and id.fromUuidString = :fromUuid" ), + @NamedQuery( name = "ReferenceEntity.removeNonEnforcedReferences", query = "delete ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.fromUuidString not in ( select props.id.uuidString from PropertiesEntity props where props.referentialIntegrityEnforced = true and props.id.workspaceId = :workspaceId )" ), + @NamedQuery( name = "ReferenceEntity.countUnresolveReferences", query = "select count(*) from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.toUuidString not in ( select props.id.uuidString from PropertiesEntity props where props.referentialIntegrityEnforced = true and props.id.workspaceId = :workspaceId )" ), + @NamedQuery( name = "ReferenceEntity.getUnresolveReferences", query = "select ref from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.toUuidString not in ( select props.id.uuidString from PropertiesEntity props where props.referentialIntegrityEnforced = true and props.id.workspaceId = :workspaceId )" ), + @NamedQuery( name = "ReferenceEntity.findInWorkspace", query = "select ref from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId" ), + @NamedQuery( name = "ReferenceEntity.getInwardReferencesForList", query = "select ref from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.toUuidString in (:toUuidList)" )} ) +public class ReferenceEntity { + + @Id + private ReferenceId id; + + /** + * + */ + public ReferenceEntity() { + } + + /** + * @param id the id + */ + public ReferenceEntity( ReferenceId id ) { + this.id = id; + } + + /** + * @return id + */ + public ReferenceId getId() { + return id; + } + + /** + * @param id Sets id to the specified value. + */ + public void setId( ReferenceId id ) { + this.id = id; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof ReferenceEntity) { + ReferenceEntity that = (ReferenceEntity)obj; + if (this.getId().equals(that.getId())) return true; + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.id.toString(); + } + + /** + * Delete all references that start from the node with the supplied UUID. + * + * @param workspaceId the ID of the workspace; may not be null + * @param uuid the UUID of the node from which the references start + * @param manager the manager; may not be null + * @return the number of deleted references + */ + public static int deleteReferencesFrom( Long workspaceId, + String uuid, + EntityManager manager ) { + assert manager != null; + Query delete = manager.createNamedQuery("ReferenceEntity.removeReferencesFrom"); + delete.setParameter("fromUuid", uuid); + delete.setParameter("workspaceId", workspaceId); + int result = delete.executeUpdate(); + manager.flush(); + return result; + } + + /** + * Delete all references (in all workspaces) that start from nodes that do not require enforced referential integrity. + * + * @param workspaceId the ID of the workspace; may not be null + * @param manager the manager; may not be null + * @return the number of deleted references + */ + public static int deleteUnenforcedReferences( Long workspaceId, + EntityManager manager ) { + assert manager != null; + Query delete = manager.createNamedQuery("ReferenceEntity.removeNonEnforcedReferences"); + delete.setParameter("workspaceId", workspaceId); + int result = delete.executeUpdate(); + manager.flush(); + return result; + } + + /** + * Delete all references that start from nodes that do not support enforced referential integrity. + * + * @param workspaceId the ID of the workspace; may not be null + * @param manager the manager; may not be null + * @return the number of deleted references + */ + public static int countAllReferencesResolved( Long workspaceId, + EntityManager manager ) { + assert manager != null; + Query query = manager.createNamedQuery("ReferenceEntity.getUnresolveReferences"); + query.setParameter("workspaceId", workspaceId); + try { + return (Integer)query.getSingleResult(); + } catch (NoResultException e) { + return 0; + } + } + + /** + * Delete all references that start from nodes that do not support enforced referential integrity. + * + * @param workspaceId the ID of the workspace; may not be null + * @param manager the manager; may not be null + * @return the number of deleted references + */ + @SuppressWarnings( "unchecked" ) + public static List verifyAllReferencesResolved( Long workspaceId, + EntityManager manager ) { + assert manager != null; + Query query = manager.createNamedQuery("ReferenceEntity.getUnresolveReferences"); + query.setParameter("workspaceId", workspaceId); + return query.getResultList(); + } + + /** + * Returns a list of all references to UUIDs in the given list within the given workspace + * + * @param workspaceId the ID of the workspace; may not be null + * @param uuids the UUIDs (as strings) of the nodes to check; may not be null + * @param manager the manager; may not be null + * @return the number of deleted references + */ + @SuppressWarnings( "unchecked" ) + public static List getReferencesToUuids( Long workspaceId, + Collection uuids, + EntityManager manager ) { + assert manager != null; + + Query query = manager.createNamedQuery("ReferenceEntity.getInwardReferencesForList"); + query.setParameter("workspaceId", workspaceId); + query.setParameter("toUuidList", uuids); + return query.getResultList(); + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\ReferenceEntity.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/ReferenceId.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/ReferenceId.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/ReferenceId.java (revision 0) @@ -0,0 +1,137 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import net.jcip.annotations.Immutable; +import org.jboss.dna.common.util.HashCode; + +/** + * An identifier for a reference, comprised of a workspace ID, a single UUID (in string form) of the node containing the + * reference, and a single UUID (in string form) of the node being referenced. + */ +@Embeddable +@Immutable +@org.hibernate.annotations.Immutable +public class ReferenceId implements Serializable { + + /** + * Version {@value} + */ + private static final long serialVersionUID = 1L; + + @Column( name = "WORKSPACE_ID", nullable = false ) + private Long workspaceId; + + @Column( name = "FROM_UUID", nullable = false, updatable = false, length = 36 ) + private String fromUuidString; + + @Column( name = "TO_UUID", nullable = false, updatable = false, length = 36 ) + private String toUuidString; + + public ReferenceId() { + } + + public ReferenceId( Long workspaceId, + String fromUuid, + String toUuid ) { + this.workspaceId = workspaceId; + this.fromUuidString = fromUuid; + this.toUuidString = toUuid; + } + + /** + * @return fromUuidString + */ + public String getFromUuidString() { + return fromUuidString; + } + + /** + * @return toUuidString + */ + public String getToUuidString() { + return toUuidString; + } + + /** + * @return workspaceId + */ + public Long getWorkspaceId() { + return workspaceId; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return HashCode.compute(fromUuidString, toUuidString); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof ReferenceId) { + ReferenceId that = (ReferenceId)obj; + if (this.workspaceId == null) { + if (that.workspaceId != null) return false; + } else { + if (!this.workspaceId.equals(that.workspaceId)) return false; + } + if (this.fromUuidString == null) { + if (that.fromUuidString != null) return false; + } else { + if (!this.fromUuidString.equals(that.fromUuidString)) return false; + } + if (this.toUuidString == null) { + if (that.toUuidString != null) return false; + } else { + if (!this.toUuidString.equals(that.toUuidString)) return false; + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Reference from " + fromUuidString + " to " + toUuidString + " in workspace " + workspaceId; + } + +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\ReferenceId.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnection.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnection.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnection.java (revision 0) @@ -0,0 +1,121 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.util.concurrent.TimeUnit; +import javax.persistence.EntityManager; +import javax.transaction.xa.XAResource; +import net.jcip.annotations.NotThreadSafe; +import org.jboss.dna.common.statistic.Stopwatch; +import org.jboss.dna.common.util.Logger; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.cache.CachePolicy; +import org.jboss.dna.graph.connector.RepositoryConnection; +import org.jboss.dna.graph.connector.RepositorySourceException; +import org.jboss.dna.graph.observe.Observer; +import org.jboss.dna.graph.request.Request; +import org.jboss.dna.graph.request.processor.RequestProcessor; + +/** + * The repository connection to JPA repository sources that use the {@link SimpleModel simple model}. + */ +@NotThreadSafe +public class SimpleJpaConnection implements RepositoryConnection { + + private final SimpleJpaRepository repository; + private final JpaSource source; + private EntityManager entityManager; + + public SimpleJpaConnection( JpaSource source ) { + this.source = source; + + this.entityManager = source.getEntityManagers().checkout(); + this.entityManager.getTransaction().begin(); + this.repository = new SimpleJpaRepository(source.getName(), source.getRootUuid(), source.getDefaultWorkspaceName(), + source.getPredefinedWorkspaceNames(), entityManager, + source.getRepositoryContext().getExecutionContext(), source.isCompressData(), + source.isCreatingWorkspacesAllowed(), source.isReferentialIntegrityEnforced(), + source.getLargeValueSizeInBytes()); + } + + public boolean ping( long time, + TimeUnit unit ) { + return entityManager != null && entityManager.isOpen(); + } + + public CachePolicy getDefaultCachePolicy() { + return source.getCachePolicy(); + } + + public String getSourceName() { + return source.getName(); + } + + @Override + public XAResource getXAResource() { + return null; + } + + public void close() { + if (entityManager != null) { + try { + source.getEntityManagers().checkin(entityManager); + } finally { + entityManager = null; + } + } + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.RepositoryConnection#execute(org.jboss.dna.graph.ExecutionContext, + * org.jboss.dna.graph.request.Request) + */ + public void execute( ExecutionContext context, + Request request ) throws RepositorySourceException { + Logger logger = context.getLogger(getClass()); + Stopwatch sw = null; + if (logger.isTraceEnabled()) { + sw = new Stopwatch(); + sw.start(); + } + // Do any commands update/write? + Observer observer = this.source.getRepositoryContext().getObserver(); + RequestProcessor processor = new SimpleRequestProcessor(context, this.repository, observer); + + try { + // Obtain the lock and execute the commands ... + processor.process(request); + } finally { + processor.close(); + } + if (logger.isTraceEnabled()) { + assert sw != null; + sw.stop(); + logger.trace("MapRepositoryConnection.execute(...) took " + sw.getTotalDuration()); + } + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleJpaConnection.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaRepository.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaRepository.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaRepository.java (revision 0) @@ -0,0 +1,825 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.Query; +import net.jcip.annotations.NotThreadSafe; +import org.jboss.dna.common.util.IoUtil; +import org.jboss.dna.common.util.StringUtil; +import org.jboss.dna.connector.store.jpa.JpaConnectorI18n; +import org.jboss.dna.connector.store.jpa.model.common.WorkspaceEntity; +import org.jboss.dna.connector.store.jpa.util.Namespaces; +import org.jboss.dna.connector.store.jpa.util.Serializer; +import org.jboss.dna.connector.store.jpa.util.Workspaces; +import org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues; +import org.jboss.dna.graph.DnaLexicon; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Location; +import org.jboss.dna.graph.connector.LockFailedException; +import org.jboss.dna.graph.connector.map.AbstractMapWorkspace; +import org.jboss.dna.graph.connector.map.MapNode; +import org.jboss.dna.graph.connector.map.MapRepository; +import org.jboss.dna.graph.connector.map.MapWorkspace; +import org.jboss.dna.graph.property.Binary; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NameFactory; +import org.jboss.dna.graph.property.Path; +import org.jboss.dna.graph.property.PathFactory; +import org.jboss.dna.graph.property.Property; +import org.jboss.dna.graph.property.PropertyFactory; +import org.jboss.dna.graph.property.PropertyType; +import org.jboss.dna.graph.property.ValueFactories; +import org.jboss.dna.graph.property.Path.Segment; +import org.jboss.dna.graph.request.CompositeRequest; +import org.jboss.dna.graph.request.LockBranchRequest.LockScope; + +/** + * Implementation of {@link MapRepository} for the {@link SimpleModel Simple JPA connector model}. This class exposes a map of + * workspace names to {@link Workspace workspaces} and each workspace provides a logical mapping of node UUIDs to {@link JpaNode + * nodes}. The {@code JpaNode} class functions as an adapter between the {@link NodeEntity persistent entity for nodes} and the + * {@link MapNode map repository interface for nodes}. + *

+ * This class differs slightly from the other {@link MapRepository} implementations in that it exists only within the lifetime of + * a single {@link EntityManager} (which itself is opened and closed within the lifetime of a single {@link SimpleJpaConnection}. + * The other map repository implementations all outlive any particular connection and generally survive for the lifetime of the + * DNA server. + *

+ */ +public class SimpleJpaRepository extends MapRepository { + + private final EntityManager entityManager; + private final Workspaces workspaceEntities; + private final Namespaces namespaceEntities; + private final ExecutionContext context; + private final PathFactory pathFactory; + private final NameFactory nameFactory; + private final List predefinedWorkspaceNames; + private final boolean compressData; + private final boolean creatingWorkspacesAllowed; + private final long minimumSizeOfLargeValuesInBytes; + + // private final boolean referentialIntegrityEnforced; + + public SimpleJpaRepository( String sourceName, + UUID rootNodeUuid, + String defaultWorkspaceName, + String[] predefinedWorkspaceNames, + EntityManager entityManager, + ExecutionContext context, + boolean compressData, + boolean creatingWorkspacesAllowed, + boolean referentialIntegrityEnforced, + long minimumSizeOfLargeValuesInBytes ) { + super(sourceName, rootNodeUuid, defaultWorkspaceName); + + this.context = context; + ValueFactories valueFactories = context.getValueFactories(); + this.nameFactory = valueFactories.getNameFactory(); + this.pathFactory = valueFactories.getPathFactory(); + this.predefinedWorkspaceNames = Arrays.asList(predefinedWorkspaceNames); + this.compressData = compressData; + this.creatingWorkspacesAllowed = creatingWorkspacesAllowed; + // this.referentialIntegrityEnforced = referentialIntegrityEnforced; + this.minimumSizeOfLargeValuesInBytes = minimumSizeOfLargeValuesInBytes; + + this.entityManager = entityManager; + workspaceEntities = new Workspaces(entityManager); + namespaceEntities = new Namespaces(entityManager); + super.initialize(); + } + + public SimpleJpaRepository( String sourceName, + UUID rootNodeUuid, + EntityManager entityManager, + ExecutionContext context, + boolean compressData, + boolean creatingWorkspacesAllowed, + boolean referentialIntegrityEnforced, + long minimumSizeOfLargeValuesInBytes ) { + super(sourceName, rootNodeUuid); + + this.context = context; + ValueFactories valueFactories = context.getValueFactories(); + this.nameFactory = valueFactories.getNameFactory(); + this.pathFactory = valueFactories.getPathFactory(); + this.predefinedWorkspaceNames = Collections.emptyList(); + this.compressData = compressData; + this.creatingWorkspacesAllowed = creatingWorkspacesAllowed; + // this.referentialIntegrityEnforced = referentialIntegrityEnforced; + this.minimumSizeOfLargeValuesInBytes = minimumSizeOfLargeValuesInBytes; + + this.entityManager = entityManager; + workspaceEntities = new Workspaces(entityManager); + namespaceEntities = new Namespaces(entityManager); + super.initialize(); + } + + final EntityManager entityManager() { + return entityManager; + } + + /** + * @see org.jboss.dna.connector.store.jpa.JpaSource#isCreatingWorkspacesAllowed() + */ + final boolean creatingWorkspacesAllowed() { + return this.creatingWorkspacesAllowed; + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.MapRepository#createWorkspace(org.jboss.dna.graph.ExecutionContext, java.lang.String) + */ + @Override + protected MapWorkspace createWorkspace( ExecutionContext context, + String name ) { + + WorkspaceEntity entity = workspaceEntities.get(name, false); + + if (entity != null) { + return new Workspace(this, name, entity.getId().intValue()); + } + + entity = workspaceEntities.create(name); + + // Flush to ensure that the entity ID is set + entityManager.flush(); + + Workspace workspace = new Workspace(this, name, entity.getId().intValue()); + workspace.createRootNode(); + + return workspace; + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.MapRepository#getWorkspace(java.lang.String) + */ + @Override + public MapWorkspace getWorkspace( String name ) { + MapWorkspace workspace = super.getWorkspace(name); + if (workspace != null) return workspace; + + // There's no such workspace in the local cache, check if one exists in the DB + if (name == null) name = getDefaultWorkspaceName(); + WorkspaceEntity entity = workspaceEntities.get(name, false); + if (entity == null) { + if (this.predefinedWorkspaceNames.contains(name)) { + return createWorkspace(context, name); + } + + return null; + } + + return new Workspace(this, name, entity.getId()); + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.MapRepository#getWorkspaceNames() + */ + @Override + public Set getWorkspaceNames() { + Set workspaceNames = new HashSet(super.getWorkspaceNames()); + workspaceNames.addAll(predefinedWorkspaceNames); + + return workspaceNames; + } + + /** + * This class provides a logical mapping of UUIDs to {@link JpaNode nodes} within a named workspace. + *

+ * Like its enclosing class, this class only survives for the lifetime of a single request (which may be a + * {@link CompositeRequest}). + *

+ */ + protected class Workspace extends AbstractMapWorkspace { + private final long workspaceId; + private final Map nodesByPath = new HashMap(); + + public Workspace( MapRepository repository, + String name, + long workspaceId ) { + super(repository, name); + + this.workspaceId = workspaceId; + + // This gets called from the repository for this connector since the repository + // already knows whether this workspace existed in the database before this call. + // initialize(); + } + + void createRootNode() { + initialize(); + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.AbstractMapWorkspace#correctSameNameSiblingIndexes(org.jboss.dna.graph.ExecutionContext, org.jboss.dna.graph.connector.map.MapNode, org.jboss.dna.graph.property.Name) + */ + @Override + protected void correctSameNameSiblingIndexes( ExecutionContext context, + MapNode parentNode, + Name name ) { + int snsIndex = 1; + int parentIndex = 0; + List children = parentNode.getChildren(); + + for (MapNode child : children) { + NodeEntity childNode = ((JpaNode)child).entity; + if (parentIndex != childNode.getIndexInParent()) { + childNode.setIndexInParent(parentIndex); + } + + if (name.equals(child.getName().getName())) { + if (snsIndex != childNode.getSameNameSiblingIndex()) { + childNode.setSameNameSiblingIndex(snsIndex); + } + snsIndex++; + + } + parentIndex++; + } + + } + + /** + * Adds the given node to the persistent store, replacing any node already in the persistent store with the same UUID. + *

+ * Invoking this method causes a database INSERT statement to execute immediately. + *

+ * + * @param node the node to add to the persistent store; may not be null + */ + @Override + protected void addNodeToMap( MapNode node ) { + assert node != null; + + NodeEntity nodeEntity = ((JpaNode)node).entity; + nodeEntity.setWorkspaceId(this.workspaceId); + + entityManager.persist(nodeEntity); + } + + @Override + protected MapNode removeNodeFromMap( UUID nodeUuid ) { + throw new IllegalStateException("This code should be unreachable"); + } + + /** + * Removes the given node and its children from the persistent store using the + * {@link SubgraphQuery#deleteSubgraph(boolean) subgraph bulk delete method}. + * + * @param node the root of the branch to be removed + */ + @Override + protected void removeUuidReference( MapNode node ) { + SubgraphQuery branch = SubgraphQuery.create(context, entityManager, workspaceId, node.getUuid(), null, 0); + branch.deleteSubgraph(true); + branch.close(); + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.AbstractMapWorkspace#createMapNode(java.util.UUID) + */ + @Override + protected MapNode createMapNode( UUID uuid ) { + return new JpaNode(uuid); + } + + /** + * Removes all of the nodes in this workspace from the persistent store with a single query. + */ + @Override + protected void removeAllNodesFromMap() { + Query query = entityManager.createQuery("NodeEntity.deleteAllInWorkspace"); + query.setParameter("workspaceId", workspaceId); + query.executeUpdate(); + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.AbstractMapWorkspace#getNode(java.util.UUID) + */ + @Override + public JpaNode getNode( UUID nodeUuid ) { + assert nodeUuid != null; + + Query query = entityManager.createNamedQuery("NodeEntity.findByNodeUuid"); + query.setParameter("workspaceId", workspaceId); + query.setParameter("nodeUuidString", nodeUuid.toString()); + try { + // Find the parent of the UUID ... + NodeEntity result = (NodeEntity)query.getSingleResult(); + return new JpaNode(result); + } catch (NoResultException e) { + return null; + } + } + + /* + * (non-Javadoc) + * @see org.jboss.dna.graph.connector.map.AbstractMapWorkspace#getNode(org.jboss.dna.graph.property.Path) + */ + @Override + public MapNode getNode( Path path ) { + MapNode node = nodesByPath.get(path); + if (node != null) return node; + + node = super.getNode(path); + nodesByPath.put(path, node); + return node; + } + + /** + * Retrieves the branch of nodes rooted at the given location using the {@link SubgraphQuery#getNodes(boolean, boolean) + * subgraph bulk accessor method}. + * + * @param rootLocation the root of the branch of nodes to retrieve + * @param maximumDepth the maximum depth to retrieve; a negative number indicates that the entire branch should be + * retrieved + * @return the list of nodes in the branch rooted at {@code rootLocation} + */ + public List getBranch( Location rootLocation, + int maximumDepth ) { + assert rootLocation.getUuid() != null || rootLocation.getPath() != null; + UUID subgraphRootUuid = rootLocation.getUuid(); + + if (subgraphRootUuid == null) { + MapNode rootNode = getNode(rootLocation.getPath()); + subgraphRootUuid = rootNode.getUuid(); + assert subgraphRootUuid != null; + } + + Path subgraphRootPath = null; // Don't need the path for this + SubgraphQuery subgraph = SubgraphQuery.create(context, + entityManager, + workspaceId, + subgraphRootUuid, + subgraphRootPath, + maximumDepth); + + List entities = subgraph.getNodes(true, true); + List nodes = new ArrayList(entities.size()); + + for (NodeEntity entity : entities) { + nodes.add(new JpaNode(entity)); + } + + subgraph.close(); + + return nodes; + } + + /** + * This connector does not support connector-level, persistent locking of nodes. + */ + public void lockNode( MapNode node, + LockScope lockScope, + long lockTimeoutInMillis ) throws LockFailedException { + // Locking is not supported by this connector + } + + /** + * This connector does not support connector-level, persistent locking of nodes. + */ + public void unlockNode( MapNode node ) { + // Locking is not supported by this connector + } + + } + + /** + * Adapter between the {@link NodeEntity persistent entity for nodes} and the {@link MapNode map repository interface for + * nodes}. + */ + @NotThreadSafe + protected class JpaNode implements MapNode { + private final NodeEntity entity; + private Map properties = null; + + protected JpaNode( NodeEntity entity ) { + this.entity = entity; + } + + public JpaNode( UUID uuid ) { + this.entity = new NodeEntity(); + entity.setNodeUuidString(uuid.toString()); + } + + private final JpaNode jpaNodeFor( MapNode node ) { + if (!(node instanceof JpaNode)) { + throw new IllegalStateException(); + } + return (JpaNode)node; + } + + public void addChild( int index, + MapNode child ) { + entity.addChild(index, jpaNodeFor(child).entity); + } + + public void addChild( MapNode child ) { + entity.addChild(jpaNodeFor(child).entity); + } + + public List getChildren() { + List children = new ArrayList(entity.getChildren().size()); + + for (NodeEntity child : entity.getChildren()) { + children.add(new JpaNode(child)); + } + + return Collections.unmodifiableList(children); + } + + public Segment getName() { + return pathFactory.createSegment(nameFactory.create(entity.getChildNamespace().getUri(), entity.getChildName()), + entity.getSameNameSiblingIndex()); + } + + public MapNode getParent() { + if (entity.getParent() == null) return null; + return new JpaNode(entity.getParent()); + } + + private void ensurePropertiesLoaded() { + if (properties != null) return; + + Collection propsCollection = new LinkedList(); + + if (entity.getData() != null) { + Serializer serializer = new Serializer(context, true); + ObjectInputStream ois = null; + + try { + LargeValueSerializer largeValues = new LargeValueSerializer(entity); + ois = new ObjectInputStream(new ByteArrayInputStream(entity.getData())); + serializer.deserializeAllProperties(ois, propsCollection, largeValues); + + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } catch (ClassNotFoundException cnfe) { + throw new IllegalStateException(cnfe); + } finally { + try { + if (ois != null) ois.close(); + } catch (Exception ex) { + } + } + } + + PropertyFactory propertyFactory = context.getPropertyFactory(); + Map properties = new HashMap(); + properties.put(DnaLexicon.UUID, propertyFactory.create(DnaLexicon.UUID, getUuid())); + for (Property prop : propsCollection) { + properties.put(prop.getName(), prop); + } + + this.properties = properties; + } + + private void serializeProperties() { + Serializer serializer = new Serializer(context, true); + ObjectOutputStream oos = null; + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + + LargeValueSerializer largeValues = new LargeValueSerializer(entity); + // dna:uuid prop is in collection but won't be serialized + int numberOfPropertiesToSerialize = properties.size() - 1; + serializer.serializeProperties(oos, + numberOfPropertiesToSerialize, + properties.values(), + largeValues, + Serializer.NO_REFERENCES_VALUES); + oos.flush(); + entity.setData(baos.toByteArray()); + entity.setPropertyCount(properties.size()); + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } finally { + try { + if (oos != null) oos.close(); + } catch (Exception ignore) { + } + } + } + + @Override + public MapNode removeProperty( Name propertyName ) { + ensurePropertiesLoaded(); + + if (properties.containsKey(propertyName)) { + properties.remove(propertyName); + serializeProperties(); + } + return this; + } + + public Map getProperties() { + ensurePropertiesLoaded(); + return properties; + } + + public Property getProperty( ExecutionContext context, + String name ) { + return getProperty(context.getValueFactories().getNameFactory().create(name)); + } + + public Property getProperty( Name name ) { + ensurePropertiesLoaded(); + return properties.get(name); + } + + public Set getUniqueChildNames() { + List children = entity.getChildren(); + Set uniqueNames = new HashSet(children.size()); + + for (NodeEntity child : children) { + uniqueNames.add(nameFactory.create(child.getChildNamespace().getUri(), child.getChildName())); + } + + return uniqueNames; + } + + public UUID getUuid() { + if (entity.getNodeUuidString() == null) return null; + return UUID.fromString(entity.getNodeUuidString()); + } + + public boolean removeChild( MapNode child ) { + + /* + * The NodeEntity.equals method compares on the Hibernate identifier to avoid + * confusing Hibernate. However, different nodes can be loaded in the same + * session for the same UUID in the same workspace, forcing us to roll our own + * implementation of indexOf that tests for the equality of the NodeEntity UUIDs, + * rather than their Hibernate identifiers. + */ + List children = entity.getChildren(); + + int index = -1; + String childUuidString = jpaNodeFor(child).entity.getNodeUuidString(); + for (int i = 0; i < children.size(); i++) { + if (childUuidString.equals(children.get(i).getNodeUuidString())) { + index = i; + break; + } + } + + // int index = entity.getChildren().indexOf(jpaNodeFor(child).entity); + // assert entity.getChildren().contains(jpaNodeFor(child).entity); + if (index < 0) return false; + + entity.removeChild(index); + + assert !entity.getChildren().contains(child); + assert child.getParent() == null; + + return true; + } + + public void clearChildren() { + entity.getChildren().clear(); + } + + public void setName( Segment name ) { + entity.setChildNamespace(namespaceEntities.get(name.getName().getNamespaceUri(), true)); + // entity.setChildNamespace(NamespaceEntity.findByUri(entityManager, name.getName().getNamespaceUri(), true)); + entity.setChildName(name.getName().getLocalName()); + entity.setSameNameSiblingIndex(name.getIndex()); + } + + public void setParent( MapNode parent ) { + if (parent == null) { + entity.setParent(null); + } else { + entity.setParent(jpaNodeFor(parent).entity); + } + } + + public MapNode setProperty( ExecutionContext context, + String name, + Object... values ) { + PropertyFactory propertyFactory = context.getPropertyFactory(); + + return this.setProperty(propertyFactory.create(nameFactory.create(name), values)); + } + + public MapNode setProperty( Property property ) { + ensurePropertiesLoaded(); + + properties.put(property.getName(), property); + serializeProperties(); + + return this; + } + + public MapNode setProperties( Iterable properties ) { + ensurePropertiesLoaded(); + + for (Property property : properties) { + this.properties.put(property.getName(), property); + } + + serializeProperties(); + + return this; + } + + @Override + public String toString() { + if (entity.getNodeUuidString().equals(rootNodeUuid.toString())) return ""; + return getName().getString() + " (" + entity.getNodeUuidString() + ")"; + } + + @Override + public boolean equals( Object obj ) { + if (!(obj instanceof JpaNode)) return false; + + JpaNode other = (JpaNode)obj; + return entity.getNodeUuidString().equals(other.entity.getNodeUuidString()); + } + + @Override + public int hashCode() { + return entity.getNodeUuidString().hashCode(); + } + + } + + protected class LargeValueSerializer implements LargeValues { + private final NodeEntity node; + private final Set written; + + public LargeValueSerializer( NodeEntity entity ) { + this.node = entity; + this.written = null; + } + + public LargeValueSerializer( NodeEntity entity, + Set written ) { + this.node = entity; + this.written = written; + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize() + */ + public long getMinimumSize() { + return minimumSizeOfLargeValuesInBytes; + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories, + * byte[], long) + */ + public Object read( ValueFactories valueFactories, + byte[] hash, + long length ) throws IOException { + String hashStr = StringUtil.getHexString(hash); + // Find the large value ... + LargeValueEntity entity = entityManager.find(LargeValueEntity.class, hashStr); + if (entity != null) { + // Find the large value from the existing property entity ... + byte[] data = entity.getData(); + if (entity.isCompressed()) { + InputStream stream = new GZIPInputStream(new ByteArrayInputStream(data)); + try { + data = IoUtil.readBytes(stream); + } finally { + stream.close(); + } + } + return valueFactories.getValueFactory(entity.getType()).create(data); + } + throw new IOException(JpaConnectorI18n.unableToReadLargeValue.text(getSourceName(), hashStr)); + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#write(byte[], long, + * org.jboss.dna.graph.property.PropertyType, java.lang.Object) + */ + public void write( byte[] hash, + long length, + PropertyType type, + Object value ) throws IOException { + if (value == null) return; + String hashStr = StringUtil.getHexString(hash); + if (written != null) written.add(hashStr); + + // Look for an existing value in the collection ... + for (LargeValueEntity existing : node.getLargeValues()) { + if (existing.getHash().equals(hashStr)) { + // Already associated with this properties entity + return; + } + } + LargeValueEntity entity = entityManager.find(LargeValueEntity.class, hashStr); + if (entity == null) { + // We have to create the large value entity ... + entity = new LargeValueEntity(); + entity.setCompressed(compressData); + entity.setHash(hashStr); + entity.setLength(length); + entity.setType(type); + ValueFactories factories = context.getValueFactories(); + byte[] bytes = null; + switch (type) { + case BINARY: + Binary binary = factories.getBinaryFactory().create(value); + InputStream stream = null; + try { + binary.acquire(); + stream = binary.getStream(); + if (compressData) stream = new GZIPInputStream(stream); + bytes = IoUtil.readBytes(stream); + } finally { + try { + if (stream != null) stream.close(); + } finally { + binary.release(); + } + } + break; + case URI: + // This will be treated as a string ... + default: + String str = factories.getStringFactory().create(value); + if (compressData) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + OutputStream strStream = new GZIPOutputStream(bs); + try { + IoUtil.write(str, strStream); + } finally { + strStream.close(); + } + bytes = bs.toByteArray(); + } else { + bytes = str.getBytes(); + } + break; + } + entity.setData(bytes); + entityManager.persist(entity); + } + // Now associate the large value with the properties entity ... + assert entity.getHash() != null; + node.getLargeValues().add(entity); + } + + } + +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleJpaRepository.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleModel.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleModel.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleModel.java (revision 0) @@ -0,0 +1,115 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import org.hibernate.ejb.Ejb3Configuration; +import org.jboss.dna.connector.store.jpa.JpaConnectorI18n; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.connector.store.jpa.Model; +import org.jboss.dna.connector.store.jpa.model.common.NamespaceEntity; +import org.jboss.dna.connector.store.jpa.model.common.WorkspaceEntity; +import org.jboss.dna.graph.connector.RepositoryConnection; +import org.jboss.dna.graph.request.CopyBranchRequest; +import org.jboss.dna.graph.request.DeleteBranchRequest; +import org.jboss.dna.graph.request.MoveBranchRequest; +import org.jboss.dna.graph.request.ReadBranchRequest; + +/** + * Database model that stores each node (transparently) and its properties (opaquely) in a single row. Large property values are + * stored separately and can be shared between nodes. + *

+ * The set of tables used in this model includes: + *

    + *
  • Namespaces - the set of namespace URIs used in paths, property names, and property values.
  • + *
  • Nodes - each node along with its name, seralized properties, parent, UUID, and position within its parent. This approach + * makes it possible to efficiently work with nodes containing large numbers of children, where adding and removing child nodes is + * largely independent of the number of children. Also, working with properties is also completely independent of the number of + * child nodes.
  • + *
  • Large values - property values larger than a certain size will be broken out into this table, where they are tracked by + * their SHA-1 hash and shared by all properties that have that same value. The values are stored in a binary (and optionally + * compressed) form.
  • + *
  • ReferenceChanges - the references from one node to another
  • + *
  • Subgraph - a working area for efficiently computing the space of a subgraph; see below
  • + *
  • Change log - a record of the changes that have been made to the repository. This is used to distribute change events across + * multiple distributed processes, and to allow a recently-connected client to identify the set of changes that have been made + * since a particular time or date. Changes are serialized into a binary, compressed format.
  • + *
  • Options - the parameters for this store's configuration (common to all models)
  • + *
+ *

+ *

Subgraph queries

+ *

+ * This database model contains two tables that are used in an efficient mechanism to find all of the nodes in the subgraph below + * a certain node. This process starts by creating a record for the subgraph query, and then proceeds by executing a join to find + * all the children of the top-level node, and inserting them into the database (in a working area associated with the subgraph + * query). Then, another join finds all the children of those children and inserts them into the same working area. This continues + * until the maximum depth has been reached, or until there are no more children (whichever comes first). All of the nodes in the + * subgraph are then represented by records in the working area, and can be used to quickly and efficient work with the subgraph + * nodes. When finished, the mechanism deletes the records in the working area associated with the subgraph query. + *

+ *

+ * This subgraph query mechanism is extremely efficient, performing one join/insert statement per level of the subgraph, + * and is completely independent of the number of nodes in the subgraph. For example, consider a subgraph of node A, where A has + * 10 children, and each child contains 10 children, and each grandchild contains 10 children. This subgraph has a total of 1111 + * nodes (1 root + 10 children + 10*10 grandchildren + 10*10*10 great-grandchildren). Finding the nodes in this subgraph would + * normally require 1 query per node (in other words, 1111 queries). But with this subgraph query mechanism, all of the nodes in + * the subgraph can be found with 1 insert plus 4 additional join/inserts. + *

+ *

+ * This mechanism has the added benefit that the set of nodes in the subgraph are kept in a working area in the database, meaning + * they don't have to be pulled into memory. + *

+ *

+ * Subgraph queries are used to efficiently process a number of different requests, including {@link ReadBranchRequest}, + * {@link DeleteBranchRequest}, {@link MoveBranchRequest}, and {@link CopyBranchRequest}. Processing each of these kinds of + * requests requires knowledge of the subgraph, and in fact all but the ReadBranchRequest need to know the complete + * subgraph. + *

+ */ +public class SimpleModel extends Model { + + public SimpleModel() { + super("Simple", JpaConnectorI18n.simpleModelDescription); + } + + /** + * Configure the entity class that will be used by JPA to store information in the database. + * + * @param configurator the Hibernate {@link Ejb3Configuration} component; never null + */ + @Override + public void configure( Ejb3Configuration configurator ) { + // Add the annotated classes ... + configurator.addAnnotatedClass(WorkspaceEntity.class); + configurator.addAnnotatedClass(NamespaceEntity.class); + configurator.addAnnotatedClass(LargeValueEntity.class); + configurator.addAnnotatedClass(NodeEntity.class); + configurator.addAnnotatedClass(SubgraphNodeEntity.class); + configurator.addAnnotatedClass(SubgraphQueryEntity.class); + } + + @Override + public RepositoryConnection createConnection( JpaSource source ) { + return new SimpleJpaConnection(source); + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleModel.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleRequestProcessor.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleRequestProcessor.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleRequestProcessor.java (revision 0) @@ -0,0 +1,151 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.persistence.EntityTransaction; +import org.jboss.dna.connector.store.jpa.JpaConnectorI18n; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Location; +import org.jboss.dna.graph.connector.map.MapNode; +import org.jboss.dna.graph.connector.map.MapRequestProcessor; +import org.jboss.dna.graph.observe.Observer; +import org.jboss.dna.graph.property.PathFactory; +import org.jboss.dna.graph.request.CloneWorkspaceRequest; +import org.jboss.dna.graph.request.CreateWorkspaceRequest; +import org.jboss.dna.graph.request.InvalidRequestException; +import org.jboss.dna.graph.request.ReadBranchRequest; +import org.jboss.dna.graph.request.processor.RequestProcessor; +import com.google.common.collect.LinkedListMultimap; + +/** + * Extension of the {@link MapRequestProcessor} that provides a {@link #process(ReadBranchRequest)} implementation optimized for + * the {@link SimpleModel simple JPA model}. This class also provides some JPA-specific functionality for the ability to control + * whether {@link JpaSource#isCreatingWorkspacesAllowed() creating workspaces is allowed}. + */ +public class SimpleRequestProcessor extends MapRequestProcessor { + + private final SimpleJpaRepository repository; + private final PathFactory pathFactory; + + public SimpleRequestProcessor( ExecutionContext context, + SimpleJpaRepository repository, + Observer observer ) { + super(context, repository, observer); + + this.repository = repository; + this.pathFactory = context.getValueFactories().getPathFactory(); + } + + @Override + public void close() { + EntityTransaction tx = repository.entityManager().getTransaction(); + if (tx != null) { + tx.commit(); + } + super.close(); + } + + /** + * Override the {@link RequestProcessor#process(ReadBranchRequest) default handling} for a read branch request to optimize the + * queries involved. + * + * @param request the request to read + */ + @Override + public void process( ReadBranchRequest request ) { + SimpleJpaRepository.Workspace workspace = (SimpleJpaRepository.Workspace)getWorkspace(request, request.inWorkspace()); + + int maximumDepth = request.maximumDepth(); + List branch = workspace.getBranch(request.at(), maximumDepth); + + if (!branch.isEmpty()) { + Map locations = new HashMap(branch.size()); + + /* + * Add the first (root) node to the request + */ + MapNode root = branch.get(0); + Location rootLocation = getActualLocation(request.at(), root); + request.setActualLocationOfNode(rootLocation); + locations.put(root.getUuid(), new LocationWithDepth(rootLocation, 0)); + + /* + * The obvious thing to do here would be to call root.getChildren(), but that would + * result in the JPA implementation running an extra query to load the collection of + * children for the entity even though we've already loaded all of the children + * with the call to workspace.getBranch(...) earlier. + * + * We'll build the list of children ourselves knowing that all children are in the result set. + * + * The concrete type is used in the variable declaration instead of the relevant interface + * (Multimap) because we need to cast the result of a .get(UUID) operation + * to a List below and the interface only guarantees a Collection. + */ + LinkedListMultimap childrenByParentUuid = LinkedListMultimap.create(); + + /* + * We don't want to process the root node (the first node) in this loop + * as this would cause us to unnecessarily load the root node's parent node. + */ + for (int i = 1; i < branch.size(); i++) { + MapNode node = branch.get(i); + UUID parentUuid = node.getParent().getUuid(); + + LocationWithDepth parentLocation = locations.get(parentUuid); + Location nodeLocation = locationFor(parentLocation.getLocation(), node); + locations.put(node.getUuid(), new LocationWithDepth(nodeLocation, parentLocation.getDepth() + 1)); + + childrenByParentUuid.put(parentUuid, locationFor(locations.get(parentUuid).getLocation(), node)); + } + + request.setChildren(rootLocation, childrenByParentUuid.get(root.getUuid())); + request.setProperties(rootLocation, root.getProperties().values()); + + /* + * Process the subsequent nodes + */ + for (int i = 1; i < branch.size(); i++) { + MapNode node = branch.get(i); + + UUID nodeUuid = node.getUuid(); + LocationWithDepth nodeLocation = locations.get(nodeUuid); + if (nodeLocation.getDepth() < maximumDepth) { + request.setChildren(nodeLocation.getLocation(), childrenByParentUuid.get(nodeUuid)); + request.setProperties(nodeLocation.getLocation(), node.getProperties().values()); + } + } + } + + setCacheableInfo(request); + } + + private Location locationFor( Location parentLocation, + MapNode node ) { + return Location.create(pathFactory.create(parentLocation.getPath(), node.getName()), node.getUuid()); + } + + @Override + public void process( CreateWorkspaceRequest request ) { + if (!repository.creatingWorkspacesAllowed()) { + String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName()); + request.setError(new InvalidRequestException(msg)); + return; + } + + super.process(request); + } + + @Override + public void process( CloneWorkspaceRequest request ) { + if (!repository.creatingWorkspacesAllowed()) { + String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName()); + request.setError(new InvalidRequestException(msg)); + return; + } + + super.process(request); + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleRequestProcessor.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphNodeEntity.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphNodeEntity.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphNodeEntity.java (revision 0) @@ -0,0 +1,175 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import org.hibernate.annotations.Index; + +/** + * Represents a single node that appears in a subgraph. + * + * @see SubgraphQueryEntity + */ +@Entity +@org.hibernate.annotations.Table( appliesTo = "DNA_SUBGRAPH_NODES", indexes = @Index( name = "QUERYID_INX", columnNames = { + "QUERY_ID", "UUID", "DEPTH"} ) ) +@Table( name = "DNA_SUBGRAPH_NODES" ) +@NamedQueries( { + @NamedQuery( name = "SubgraphNodeEntity.insertChildren", query = "insert into SubgraphNodeEntity(queryId,nodeUuid,depth,parentIndexInParent,indexInParent) select parentNode.queryId, child.nodeUuidString, parentNode.depth+1, parentNode.indexInParent, child.indexInParent from NodeEntity child, SubgraphNodeEntity parentNode where child.workspaceId = :workspaceId and child.parent.nodeUuidString = parentNode.nodeUuid and parentNode.queryId = :queryId and parentNode.depth = :parentDepth" ), + @NamedQuery( name = "SubgraphNodeEntity.getCount", query = "select count(*) from SubgraphNodeEntity where queryId = :queryId" ), + @NamedQuery( name = "SubgraphNodeEntity.getNodeEntitiesWithLargeValues", query = "select props from NodeEntity props, SubgraphNodeEntity node where props.workspaceId = :workspaceId and props.nodeUuidString = node.nodeUuid and node.queryId = :queryId and node.depth >= :depth and size(props.largeValues) > 0" ), + @NamedQuery( name = "SubgraphNodeEntity.getChildEntities", query = "select child from NodeEntity child, SubgraphNodeEntity node where child.workspaceId = :workspaceId and child.nodeUuidString = node.nodeUuid and node.queryId = :queryId and node.depth >= :depth and node.depth <= :maxDepth order by node.depth, node.parentIndexInParent, node.indexInParent" ), + // @NamedQuery( name = "SubgraphNodeEntity.getInternalReferences", query = + // "select ref from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.toUuidString in ( select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId) and ref.id.fromUuidString in (select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId)" + // ), + // @NamedQuery( name = "SubgraphNodeEntity.getOutwardReferences", query = + // "select ref from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.toUuidString not in ( select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId) and ref.id.fromUuidString in (select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId)" + // ), + // @NamedQuery( name = "SubgraphNodeEntity.getInwardReferences", query = + // "select ref from ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.toUuidString in ( select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId) and ref.id.fromUuidString not in (select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId)" + // ), + @NamedQuery( name = "SubgraphNodeEntity.clearParentReferences", query = "update NodeEntity child set child.parent = null where child.workspaceId = :workspaceId and child.nodeUuidString in ( select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId and node.depth >= :depth )" ), + @NamedQuery( name = "SubgraphNodeEntity.deleteChildEntities", query = "delete NodeEntity child where child.workspaceId = :workspaceId and child.nodeUuidString in ( select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId and node.depth >= :depth )" ), + // @NamedQuery( name = "SubgraphNodeEntity.deleteReferences", query = + // "delete ReferenceEntity as ref where ref.id.workspaceId = :workspaceId and ref.id.fromUuidString in ( select node.nodeUuid from SubgraphNodeEntity node where node.queryId = :queryId )" + // ), + @NamedQuery( name = "SubgraphNodeEntity.deleteByQueryId", query = "delete SubgraphNodeEntity where queryId = :queryId" )} ) +public class SubgraphNodeEntity { + + @Id + @Column( name = "ID" ) + @GeneratedValue( strategy = GenerationType.AUTO ) + private Long id; + + @Column( name = "QUERY_ID", nullable = false, unique = false, updatable = false ) + private Long queryId; + + @Column( name = "UUID", updatable = false, nullable = false, length = 36 ) + private String nodeUuid; + + @Column( name = "DEPTH", updatable = false, nullable = false ) + private int depth; + + @Column( name = "PARENT_NUM", updatable = false, nullable = false ) + private int parentIndexInParent; + + @Column( name = "CHILD_NUM", updatable = false, nullable = false ) + private int indexInParent; + + public SubgraphNodeEntity() { + } + + public SubgraphNodeEntity( Long queryId, + String nodeUuid, + int depth ) { + this.queryId = queryId; + this.nodeUuid = nodeUuid; + this.depth = depth; + } + + /** + * @return id + */ + public Long getId() { + return id; + } + + /** + * @return depth + */ + public int getDepth() { + return depth; + } + + /** + * @return nodeUuid + */ + public String getNodeUuid() { + return nodeUuid; + } + + /** + * @return queryId + */ + public Long getQueryId() { + return queryId; + } + + /** + * @return indexInParent + */ + public int getIndexInParent() { + return indexInParent; + } + + /** + * @return parentIndexInParent + */ + public int getParentIndexInParent() { + return parentIndexInParent; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return id != null ? id.intValue() : 0; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof SubgraphNodeEntity) { + SubgraphNodeEntity that = (SubgraphNodeEntity)obj; + if (this.id.equals(that.id)) return true; + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "" + id + " - Query " + queryId + "; depth=" + depth + "; node=" + nodeUuid + " at index " + indexInParent; + } + +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SubgraphNodeEntity.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphQuery.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphQuery.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphQuery.java (revision 0) @@ -0,0 +1,388 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.Query; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.property.Path; + +/** + * Represents a temporary working area for a query that efficiently retrieves the nodes in a subgraph. This class uses the + * database to build up the content of the subgraph, and therefore requires write privilege on the database. The benefit is that + * it minimizes the amount of memory required to process the subgraph, plus the set of nodes that make up the subgraph can be + * produced with database joins. + *

+ * The use of database joins also produces another benefit: the number of SQL statements necessary to build the set of nodes in a + * subgraph is equal to the depth of the subgraph, regardless of the number of child nodes at any level. + *

+ */ +public class SubgraphQuery { + + /** + * Create a query that returns a subgraph at and below the node with the supplied path and the supplied UUID. + * + * @param context the execution context; may not be null + * @param entities the entity manager; may not be null + * @param workspaceId the ID of the workspace; may not be null + * @param subgraphRootUuid the UUID (in string form) of the root node in the subgraph + * @param subgraphRootPath the path of the root node in the subgraph + * @param maxDepth the maximum depth of the subgraph, or 0 if there is no maximum depth + * @return the object representing the subgraph + */ + public static SubgraphQuery create( ExecutionContext context, + EntityManager entities, + Long workspaceId, + UUID subgraphRootUuid, + Path subgraphRootPath, + int maxDepth ) { + assert entities != null; + assert subgraphRootUuid != null; + assert workspaceId != null; + assert maxDepth >= 0; + if (maxDepth == 0) maxDepth = Integer.MAX_VALUE; + final String subgraphRootUuidString = subgraphRootUuid.toString(); + // Create a new subgraph query, and add a child for the root ... + + SubgraphQueryEntity query = new SubgraphQueryEntity(workspaceId, subgraphRootUuidString); + entities.persist(query); + Long queryId = query.getId(); + + try { + // Insert a node for the root (this will be the starting point for the recursive operation) ... + SubgraphNodeEntity root = new SubgraphNodeEntity(queryId, subgraphRootUuidString, 0); + entities.persist(root); + + // Now add the children by inserting the children, one level at a time. + // Note that we do this for the root, and for each level until 1 BEYOND + // the max depth (so that we can get the children for the nodes that are + // at the maximum depth)... + Query statement = entities.createNamedQuery("SubgraphNodeEntity.insertChildren"); + int numChildrenInserted = 0; + int parentLevel = 0; + while (parentLevel <= maxDepth) { + // Insert the children of the next level by inserting via a select (join) of the children + statement.setParameter("queryId", queryId); + statement.setParameter("workspaceId", workspaceId); + statement.setParameter("parentDepth", parentLevel); + numChildrenInserted = statement.executeUpdate(); + if (numChildrenInserted == 0) break; + parentLevel = parentLevel + 1; + } + } catch (RuntimeException t) { + // Clean up the search and results ... + try { + Query search = entities.createNamedQuery("SubgraphNodeEntity.deleteByQueryId"); + search.setParameter("queryId", query.getId()); + search.executeUpdate(); + } finally { + entities.remove(query); + } + throw t; + } + + return new SubgraphQuery(context, entities, workspaceId, query, subgraphRootPath, maxDepth); + } + + // private final ExecutionContext context; + private final EntityManager manager; + private final Long workspaceId; + private SubgraphQueryEntity query; + private final int maxDepth; + private final Path subgraphRootPath; + + protected SubgraphQuery( ExecutionContext context, + EntityManager manager, + Long workspaceId, + SubgraphQueryEntity query, + Path subgraphRootPath, + int maxDepth ) { + assert manager != null; + assert query != null; + assert context != null; + // assert subgraphRootPath != null; + assert workspaceId != null; + // this.context = context; + this.manager = manager; + this.workspaceId = workspaceId; + this.query = query; + this.maxDepth = maxDepth; + this.subgraphRootPath = subgraphRootPath; + } + + /** + * @return maxDepth + */ + public int getMaxDepth() { + return maxDepth; + } + + /** + * @return manager + */ + public EntityManager getEntityManager() { + return manager; + } + + /** + * @return subgraphRootPath + */ + public Path getSubgraphRootPath() { + return subgraphRootPath; + } + + /** + * @return query + */ + public SubgraphQueryEntity getSubgraphQueryEntity() { + if (query == null) throw new IllegalStateException(); + return query; + } + + public int getNodeCount( boolean includeRoot ) { + if (query == null) throw new IllegalStateException(); + // Now query for all the nodes and put into a list ... + Query search = manager.createNamedQuery("SubgraphNodeEntity.getCount"); + search.setParameter("queryId", query.getId()); + + // Now process the nodes below the subgraph's root ... + try { + return ((Long)search.getSingleResult()).intValue() - (includeRoot ? 0 : 1); + } catch (NoResultException e) { + return 0; + } + } + + /** + * Get the {@link NodeEntity root node} of the subgraph. This must be called before the query is {@link #close() closed}. + * + * @return the subgraph's root nodes + */ + public NodeEntity getNode() { + // Now query for all the nodes and put into a list ... + Query search = manager.createNamedQuery("SubgraphNodeEntity.getChildEntities"); + search.setParameter("queryId", query.getId()); + search.setParameter("workspaceId", workspaceId); + search.setParameter("depth", 0); + search.setParameter("maxDepth", 0); + + // Now process the nodes below the subgraph's root ... + return (NodeEntity)search.getSingleResult(); + } + + /** + * Get the {@link NodeEntity nodes} in the subgraph. This must be called before the query is {@link #close() closed}. + * + * @param includeRoot true if the subgraph's root node is to be included, or false otherwise + * @param includeChildrenOfMaxDepthNodes true if the method is to include nodes that are children of nodes that are at the + * maximum depth, or false if only nodes up to the maximum depth are to be included + * @return the list of nodes, in breadth-first order + */ + @SuppressWarnings( "unchecked" ) + public List getNodes( boolean includeRoot, + boolean includeChildrenOfMaxDepthNodes ) { + if (query == null) throw new IllegalStateException(); + // Now query for all the nodes and put into a list ... + Query search = manager.createNamedQuery("SubgraphNodeEntity.getChildEntities"); + search.setParameter("queryId", query.getId()); + search.setParameter("workspaceId", workspaceId); + search.setParameter("depth", includeRoot ? 0 : 1); + search.setParameter("maxDepth", includeChildrenOfMaxDepthNodes ? maxDepth : maxDepth - 1); + + // Now process the nodes below the subgraph's root ... + return search.getResultList(); + } + + /** + * Get the {@link Location} for each of the nodes in the subgraph. This must be called before the query is {@link #close() + * closed}. + *

+ * This method calls {@link #getNodes(boolean,boolean)}. Therefore, calling {@link #getNodes(boolean,boolean)} and this method + * for the same subgraph is not efficient; consider just calling {@link #getNodes(boolean,boolean)} alone. + *

+ * + * @param includeRoot true if the properties for the subgraph's root node are to be included, or false otherwise + * @param includeChildrenOfMaxDepthNodes true if the method is to include nodes that are children of nodes that are at the + * maximum depth, or false if only nodes up to the maximum depth are to be included + * @return the list of {@link Location locations}, one for each of the nodes in the subgraph, in breadth-first order + */ + // public List getNodeLocations( boolean includeRoot, + // boolean includeChildrenOfMaxDepthNodes ) { + // if (query == null) throw new IllegalStateException(); + // // Set up a map of the paths to the nodes, keyed by UUIDs. This saves us from having to build + // // the paths every time ... + // Map pathByUuid = new HashMap(); + // LinkedList locations = new LinkedList(); + // String subgraphRootUuid = query.getRootUuid(); + // pathByUuid.put(subgraphRootUuid, subgraphRootPath); + // UUID uuid = UUID.fromString(subgraphRootUuid); + // if (includeRoot) { + // locations.add(Location.create(subgraphRootPath, uuid)); + // } + // + // // Now iterate over the child nodes in the subgraph (we've already included the root) ... + // final PathFactory pathFactory = context.getValueFactories().getPathFactory(); + // final NameFactory nameFactory = context.getValueFactories().getNameFactory(); + // for (ChildEntity entity : getNodes(false, includeChildrenOfMaxDepthNodes)) { + // String parentUuid = entity.getParentUuidString(); + // Path parentPath = pathByUuid.get(parentUuid); + // assert parentPath != null; + // String nsUri = entity.getChildNamespace().getUri(); + // String localName = entity.getChildName(); + // int sns = entity.getSameNameSiblingIndex(); + // Name childName = nameFactory.create(nsUri, localName); + // Path childPath = pathFactory.create(parentPath, childName, sns); + // String childUuid = entity.getId().getChildUuidString(); + // pathByUuid.put(childUuid, childPath); + // uuid = UUID.fromString(childUuid); + // locations.add(Location.create(childPath, uuid)); + // + // } + // return locations; + // } + + /** + * Get the list of references that are owned by nodes within the subgraph and that point to other nodes in this same + * subgraph. This set of references is important in copying a subgraph, since all intra-subgraph references in the + * original subgraph must also be intra-subgraph references in the copy. + * + * @return the list of references completely contained by this subgraphs + */ + @SuppressWarnings( "unchecked" ) + public List getInternalReferences() { + Query references = manager.createNamedQuery("SubgraphNodeEntity.getInternalReferences"); + references.setParameter("queryId", query.getId()); + references.setParameter("workspaceId", workspaceId); + return references.getResultList(); + } + + /** + * Get the list of references that are owned by nodes within the subgraph and that point to nodes not in this same + * subgraph. This set of references is important in copying a subgraph. + * + * @return the list of references that are owned by the subgraph but that point to nodes outside of the subgraph + */ + @SuppressWarnings( "unchecked" ) + public List getOutwardReferences() { + Query references = manager.createNamedQuery("SubgraphNodeEntity.getOutwardReferences"); + references.setParameter("queryId", query.getId()); + references.setParameter("workspaceId", workspaceId); + return references.getResultList(); + } + + /** + * Get the list of references that are owned by nodes outside of the subgraph that point to nodes in this + * subgraph. This set of references is important in deleting nodes, since such references prevent the deletion of the + * subgraph. + * + * @return the list of references that are no longer valid + */ + @SuppressWarnings( "unchecked" ) + public List getInwardReferences() { + // Verify referential integrity: that none of the deleted nodes are referenced by nodes not being deleted. + Query references = manager.createNamedQuery("SubgraphNodeEntity.getInwardReferences"); + references.setParameter("queryId", query.getId()); + references.setParameter("workspaceId", workspaceId); + return references.getResultList(); + } + + /** + * Delete the nodes in the subgraph. This method first does not check for referential integrity (see + * {@link #getInwardReferences()}). + * + * @param includeRoot true if the root node should also be deleted + */ + @SuppressWarnings( "unchecked" ) + public void deleteSubgraph( boolean includeRoot ) { + if (query == null) throw new IllegalStateException(); + + List nodes = getNodes(true, true); + List uuids = new ArrayList(nodes.size()); + for (NodeEntity node : nodes) { + uuids.add(node.getNodeUuidString()); + } + + // Delete the LargeValueEntities ... + Query withLargeValues = manager.createNamedQuery("SubgraphNodeEntity.getNodeEntitiesWithLargeValues"); + withLargeValues.setParameter("queryId", query.getId()); + withLargeValues.setParameter("depth", includeRoot ? 0 : 1); + withLargeValues.setParameter("workspaceId", workspaceId); + List nodesWithLargeValues = withLargeValues.getResultList(); + if (nodesWithLargeValues.size() != 0) { + for (NodeEntity node : nodesWithLargeValues) { + node.getLargeValues().clear(); + } + manager.flush(); + } + + // Delete the ChildEntities ... + Query delete = manager.createNamedQuery("SubgraphNodeEntity.clearParentReferences"); + delete.setParameter("queryId", query.getId()); + delete.setParameter("depth", includeRoot ? 0 : 1); + delete.setParameter("workspaceId", workspaceId); + delete.executeUpdate(); + + delete = manager.createNamedQuery("SubgraphNodeEntity.deleteChildEntities"); + delete.setParameter("queryId", query.getId()); + delete.setParameter("depth", includeRoot ? 0 : 1); + delete.setParameter("workspaceId", workspaceId); + delete.executeUpdate(); + + // Delete references ... + // delete = manager.createNamedQuery("SubgraphNodeEntity.deleteReferences"); + // delete.setParameter("queryId", query.getId()); + // delete.setParameter("depth", includeRoot ? 0 : 1); + // delete.setParameter("workspaceId", workspaceId); + // delete.executeUpdate(); + + // Delete unused large values ... + LargeValueEntity.deleteUnused(manager); + + manager.flush(); + } + + /** + * Close this query object and clean up all in-database records associated with this query. This method must be called + * when this query is no longer needed, and once it is called, this subgraph query is no longer usable. + */ + public void close() { + if (query == null) return; + // Clean up the search and results ... + try { + Query search = manager.createNamedQuery("SubgraphNodeEntity.deleteByQueryId"); + search.setParameter("queryId", query.getId()); + search.executeUpdate(); + } finally { + try { + manager.remove(query); + } finally { + query = null; + } + } + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SubgraphQuery.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphQueryEntity.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphQueryEntity.java (revision 0) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/model/simple/SubgraphQueryEntity.java (revision 0) @@ -0,0 +1,75 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.connector.store.jpa.model.simple; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +/** + * Represents a temporary working area for a query that retrieves the nodes in a subgraph. + */ +@Entity( name = "DNA_SUBGRAPH_QUERIES" ) +public class SubgraphQueryEntity { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + @Column( name = "ID", updatable = false ) + private Long id; + + @Column( name = "WORKSPACE_ID", nullable = false ) + private Long workspaceId; + + @Column( name = "ROOT_UUID", updatable = false, nullable = false, length = 36 ) + private String rootUuid; + + public SubgraphQueryEntity( Long workspaceId, + String rootUuid ) { + this.rootUuid = rootUuid; + this.workspaceId = workspaceId; + } + + /** + * @return id + */ + public Long getId() { + return id; + } + + /** + * @return rootUuid + */ + public String getRootUuid() { + return rootUuid; + } + + /** + * @return workspaceId + */ + public Long getWorkspaceId() { + return workspaceId; + } +} Property changes on: extensions\dna-connector-store-jpa\src\main\java\org\jboss\dna\connector\store\jpa\model\simple\SubgraphQueryEntity.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/util/Serializer.java =================================================================== --- extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/util/Serializer.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/main/java/org/jboss/dna/connector/store/jpa/util/Serializer.java (working copy) @@ -786,7 +786,7 @@ // Read the length of the content ... long binaryLength = stream.readLong(); byte[] content = new byte[(int)binaryLength]; - stream.read(content); + stream.readFully(content, 0, content.length); if (!skip) { value = valueFactories.getBinaryFactory().create(content); } @@ -796,7 +796,7 @@ // Read the hash ... int hashLength = stream.readInt(); byte[] hash = new byte[hashLength]; - stream.read(hash); + stream.readFully(hash, 0, hashLength); // Read the length of the content ... long length = stream.readLong(); if (skip) { Index: extensions/dna-connector-store-jpa/src/main/resources/org/jboss/dna/connector/store/jpa/JpaConnectorI18n.properties =================================================================== --- extensions/dna-connector-store-jpa/src/main/resources/org/jboss/dna/connector/store/jpa/JpaConnectorI18n.properties (revision 1382) +++ extensions/dna-connector-store-jpa/src/main/resources/org/jboss/dna/connector/store/jpa/JpaConnectorI18n.properties (working copy) @@ -43,3 +43,4 @@ connectionIsNoLongerOpen = This connection for source {0} has already been closed basicModelDescription = Database model that stores node properties as opaque records and children as transparent records. Large property values are stored separately. +simpleModelDescription = Database model that stores nodes (transparently) and their properties (opaquely) in the same row. Large property values are stored separately. Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/JpaConnectorCreateWorkspacesTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/JpaConnectorCreateWorkspacesTest.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/JpaConnectorCreateWorkspacesTest.java (working copy) @@ -30,7 +30,6 @@ import org.jboss.dna.common.statistic.Stopwatch; import org.jboss.dna.graph.Graph; import org.jboss.dna.graph.Workspace; -import org.jboss.dna.graph.connector.RepositorySource; import org.jboss.dna.graph.connector.test.WorkspaceConnectorTest; import org.junit.Test; @@ -38,32 +37,13 @@ * These tests verify that the JPA connector behaves correctly when the source is configured to * {@link JpaSource#setCreatingWorkspacesAllowed(boolean) allow the creation of workspaces}. */ -public class JpaConnectorCreateWorkspacesTest extends WorkspaceConnectorTest { +public abstract class JpaConnectorCreateWorkspacesTest extends WorkspaceConnectorTest { - private String[] predefinedWorkspaces; + protected String[] predefinedWorkspaces; /** * {@inheritDoc} * - * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#setUpSource() - */ - @Override - protected RepositorySource setUpSource() { - predefinedWorkspaces = new String[] {"workspace1", "workspace1a"}; - - // Set the connection properties using the environment defined in the POM files ... - JpaSource source = TestEnvironment.configureJpaSource("Test Repository", this); - - // Override the inherited properties, since that's the focus of these tests ... - source.setCreatingWorkspacesAllowed(true); - source.setPredefinedWorkspaceNames(predefinedWorkspaces); - - return source; - } - - /** - * {@inheritDoc} - * * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#initializeContent(org.jboss.dna.graph.Graph) */ @Override Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/JpaConnectorNoCreateWorkspaceTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/JpaConnectorNoCreateWorkspaceTest.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/JpaConnectorNoCreateWorkspaceTest.java (working copy) @@ -40,7 +40,7 @@ */ public class JpaConnectorNoCreateWorkspaceTest extends WorkspaceConnectorTest { - private String[] predefinedWorkspaces; + protected String[] predefinedWorkspaces; /** * {@inheritDoc} Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/basic/BasicCreateWorkspacesTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/basic/BasicCreateWorkspacesTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/basic/BasicCreateWorkspacesTest.java (revision 0) @@ -0,0 +1,24 @@ +package org.jboss.dna.connector.store.jpa.model.basic; + +import org.jboss.dna.connector.store.jpa.JpaConnectorCreateWorkspacesTest; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.connector.store.jpa.TestEnvironment; +import org.jboss.dna.graph.connector.RepositorySource; + +public class BasicCreateWorkspacesTest extends JpaConnectorCreateWorkspacesTest { + + @Override + protected RepositorySource setUpSource() { + predefinedWorkspaces = new String[] {"workspace1", "workspace1a"}; + + // Set the connection properties using the environment defined in the POM files ... + JpaSource source = TestEnvironment.configureJpaSource("Test Repository", this); + + // Override the inherited properties, since that's the focus of these tests ... + source.setCreatingWorkspacesAllowed(true); + source.setPredefinedWorkspaceNames(predefinedWorkspaces); + + return source; + } + +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\basic\BasicCreateWorkspacesTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/basic/BasicNoCreateWorkspaceTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/basic/BasicNoCreateWorkspaceTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/basic/BasicNoCreateWorkspaceTest.java (revision 0) @@ -0,0 +1,23 @@ +package org.jboss.dna.connector.store.jpa.model.basic; + +import org.jboss.dna.connector.store.jpa.JpaConnectorNoCreateWorkspaceTest; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.connector.store.jpa.TestEnvironment; +import org.jboss.dna.graph.connector.RepositorySource; + +public class BasicNoCreateWorkspaceTest extends JpaConnectorNoCreateWorkspaceTest { + + @Override + protected RepositorySource setUpSource() { + predefinedWorkspaces = new String[] {"workspace1", "workspace1a"}; + + // Set the connection properties using the environment defined in the POM files ... + JpaSource source = TestEnvironment.configureJpaSource("Test Repository", this); + + // Override the inherited properties, since that's the focus of these tests ... + source.setCreatingWorkspacesAllowed(true); + source.setPredefinedWorkspaceNames(predefinedWorkspaces); + + return source; + } +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\basic\BasicNoCreateWorkspaceTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/NodeEntityTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/NodeEntityTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/NodeEntityTest.java (revision 0) @@ -0,0 +1,168 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertThat; +import java.util.UUID; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Query; +import org.hibernate.ejb.Ejb3Configuration; +import org.jboss.dna.connector.store.jpa.model.common.NamespaceEntity; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.property.PropertyType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class NodeEntityTest { + + private static final Boolean SHOW_SQL = false; + private static final Boolean USE_CACHE = false; + + private ExecutionContext context; + private EntityManagerFactory factory; + private EntityManager manager; + private SimpleModel model; + + @Before + public void beforeEach() throws Exception { + model = new SimpleModel(); + + // Connect to the database ... + Ejb3Configuration configurator = new Ejb3Configuration(); + model.configure(configurator); + configurator.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect"); + configurator.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver"); + configurator.setProperty("hibernate.connection.username", "sa"); + configurator.setProperty("hibernate.connection.password", ""); + configurator.setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:."); + configurator.setProperty("hibernate.show_sql", SHOW_SQL.toString()); + configurator.setProperty("hibernate.format_sql", "true"); + configurator.setProperty("hibernate.use_sql_comments", "true"); + configurator.setProperty("hibernate.hbm2ddl.auto", "create"); + if (USE_CACHE) { + configurator.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.HashtableCacheProvider"); + + } + + factory = configurator.buildEntityManagerFactory(); + manager = factory.createEntityManager(); + context = new ExecutionContext(); + } + + @After + public void afterEach() throws Exception { + try { + if (manager != null) manager.close(); + } finally { + manager = null; + if (factory != null) { + try { + factory.close(); + } finally { + factory = null; + } + } + } + } + + @Test + public void shouldSaveAndReloadNode() { + String rootUuid = UUID.randomUUID().toString(); + long workspaceId = 1L; + + manager.getTransaction().begin(); + + NamespaceEntity namespace = new NamespaceEntity(""); + manager.persist(namespace); + + NodeEntity root = new NodeEntity(0, null, rootUuid, workspaceId, 1, namespace, "root"); + LargeValueEntity largeValue = LargeValueEntity.create("This is a nonsense string that I am typing.".getBytes(), + PropertyType.STRING, + false); + root.getLargeValues().add(largeValue); + manager.persist(root); + manager.persist(largeValue); + + final int NUM_CHILDREN = 10; + for (int i = 0; i < NUM_CHILDREN; i++) { + NodeEntity child = new NodeEntity(0, root, UUID.randomUUID().toString(), workspaceId, 1, namespace, "child" + i); + root.addChild(child); + + manager.persist(child); + } + + + manager.getTransaction().commit(); + manager.close(); + + manager = factory.createEntityManager(); + + Query query = manager.createNamedQuery("NodeEntity.findByNodeUuid"); + query.setParameter("workspaceId", workspaceId); + query.setParameter("nodeUuidString", rootUuid); + + NodeEntity newRoot = (NodeEntity)query.getSingleResult(); + assertThat(newRoot, is(notNullValue())); + assertThat(newRoot, is(root)); + assertThat(newRoot.getChildren().size(), is(NUM_CHILDREN)); + + for (int i = 0; i < NUM_CHILDREN; i++) { + assertThat(newRoot.getChildren().get(i).getChildName(), is("child" + i)); + // NodeEntity child = newRoot.getChildren().get(i); + // System.out.println(child.getChildName() + " " + child.getIndexInParent()); + } + + root.getLargeValues().size(); + } + + @Test + public void shouldDeleteRecursively() { + String rootUuid = UUID.randomUUID().toString(); + long workspaceId = 1L; + + manager.getTransaction().begin(); + + NamespaceEntity namespace = new NamespaceEntity(""); + manager.persist(namespace); + + NodeEntity root = new NodeEntity(0, null, rootUuid, workspaceId, 1, namespace, "root"); + manager.persist(root); + + final int DEPTH = 10; + NodeEntity parent = root; + + for (int i = 0; i < DEPTH; i++) { + NodeEntity child = new NodeEntity(0, parent, UUID.randomUUID().toString(), workspaceId, 1, namespace, "child" + i); + root.addChild(child); + + manager.persist(child); + parent = child; + } + + manager.getTransaction().commit(); + manager.close(); + + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + + SubgraphQuery subgraph = SubgraphQuery.create(context, + manager, + workspaceId, + UUID.fromString(rootUuid), + context.getValueFactories().getPathFactory().createRootPath(), + 0); + + assertThat(subgraph.getNodeCount(false), is(10)); + + subgraph.deleteSubgraph(true); + subgraph.close(); + + int count = (Integer)manager.createNativeQuery("SELECT count(*) FROM dna_simple_node").getSingleResult(); + assertThat(count, is(0)); + + manager.getTransaction().commit(); + + } +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\simple\NodeEntityTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleCreateWorkspacesTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleCreateWorkspacesTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleCreateWorkspacesTest.java (revision 0) @@ -0,0 +1,31 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import org.jboss.dna.connector.store.jpa.JpaConnectorCreateWorkspacesTest; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.connector.store.jpa.TestEnvironment; +import org.jboss.dna.graph.connector.RepositorySource; + +public class SimpleCreateWorkspacesTest extends JpaConnectorCreateWorkspacesTest { + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#setUpSource() + */ + @Override + protected RepositorySource setUpSource() { + predefinedWorkspaces = new String[] {"workspace1", "workspace1a"}; + + // Set the connection properties using the environment defined in the POM files ... + JpaSource source = TestEnvironment.configureJpaSource("Test Repository", this); + source.setModel(JpaSource.Models.SIMPLE.getName()); + + // Override the inherited properties, since that's the focus of these tests ... + source.setCreatingWorkspacesAllowed(true); + source.setPredefinedWorkspaceNames(predefinedWorkspaces); + source.setDefaultWorkspaceName(predefinedWorkspaces[0]); + + return source; + } + +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleCreateWorkspacesTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnectorReadableTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnectorReadableTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnectorReadableTest.java (revision 0) @@ -0,0 +1,81 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import org.jboss.dna.common.statistic.Stopwatch; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Graph; +import org.jboss.dna.graph.Subgraph; +import org.jboss.dna.graph.connector.RepositoryConnectionFactory; +import org.jboss.dna.graph.connector.RepositoryContext; +import org.jboss.dna.graph.connector.RepositorySource; +import org.jboss.dna.graph.connector.test.ReadableConnectorTest; +import org.jboss.dna.graph.observe.Observer; + +public class SimpleJpaConnectorReadableTest extends ReadableConnectorTest { + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#setUpSource() + */ + @Override + protected RepositorySource setUpSource() { + // Set the connection properties using the environment defined in the POM files ... + JpaSource source = new JpaSource(); + + source.setModel(JpaSource.Models.SIMPLE.getName()); + source.setName("SimpleJpaSource"); + source.setDialect("org.hibernate.dialect.HSQLDialect"); + source.setDriverClassName("org.hsqldb.jdbcDriver"); + source.setUsername("sa"); + source.setPassword(""); + source.setUrl("jdbc:hsqldb:mem:test"); + source.setShowSql(false); + source.setAutoGenerateSchema("create"); + + source.initialize(new RepositoryContext() { + + private final ExecutionContext context = new ExecutionContext(); + + public Subgraph getConfiguration( int depth ) { + return null; + } + + public ExecutionContext getExecutionContext() { + return context; + } + + public Observer getObserver() { + return null; + } + + public RepositoryConnectionFactory getRepositoryConnectionFactory() { + return null; + } + + }); + + return source; + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#initializeContent(org.jboss.dna.graph.Graph) + */ + @Override + protected void initializeContent( Graph graph ) { + String initialPath = ""; + int depth = 4; + int numChildrenPerNode = 4; + int numPropertiesPerNode = 7; + Stopwatch sw = new Stopwatch(); + boolean batch = true; + // graph.createWorkspace().named("default"); + createSubgraph(graph, initialPath, depth, numChildrenPerNode, numPropertiesPerNode, batch, sw, System.out, null); + graph.createWorkspace().named("other workspace"); + createSubgraph(graph, initialPath, depth, numChildrenPerNode, numPropertiesPerNode, batch, sw, System.out, null); + graph.useWorkspace("default"); + } + +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleJpaConnectorReadableTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnectorWritableTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnectorWritableTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaConnectorWritableTest.java (revision 0) @@ -0,0 +1,74 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Graph; +import org.jboss.dna.graph.Subgraph; +import org.jboss.dna.graph.connector.RepositoryConnectionFactory; +import org.jboss.dna.graph.connector.RepositoryContext; +import org.jboss.dna.graph.connector.RepositorySource; +import org.jboss.dna.graph.connector.test.WritableConnectorTest; +import org.jboss.dna.graph.observe.Observer; + +public class SimpleJpaConnectorWritableTest extends WritableConnectorTest { + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#setUpSource() + */ + @Override + protected RepositorySource setUpSource() { + // Set the connection properties using the environment defined in the POM files ... + JpaSource source = new JpaSource(); + + source.setModel(JpaSource.Models.SIMPLE.getName()); + source.setName("SimpleJpaSource"); + source.setDialect("org.hibernate.dialect.HSQLDialect"); + source.setDriverClassName("org.hsqldb.jdbcDriver"); + source.setUsername("sa"); + source.setPassword(""); + source.setUrl("jdbc:hsqldb:mem:test"); + source.setShowSql(false); + source.setAutoGenerateSchema("create"); + + source.initialize(new RepositoryContext() { + + private final ExecutionContext context = new ExecutionContext(); + + public Subgraph getConfiguration( int depth ) { + return null; + } + + public ExecutionContext getExecutionContext() { + return context; + } + + public Observer getObserver() { + return null; + } + + public RepositoryConnectionFactory getRepositoryConnectionFactory() { + return null; + } + + }); + + return source; + } + + @Override + public void shouldCopyNodeWithChildren() { + + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#initializeContent(org.jboss.dna.graph.Graph) + */ + @Override + protected void initializeContent( Graph graph ) { + } + +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleJpaConnectorWritableTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaSourceTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaSourceTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleJpaSourceTest.java (revision 0) @@ -0,0 +1,62 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import java.util.concurrent.TimeUnit; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Subgraph; +import org.jboss.dna.graph.connector.RepositoryConnection; +import org.jboss.dna.graph.connector.RepositoryConnectionFactory; +import org.jboss.dna.graph.connector.RepositoryContext; +import org.jboss.dna.graph.observe.Observer; +import org.junit.Before; +import org.junit.Test; + +public class SimpleJpaSourceTest { + + private JpaSource source; + + @Before + public void beforeEach() { + // Set the connection properties using the environment defined in the POM files ... + source = new JpaSource(); + + source.setModel(JpaSource.Models.SIMPLE.getName()); + source.setName("SimpleJpaSource"); + source.setDialect("org.hibernate.dialect.HSQLDialect"); + source.setDriverClassName("org.hsqldb.jdbcDriver"); + source.setUsername("sa"); + source.setPassword(""); + source.setUrl("jdbc:hsqldb:."); + source.setShowSql(true); + source.setAutoGenerateSchema("create"); + + source.initialize(new RepositoryContext() { + + private final ExecutionContext context = new ExecutionContext(); + + public Subgraph getConfiguration( int depth ) { + return null; + } + + public ExecutionContext getExecutionContext() { + return context; + } + + public Observer getObserver() { + return null; + } + + public RepositoryConnectionFactory getRepositoryConnectionFactory() { + return null; + } + + }); + } + + @Test + public void shouldCreateLiveConnection() throws InterruptedException { + RepositoryConnection connection = source.getConnection(); + connection.ping(1, TimeUnit.SECONDS); + connection.close(); + } +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleJpaSourceTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleNoCreateWorkspaceTest.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleNoCreateWorkspaceTest.java (revision 0) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/model/simple/SimpleNoCreateWorkspaceTest.java (revision 0) @@ -0,0 +1,30 @@ +package org.jboss.dna.connector.store.jpa.model.simple; + +import org.jboss.dna.connector.store.jpa.JpaConnectorNoCreateWorkspaceTest; +import org.jboss.dna.connector.store.jpa.JpaSource; +import org.jboss.dna.connector.store.jpa.TestEnvironment; +import org.jboss.dna.graph.connector.RepositorySource; + +public class SimpleNoCreateWorkspaceTest extends JpaConnectorNoCreateWorkspaceTest { + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#setUpSource() + */ + @Override + protected RepositorySource setUpSource() { + predefinedWorkspaces = new String[] {"workspace1", "workspace1a"}; + + // Set the connection properties using the environment defined in the POM files ... + JpaSource source = TestEnvironment.configureJpaSource("Test Repository", this); + source.setModel(JpaSource.Models.SIMPLE.getName()); + + // Override the inherited properties, since that's the focus of these tests ... + source.setCreatingWorkspacesAllowed(true); + source.setPredefinedWorkspaceNames(predefinedWorkspaces); + source.setDefaultWorkspaceName(predefinedWorkspaces[0]); + + return source; + } +} Property changes on: extensions\dna-connector-store-jpa\src\test\java\org\jboss\dna\connector\store\jpa\model\simple\SimpleNoCreateWorkspaceTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/TestEnvironment.java =================================================================== --- extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/TestEnvironment.java (revision 1382) +++ extensions/dna-connector-store-jpa/src/test/java/org/jboss/dna/connector/store/jpa/TestEnvironment.java (working copy) @@ -31,7 +31,7 @@ public static JpaSource configureJpaSource( String sourceName, Object testCase ) { Properties properties = new Properties(); - ClassLoader loader = testCase instanceof Class ? ((Class)testCase).getClassLoader() : testCase.getClass() + ClassLoader loader = testCase instanceof Class ? ((Class)testCase).getClassLoader() : testCase.getClass() .getClassLoader(); try { properties.load(loader.getResourceAsStream("database.properties"));