DNA SVN: r1296 - trunk/dna-jcr/src/main/java/org/jboss/dna/jcr.
by dna-commits@lists.jboss.org
Author: bcarothers
Date: 2009-10-19 16:26:00 -0400 (Mon, 19 Oct 2009)
New Revision: 1296
Modified:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
Log:
DNA-457 Add JSR-170 Locking Optional Feature
Quick change to address some Java5/6 compilation issues.
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-10-19 19:54:55 UTC (rev 1295)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-10-19 20:26:00 UTC (rev 1296)
@@ -1368,7 +1368,7 @@
nodesToVisit.add(nodeInfo());
while (!nodesToVisit.isEmpty()) {
- Node<JcrNodePayload, JcrPropertyPayload> node = nodesToVisit.pop();
+ Node<JcrNodePayload, JcrPropertyPayload> node = nodesToVisit.get(nodesToVisit.size() - 1);
if (session().workspace().lockManager().lockFor(node.getLocation()) != null) throw new LockException(
JcrI18n.parentAlreadyLocked.text(this.location,
node.getLocation()));
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java 2009-10-19 19:54:55 UTC (rev 1295)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java 2009-10-19 20:26:00 UTC (rev 1296)
@@ -270,38 +270,31 @@
final AbstractJcrNode node = cache.findJcrNode(Location.create(nodeUuid));
final JcrSession session = cache.session();
return new Lock() {
- @Override
public String getLockOwner() {
return lockOwner;
}
- @Override
public String getLockToken() {
String uuidString = lockUuid.toString();
return session.lockTokens().contains(uuidString) ? uuidString : null;
}
- @Override
public Node getNode() {
return node;
}
- @Override
public boolean isDeep() {
return deep;
}
- @Override
public boolean isLive() throws RepositoryException {
return workspaceLocksByNodeUuid.containsKey(nodeUuid);
}
- @Override
public boolean isSessionScoped() {
return sessionScoped;
}
- @Override
public void refresh() throws LockException, RepositoryException {
if (getLockToken() == null) {
throw new LockException(JcrI18n.notLocked.text(node.location));
14 years, 7 months
DNA SVN: r1295 - in trunk: dna-cnd/src/test/resources/cnd and 4 other directories.
by dna-commits@lists.jboss.org
Author: bcarothers
Date: 2009-10-19 15:54:55 -0400 (Mon, 19 Oct 2009)
New Revision: 1295
Added:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
Modified:
trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java
trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java
Log:
DNA-457 Add JSR-170 Locking Optional Feature
Committed patch that provides a locking implementation based on tracking the UUIDs of locked nodes. This implies that any lockable node must also be referenceable. The patch allows all of the TCK locking tests to pass as well as a few L2 tests which assumed a locking implementation for the repository. However, the locking approach does not yet provide any way of integrating the lock/unlock capabilities with the underlying repository. I will leave the defect open until that need can be addressed as well.
Thanks much for the reviews and suggestions!
Modified: trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java
===================================================================
--- trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -306,7 +306,7 @@
// mixin
// - jcr:lockOwner (string) protected ignore
// - jcr:lockIsDeep (boolean) protected ignore
- assertNodeType("mix:lockable", NO_SUPERTYPES, NO_PRIMARY_NAME, NodeOptions.Mixin);
+ assertNodeType("mix:lockable", new String[] {"mix:referenceable"}, NO_PRIMARY_NAME, NodeOptions.Mixin);
assertProperty("mix:lockable", "jcr:lockOwner", "String", NO_DEFAULTS, OnParentVersion.Ignore, PropertyOptions.Protected);
assertProperty("mix:lockable",
"jcr:lockIsDeep",
Modified: trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd
===================================================================
--- trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd 2009-10-19 19:54:55 UTC (rev 1295)
@@ -37,7 +37,7 @@
mixin
- jcr:uuid (string) mandatory autocreated protected initialize
-[mix:lockable]
+[mix:lockable] > mix:referenceable
mixin
- jcr:lockOwner (string) protected ignore
- jcr:lockIsDeep (boolean) protected ignore
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -824,8 +824,8 @@
JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName);
// Check this separately since it throws a different type of exception
- if (this.isLocked()) {
- throw new LockException();
+ if (this.isLocked() && !holdsLock()) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
}
if (!canAddMixin(mixinName)) {
@@ -853,11 +853,10 @@
*/
public final void removeMixin( String mixinName ) throws RepositoryException {
- if (this.isLocked()) {
- throw new LockException();
+ if (this.isLocked() && !holdsLock()) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
}
- // TODO: Check access control when that support is added
// TODO: Throw VersionException if this node is versionable and checked in or unversionable and the nearest versionable
// ancestor is checked in
@@ -1007,6 +1006,12 @@
UUID desiredUuid )
throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
RepositoryException {
+
+
+ if (isLocked() && !holdsLock()) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
+ }
+
// Determine the path ...
NodeEditor editor = null;
Path path = null;
@@ -1331,8 +1336,10 @@
* @return <code>false</code>
* @see javax.jcr.Node#holdsLock()
*/
- public final boolean holdsLock() {
- return false;
+ public final boolean holdsLock() throws RepositoryException {
+ WorkspaceLockManager.DnaLock lock = session().workspace().lockManager().lockFor(this.location);
+
+ return lock != null && cache.session().lockTokens().contains(lock.getLockToken());
}
/**
@@ -1341,41 +1348,99 @@
* @return <code>false</code>
* @see javax.jcr.Node#isLocked()
*/
- public final boolean isLocked() {
- return false;
+ public final boolean isLocked() throws LockException, RepositoryException {
+ return lock() != null;
}
-
+
/**
* {@inheritDoc}
*
- * @throws UnsupportedRepositoryOperationException always
* @see javax.jcr.Node#lock(boolean, boolean)
*/
public final Lock lock( boolean isDeep,
- boolean isSessionScoped ) throws UnsupportedRepositoryOperationException {
- throw new UnsupportedRepositoryOperationException();
+ boolean isSessionScoped ) throws LockException, RepositoryException {
+ if (isLocked()) {
+ throw new LockException(JcrI18n.alreadyLocked.text(this.location));
+ }
+
+ if (isDeep) {
+ LinkedList<Node<JcrNodePayload, JcrPropertyPayload>> nodesToVisit = new LinkedList<Node<JcrNodePayload, JcrPropertyPayload>>();
+ nodesToVisit.add(nodeInfo());
+
+ while (!nodesToVisit.isEmpty()) {
+ Node<JcrNodePayload, JcrPropertyPayload> node = nodesToVisit.pop();
+ if (session().workspace().lockManager().lockFor(node.getLocation()) != null) throw new LockException(
+ JcrI18n.parentAlreadyLocked.text(this.location,
+ node.getLocation()));
+
+ for (Node<JcrNodePayload, JcrPropertyPayload> child : node.getChildren()) {
+ nodesToVisit.add(child);
+ }
+ }
+ }
+
+ WorkspaceLockManager.DnaLock lock = session().workspace().lockManager().lock(cache,
+ this.location,
+ session().getUserID(),
+ isDeep,
+ isSessionScoped);
+
+ cache.session().addLockToken(lock.getLockToken());
+ return lock.lockFor(cache);
}
/**
* {@inheritDoc}
*
- * @throws UnsupportedRepositoryOperationException always
* @see javax.jcr.Node#unlock()
*/
- public final void unlock() throws UnsupportedRepositoryOperationException {
- throw new UnsupportedRepositoryOperationException();
+ public final void unlock() throws LockException, RepositoryException {
+ WorkspaceLockManager.DnaLock lock = session().workspace().lockManager().lockFor(this.location);
+
+ if (lock == null) {
+ throw new LockException(JcrI18n.notLocked.text(this.location));
+ }
+
+ if (!cache.session().lockTokens().contains(lock.getLockToken())) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
+ }
+
+ session().workspace().lockManager().unlock(lock);
+ session().removeLockToken(lock.getLockToken());
}
+ private final WorkspaceLockManager.DnaLock lock() throws RepositoryException {
+ // This can only happen in mocked testing.
+ if (session() == null || session().workspace() == null) return null;
+
+ WorkspaceLockManager lockManager = session().workspace().lockManager();
+ WorkspaceLockManager.DnaLock lock = lockManager.lockFor(this.location);
+ if (lock != null) return lock;
+
+ AbstractJcrNode parent = this;
+ while (!parent.isRoot()) {
+ parent = parent.getParent();
+
+ WorkspaceLockManager.DnaLock parentLock = lockManager.lockFor(parent.location);
+ if (parentLock != null && parentLock.isLive()) {
+ return parentLock.isDeep() ? parentLock : null;
+ }
+ }
+ return null;
+ }
+
/**
* {@inheritDoc}
*
- * @throws UnsupportedRepositoryOperationException always
* @see javax.jcr.Node#getLock()
*/
- public final Lock getLock() throws UnsupportedRepositoryOperationException {
- throw new UnsupportedRepositoryOperationException();
+ public final Lock getLock() throws LockException, RepositoryException {
+ WorkspaceLockManager.DnaLock lock = lock();
+
+ if (lock == null) throw new LockException(JcrI18n.notLocked.text(this.location));
+ return lock.lockFor(cache);
}
-
+
/**
* {@inheritDoc}
*
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -31,6 +31,7 @@
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.PropertyDefinition;
@@ -69,6 +70,27 @@
abstract boolean isMultiple();
/**
+ * Checks that this property's parent node is not already locked by another session. If the parent node is not locked or the
+ * parent node is locked but the lock is owned by this {@code Session}, this method completes silently. If the parent node is
+ * locked (either directly or as part of a deep lock from an ancestor), this method throws a {@code LockException}.
+ *
+ * @throws LockException if the parent node of this property is locked (that is, if {@code getParent().isLocked() == true &&
+ * getParent().getLock().getLockToken() == null}.
+ * @throws RepositoryException if any other error occurs
+ * @see Node#isLocked()
+ * @see Lock#getLockToken()
+ */
+ protected final void checkForLock() throws LockException, RepositoryException {
+
+ if (this.getParent().isLocked()) {
+ Lock parentLock = this.getParent().getLock();
+ if (parentLock != null && parentLock.getLockToken() == null) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(this.getParent().location));
+ }
+ }
+ }
+
+ /**
* {@inheritDoc}
*
* @throws IllegalArgumentException if <code>visitor</code> is <code>null</code>.
@@ -145,7 +167,7 @@
*
* @see javax.jcr.Item#getParent()
*/
- public final Node getParent() {
+ public final AbstractJcrNode getParent() {
return node;
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -34,9 +34,15 @@
public class DnaLexicon extends org.jboss.dna.repository.DnaLexicon {
public static final Name BASE = new BasicName(Namespace.URI, "base");
+ public static final Name IS_HELD_BY_SESSION = new BasicName(Namespace.URI, "isHeldBySession");
+ public static final Name IS_SESSION_SCOPED = new BasicName(Namespace.URI, "isSessionScoped");
+ public static final Name LOCK = new BasicName(Namespace.URI, "lock");
+ public static final Name LOCKED_NODE = new BasicName(Namespace.URI, "lockedNode");
+ public static final Name LOCKS = new BasicName(Namespace.URI, "locks");
public static final Name NAMESPACE = new BasicName(Namespace.URI, "namespace");
public static final Name NODE_TYPES = new BasicName(Namespace.URI, "nodeTypes");
public static final Name REPOSITORIES = new BasicName(Namespace.URI, "repositories");
public static final Name SYSTEM = new BasicName(Namespace.URI, "system");
public static final Name URI = new BasicName(Namespace.URI, "uri");
+ public static final Name WORKSPACE = new BasicName(Namespace.URI, "workspace");
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -166,6 +166,15 @@
public static I18n cannotAddMixin;
public static I18n invalidMixinTypeForNode;
public static I18n notOrderable;
+
+ // Lock messages
+ public static I18n cannotRemoveLockToken;
+ public static I18n alreadyLocked;
+ public static I18n parentAlreadyLocked;
+ public static I18n notLocked;
+ public static I18n lockTokenNotHeld;
+ public static I18n lockTokenAlreadyHeld;
+ public static I18n uuidRequiredForLock;
static {
try {
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -187,7 +187,8 @@
*/
public final void setValue( Value[] values )
throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
-
+ checkForLock();
+
if (values == null) {
this.remove();
return;
@@ -208,6 +209,8 @@
*/
public final void setValue( String[] values )
throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ checkForLock();
+
if (values == null) {
this.remove();
return;
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -26,6 +26,8 @@
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.session.GraphSession.NodeId;
@@ -96,7 +98,15 @@
*
* @see javax.jcr.Item#remove()
*/
- public void remove() throws RepositoryException {
+ public void remove() throws RepositoryException, LockException {
+ Node parentNode = getParent();
+ if (parentNode.isLocked()) {
+ Lock parentLock = parentNode.getLock();
+ if (parentLock != null && parentLock.getLockToken() == null) {
+ throw new LockException();
+ }
+ }
+
editor().destroy();
}
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -35,6 +35,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import javax.jcr.Credentials;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Repository;
@@ -45,6 +47,7 @@
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
+import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;
import org.jboss.dna.common.i18n.I18n;
@@ -204,6 +207,9 @@
private final ExecutionContext executionContext;
private final RepositoryConnectionFactory connectionFactory;
private final RepositoryNodeTypeManager repositoryTypeManager;
+ @GuardedBy( "lockManagersLock" )
+ private final ConcurrentMap<String, WorkspaceLockManager> lockManagers;
+ private final Path locksPath;
private final Map<Option, String> options;
private final String systemSourceName;
private final String systemWorkspaceName;
@@ -257,7 +263,7 @@
// Initialize required JCR descriptors.
modifiableDescriptors.put(Repository.LEVEL_1_SUPPORTED, "true");
modifiableDescriptors.put(Repository.LEVEL_2_SUPPORTED, "true");
- modifiableDescriptors.put(Repository.OPTION_LOCKING_SUPPORTED, "false");
+ modifiableDescriptors.put(Repository.OPTION_LOCKING_SUPPORTED, "true");
modifiableDescriptors.put(Repository.OPTION_OBSERVATION_SUPPORTED, "false");
modifiableDescriptors.put(Repository.OPTION_QUERY_SQL_SUPPORTED, "false");
modifiableDescriptors.put(Repository.OPTION_TRANSACTIONS_SUPPORTED, "false");
@@ -402,6 +408,9 @@
this.federatedSource = new FederatedRepositorySource();
this.federatedSource.setName("JCR " + repositorySourceName);
this.federatedSource.initialize(new FederatedRepositoryContext(this.connectionFactory));
+
+ this.lockManagers = new ConcurrentHashMap<String, WorkspaceLockManager>();
+ this.locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, DnaLexicon.LOCKS);
}
protected void initializeSystemContent( Graph systemGraph ) {
@@ -662,6 +671,24 @@
}
/**
+ * Returns the lock manager for the named workspace (if one already exists) or creates a new lock manager and returns it. This
+ * method is thread-safe.
+ *
+ * @param workspaceName the name of the workspace for which the lock manager should be returned
+ * @return the lock manager for the workspace; never null
+ */
+ WorkspaceLockManager getLockManager( String workspaceName ) {
+ WorkspaceLockManager lockManager = lockManagers.get(workspaceName);
+ if (lockManager != null) return lockManager;
+
+ lockManager = new WorkspaceLockManager(executionContext, this, workspaceName, locksPath);
+ WorkspaceLockManager newLockManager = lockManagers.putIfAbsent(workspaceName, lockManager);
+
+ if (newLockManager != null) return newLockManager;
+ return lockManager;
+ }
+
+ /**
* Returns the name of this repository
*
* @return the name of this repository
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -28,6 +28,8 @@
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -48,6 +50,7 @@
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.Workspace;
+import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.NotThreadSafe;
@@ -68,6 +71,7 @@
import org.jboss.dna.jcr.JcrContentHandler.SaveMode;
import org.jboss.dna.jcr.JcrNamespaceRegistry.Behavior;
import org.jboss.dna.jcr.SessionCache.JcrPropertyPayload;
+import org.jboss.dna.jcr.WorkspaceLockManager.DnaLock;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -128,6 +132,8 @@
private final SessionCache cache;
+ private final Set<String> lockTokens;
+
/**
* A cached instance of the root path.
*/
@@ -159,6 +165,7 @@
this.cache = new SessionCache(this);
this.isLive = true;
+ this.lockTokens = new HashSet<String>();
assert this.sessionAttributes != null;
assert this.workspace != null;
@@ -194,6 +201,10 @@
return this.repository;
}
+ final Collection<String> lockTokens() {
+ return lockTokens;
+ }
+
Graph.Batch createBatch() {
return graph.batch();
}
@@ -286,11 +297,22 @@
/**
* {@inheritDoc}
*
- * @throws UnsupportedOperationException always
* @see javax.jcr.Session#addLockToken(java.lang.String)
*/
- public void addLockToken( String lt ) {
- throw new UnsupportedOperationException();
+ public void addLockToken( String lt ) throws LockException {
+ CheckArg.isNotNull(lt, "lock token");
+
+ // Trivial case of giving a token back to ourself
+ if (lockTokens.contains(lt)) {
+ return;
+ }
+
+ if (workspace().lockManager().isHeldBySession(lt)) {
+ throw new LockException(JcrI18n.lockTokenAlreadyHeld.text(lt));
+ }
+
+ workspace().lockManager().setHeldBySession(lt, true);
+ lockTokens.add(lt);
}
/**
@@ -493,11 +515,10 @@
/**
* {@inheritDoc}
*
- * @throws UnsupportedOperationException always
* @see javax.jcr.Session#getLockTokens()
*/
public String[] getLockTokens() {
- throw new UnsupportedOperationException();
+ return lockTokens.toArray(new String[lockTokens.size()]);
}
/**
@@ -751,6 +772,7 @@
return;
}
+ this.workspace().lockManager().cleanLocks(lockTokens);
this.executionContext.getSecurityContext().logout();
isLive = false;
}
@@ -782,6 +804,20 @@
throw new ItemExistsException(JcrI18n.childNodeAlreadyExists.text(newNodeNameAsString, newParentNode.getPath()));
}
+ if (sourceNode.isLocked()) {
+ javax.jcr.lock.Lock sourceLock = sourceNode.getLock();
+ if (sourceLock != null && sourceLock.getLockToken() == null) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(srcAbsPath));
+ }
+ }
+
+ if (newParentNode.isLocked()) {
+ javax.jcr.lock.Lock newParentLock = newParentNode.getLock();
+ if (newParentLock != null && newParentLock.getLockToken() == null) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(destAbsPath));
+ }
+ }
+
newParentNode.editor().moveToBeChild(sourceNode, newNodeName.getName());
}
@@ -797,11 +833,29 @@
/**
* {@inheritDoc}
*
- * @throws UnsupportedOperationException always
* @see javax.jcr.Session#removeLockToken(java.lang.String)
*/
public void removeLockToken( String lt ) {
- throw new UnsupportedOperationException();
+ CheckArg.isNotNull(lt, "lock token");
+ // A LockException is thrown if the lock associated with the specified lock token is session-scoped.
+ /*
+ * The JCR API library that we're using diverges from the spec in that it doesn't declare
+ * this method to throw a LockException. We'll throw a runtime exception for now.
+ */
+
+ DnaLock lock = workspace().lockManager().lockFor(lt);
+ if (lock == null) {
+ // The lock is no longer valid
+ lockTokens.remove(lt);
+ return;
+ }
+
+ if (lock.isSessionScoped()) {
+ throw new IllegalStateException(JcrI18n.cannotRemoveLockToken.text(lt));
+ }
+
+ workspace().lockManager().setHeldBySession(lt, false);
+ lockTokens.remove(lt);
}
/**
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -197,6 +197,8 @@
public void setValue( Value value )
throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
JcrValue jcrValue = null;
+ checkForLock();
+
if (value instanceof JcrValue) {
jcrValue = (JcrValue)value;
@@ -249,6 +251,9 @@
protected void setValue( JcrValue jcrValue )
throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
assert jcrValue != null;
+
+ checkForLock();
+
editor().setProperty(name(), jcrValue);
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -41,6 +41,7 @@
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Workspace;
+import javax.jcr.lock.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NodeTypeManager;
@@ -62,6 +63,7 @@
import org.jboss.dna.graph.property.Name;
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.ValueFormatException;
import org.jboss.dna.graph.request.InvalidWorkspaceException;
import org.jboss.dna.graph.request.ReadBranchRequest;
@@ -71,6 +73,7 @@
import org.jboss.dna.jcr.JcrContentHandler.SaveMode;
import org.jboss.dna.jcr.SessionCache.JcrNodePayload;
import org.jboss.dna.jcr.SessionCache.JcrPropertyPayload;
+import org.jboss.dna.jcr.WorkspaceLockManager.DnaLock;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -128,6 +131,8 @@
*/
private final JcrQueryManager queryManager;
+ private final WorkspaceLockManager lockManager;
+
/**
* The {@link Session} instance that this corresponds with this workspace.
*/
@@ -144,6 +149,7 @@
assert repository != null;
this.name = workspaceName;
this.repository = repository;
+ this.lockManager = repository.getLockManager(workspaceName);
// // Set up the execution context for this workspace, which should use the namespace registry that persists
// // the namespaces in the graph ...
@@ -204,6 +210,10 @@
return this.context;
}
+ final WorkspaceLockManager lockManager() {
+ return this.lockManager;
+ }
+
/**
* {@inheritDoc}
*/
@@ -315,6 +325,32 @@
// This also performs the check permission for reading the parent ...
Name newNodeName = destPath.getLastSegment().getName();
SessionCache cache = this.session.cache();
+
+
+ /*
+ * Find the UUID for the source node. Have to go directly against the graph.
+ */
+ org.jboss.dna.graph.Node sourceNode = repository.createWorkspaceGraph(srcWorkspace).getNodeAt(srcPath);
+ Property uuidProp = sourceNode.getProperty(DnaLexicon.UUID);
+
+ if (uuidProp != null) {
+ UUID sourceUuid = this.context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
+
+ DnaLock sourceLock = lockManager().lockFor(Location.create(sourceUuid));
+ if (sourceLock != null && sourceLock.getLockToken() == null) {
+ throw new LockException(JcrI18n.lockTokenNotHeld.text(srcAbsPath));
+ }
+ }
+
+ AbstractJcrNode parentNode = cache.findJcrNode(Location.create(destPath.getParent()));
+
+ if (parentNode.isLocked()) {
+ Lock newParentLock = parentNode.getLock();
+ if (newParentLock != null && newParentLock.getLockToken() == null) {
+ throw new LockException(destAbsPath);
+ }
+ }
+
Node<JcrNodePayload, JcrPropertyPayload> parent = cache.findNode(null, destPath.getParent());
cache.findBestNodeDefinition(parent, newNodeName, parent.getPayload().getPrimaryTypeName());
@@ -445,6 +481,32 @@
// This also performs the check permission for reading the parent ...
Name newNodeName = destPath.getLastSegment().getName();
SessionCache cache = this.session.cache();
+
+
+ /*
+ * Find the UUID for the source node. Have to go directly against the graph.
+ */
+ org.jboss.dna.graph.Node sourceNode = repository.createWorkspaceGraph(srcWorkspace).getNodeAt(srcPath);
+ Property uuidProp = sourceNode.getProperty(DnaLexicon.UUID);
+
+ if (uuidProp != null) {
+ UUID sourceUuid = this.context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
+
+ DnaLock sourceLock = lockManager().lockFor(Location.create(sourceUuid));
+ if (sourceLock != null && sourceLock.getLockToken() == null) {
+ throw new LockException(srcAbsPath);
+ }
+ }
+
+ AbstractJcrNode parentNode = cache.findJcrNode(Location.create(destPath.getParent()));
+
+ if (parentNode.isLocked()) {
+ Lock newParentLock = parentNode.getLock();
+ if (newParentLock != null && newParentLock.getLockToken() == null) {
+ throw new LockException(destAbsPath);
+ }
+ }
+
Node<JcrNodePayload, JcrPropertyPayload> parent = cache.findNode(null, destPath.getParent());
cache.findBestNodeDefinition(parent, newNodeName, parent.getPayload().getPrimaryTypeName());
@@ -555,10 +617,32 @@
Node<JcrNodePayload, JcrPropertyPayload> newParent = cache.findNode(null, destPath.getParent());
cache.findBestNodeDefinition(newParent, newNodeName, newParent.getPayload().getPrimaryTypeName());
+ AbstractJcrNode sourceNode = cache.findJcrNode(Location.create(srcPath));
+
+ if (sourceNode.isLocked()) {
+ Lock sourceLock = sourceNode.getLock();
+ if (sourceLock != null && sourceLock.getLockToken() == null) {
+ throw new LockException(srcAbsPath);
+ }
+ }
+
+ AbstractJcrNode parentNode = cache.findJcrNode(Location.create(destPath.getParent()));
+
+ if (parentNode.isLocked()) {
+ Lock newParentLock = parentNode.getLock();
+ if (newParentLock != null && newParentLock.getLockToken() == null) {
+ throw new LockException(destAbsPath);
+ }
+ }
+
// Now perform the clone, using the direct (non-session) method ...
cache.graphSession().immediateMove(srcPath, destPath);
} catch (AccessControlException ace) {
throw new AccessDeniedException(ace);
+ } catch (ItemNotFoundException infe) {
+ throw new PathNotFoundException(infe);
+ } catch (org.jboss.dna.graph.property.PathNotFoundException pnfe) {
+ throw new PathNotFoundException(pnfe);
}
// /*
Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -0,0 +1,314 @@
+package org.jboss.dna.jcr;
+
+import java.util.Collection;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.PathNotFoundException;
+import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.graph.property.PropertyFactory;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.jcr.SessionCache.NodeEditor;
+
+/**
+ * Manages the locks for a particular workspace in a repository. Locks are stored in a {@code Map<UUID, DnaLock>} while they exist
+ * and are discarded after they are discarded (through the {@link Node#unlock()} method).
+ */
+@ThreadSafe
+class WorkspaceLockManager {
+
+ private final ExecutionContext context;
+ private final Path locksPath;
+ private final JcrRepository repository;
+ private final String workspaceName;
+ private final ConcurrentMap<UUID, DnaLock> workspaceLocksByNodeUuid;
+
+ WorkspaceLockManager( ExecutionContext context,
+ JcrRepository repository,
+ String workspaceName,
+ Path locksPath ) {
+ this.context = context;
+ this.repository = repository;
+ this.workspaceName = workspaceName;
+ this.locksPath = locksPath;
+
+ this.workspaceLocksByNodeUuid = new ConcurrentHashMap<UUID, DnaLock>();
+
+ Property locksPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.LOCKS);
+ repository.createSystemGraph().create(locksPath, locksPrimaryType).ifAbsent().and();
+ }
+
+ /**
+ * Creates a lock on the node with the given {@link Location}. This method creates a new lock, registers it in the list of
+ * locks, immediately modifies the {@code jcr:lockOwner} and {@code jcr:lockIsDeep} properties on the node in the underlying
+ * repository, and adds the lock to the system view.
+ * <p>
+ * The location given in {@code nodeLocation} must have a UUID.
+ * </p>
+ *
+ * @param cache the session cache from which the node was loaded
+ * @param nodeLocation the location for the node; may not be null and must have a UUID
+ * @param lockOwner the owner of the new lock
+ * @param isDeep whether the node's descendants in the content graph should also be locked
+ * @param isSessionScoped whether the lock should outlive the session in which it was created
+ * @return an object representing the newly created lock
+ * @throws RepositoryException if an error occurs updating the graph state
+ */
+ DnaLock lock( SessionCache cache,
+ Location nodeLocation,
+ String lockOwner,
+ boolean isDeep,
+ boolean isSessionScoped ) throws RepositoryException {
+ assert nodeLocation != null;
+
+ UUID lockUuid = UUID.randomUUID();
+ UUID nodeUuid = uuidFor(nodeLocation);
+
+ if (nodeUuid == null) {
+ throw new RepositoryException(JcrI18n.uuidRequiredForLock.text(nodeLocation));
+ }
+
+ DnaLock lock = new DnaLock(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped);
+
+ Graph.Batch batch = repository.createSystemGraph().batch();
+
+ PropertyFactory propFactory = context.getPropertyFactory();
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+ Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner);
+ Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep);
+
+ batch.create(pathFactory.create(locksPath, pathFactory.createSegment(lockUuid.toString())),
+ propFactory.create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.LOCK),
+ propFactory.create(JcrLexicon.UUID, nodeUuid.toString()),
+ propFactory.create(DnaLexicon.WORKSPACE, workspaceName),
+ propFactory.create(DnaLexicon.LOCKED_NODE, nodeUuid.toString()),
+ propFactory.create(DnaLexicon.IS_SESSION_SCOPED, isSessionScoped),
+ // This gets set after the lock succeeds and the lock token gets added to the session
+ propFactory.create(DnaLexicon.IS_HELD_BY_SESSION, false),
+ lockOwnerProp,
+ lockIsDeepProp).ifAbsent().and();
+ batch.execute();
+
+ AbstractJcrNode lockedNode = cache.findJcrNode(Location.create(nodeUuid));
+ NodeEditor editor = cache.getEditorFor(lockedNode.nodeInfo());
+
+ // Set the properties in the cache...
+ editor.setProperty(JcrLexicon.LOCK_OWNER,
+ (JcrValue)cache.session().getValueFactory().createValue(lockOwner, PropertyType.STRING),
+ false);
+ editor.setProperty(JcrLexicon.LOCK_IS_DEEP, (JcrValue)cache.session().getValueFactory().createValue(isDeep), false);
+
+ // Write them directly to the underlying graph
+ repository.createWorkspaceGraph(workspaceName).set(lockOwnerProp, lockIsDeepProp).on(nodeUuid);
+
+ workspaceLocksByNodeUuid.put(nodeUuid, lock);
+
+ return lock;
+ }
+
+ /**
+ * Removes the provided lock, effectively unlocking the node to which the lock is associated.
+ *
+ * @param lock the lock to be removed
+ */
+ void unlock( DnaLock lock ) {
+ try {
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+
+ Graph.Batch batch = repository.createSystemGraph().batch();
+
+ batch.delete(pathFactory.create(locksPath, pathFactory.createSegment(lock.lockUuid.toString())));
+ batch.remove(JcrLexicon.LOCK_OWNER, JcrLexicon.LOCK_IS_DEEP).on(lock.nodeUuid);
+ batch.execute();
+ workspaceLocksByNodeUuid.remove(lock.nodeUuid);
+ } catch (PathNotFoundException pnfe) {
+ /*
+ * This can legitimately happen if there is a session-scoped lock on a node which is then deleted by the lock owner and the lock
+ * owner logs out. At that point, the lock owner still holds the token for the lock, so it will get cleaned up with a call to cleanLocks.
+ */
+ if (!lock.nodeUuid.equals(pnfe.getLocation().getUuid())) {
+ // If the lock node under dna:locks is not found, this is an internal error
+ throw new IllegalStateException(pnfe);
+ }
+ workspaceLocksByNodeUuid.remove(lock.nodeUuid);
+
+ }
+ }
+
+ boolean isHeldBySession( String lockToken ) {
+ ValueFactory<Boolean> booleanFactory = context.getValueFactories().getBooleanFactory();
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+
+ org.jboss.dna.graph.Node lockNode = repository.createSystemGraph().getNodeAt(pathFactory.create(locksPath,
+ pathFactory.createSegment(lockToken)));
+
+ return booleanFactory.create(lockNode.getProperty(DnaLexicon.IS_HELD_BY_SESSION).getFirstValue());
+
+ }
+
+ void setHeldBySession( String lockToken,
+ boolean value ) {
+ PropertyFactory propFactory = context.getPropertyFactory();
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+
+ repository.createSystemGraph().set(propFactory.create(DnaLexicon.IS_HELD_BY_SESSION, value)).on(pathFactory.create(locksPath,
+ pathFactory.createSegment(lockToken)));
+ }
+
+ /**
+ * Returns the lock that corresponds to the given lock token
+ *
+ * @param lockToken the lock token
+ * @return the corresponding lock, possibly null
+ * @see Session#addLockToken(String)
+ * @see Session#removeLockToken(String)
+ */
+ DnaLock lockFor( String lockToken ) {
+ for (DnaLock lock : workspaceLocksByNodeUuid.values()) {
+ if (lockToken.equals(lock.getLockToken())) {
+ return lock;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the lock that corresponds to the given UUID
+ *
+ * @param nodeUuid the node UUID
+ * @return the corresponding lock, possibly null
+ */
+ DnaLock lockFor( Location nodeLocation ) throws RepositoryException {
+ UUID nodeUuid = uuidFor(nodeLocation);
+ if (nodeUuid == null) return null;
+ return workspaceLocksByNodeUuid.get(nodeUuid);
+ }
+
+
+ UUID uuidFor( Location location ) throws RepositoryException {
+ if (location.getUuid() != null) return location.getUuid();
+
+ org.jboss.dna.graph.property.Property uuidProp = location.getIdProperty(JcrLexicon.UUID);
+ if (uuidProp == null) return null;
+
+ return context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
+ }
+
+ /**
+ * Unlocks all locks corresponding to the tokens in the {@code lockTokens} collection that are session scoped.
+ *
+ * @param lockTokens the collection of lock tokens
+ */
+ void cleanLocks( Collection<String> lockTokens ) {
+ for (String lockToken : lockTokens) {
+ DnaLock lock = lockFor(lockToken);
+ if (lock != null && lock.isSessionScoped()) {
+ unlock(lock);
+ }
+ }
+ }
+
+ /**
+ * Internal representation of a locked node. This class should only be created through calls to
+ * {@link WorkspaceLockManager#lock(SessionCache, Location, String, boolean, boolean)}.
+ */
+ @ThreadSafe
+ public class DnaLock {
+ final UUID nodeUuid;
+ private final UUID lockUuid;
+ private final String lockOwner;
+ private final boolean deep;
+ private final boolean sessionScoped;
+
+ DnaLock( String lockOwner,
+ UUID lockUuid,
+ UUID nodeUuid,
+ boolean deep,
+ boolean sessionScoped ) {
+ super();
+ this.lockOwner = lockOwner;
+ this.lockUuid = lockUuid;
+ this.nodeUuid = nodeUuid;
+ this.deep = deep;
+ this.sessionScoped = sessionScoped;
+ }
+
+ public boolean isLive() {
+ return workspaceLocksByNodeUuid.containsKey(nodeUuid);
+ }
+
+ public boolean isDeep() {
+ return deep;
+ }
+
+ public String getLockOwner() {
+ return lockOwner;
+ }
+
+ public boolean isSessionScoped() {
+ return sessionScoped;
+ }
+
+ public String getLockToken() {
+ return lockUuid.toString();
+ }
+
+ public Lock lockFor( SessionCache cache ) throws RepositoryException {
+ final AbstractJcrNode node = cache.findJcrNode(Location.create(nodeUuid));
+ final JcrSession session = cache.session();
+ return new Lock() {
+ @Override
+ public String getLockOwner() {
+ return lockOwner;
+ }
+
+ @Override
+ public String getLockToken() {
+ String uuidString = lockUuid.toString();
+ return session.lockTokens().contains(uuidString) ? uuidString : null;
+ }
+
+ @Override
+ public Node getNode() {
+ return node;
+ }
+
+ @Override
+ public boolean isDeep() {
+ return deep;
+ }
+
+ @Override
+ public boolean isLive() throws RepositoryException {
+ return workspaceLocksByNodeUuid.containsKey(nodeUuid);
+ }
+
+ @Override
+ public boolean isSessionScoped() {
+ return sessionScoped;
+ }
+
+ @Override
+ public void refresh() throws LockException, RepositoryException {
+ if (getLockToken() == null) {
+ throw new LockException(JcrI18n.notLocked.text(node.location));
+ }
+ }
+ };
+ }
+
+ }
+}
Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-10-19 19:54:55 UTC (rev 1295)
@@ -150,3 +150,12 @@
cannotAddMixin = This node does not allow adding the mixin type "{0}"
invalidMixinTypeForNode = "{0}" is not currently a mixin type for node "{1}"
notOrderable = The primary type "{0}" for this node (at "{1}") does not have orderable children
+
+# Lock messages
+cannotRemoveLockToken = The lock token '{0}' is a session-scoped lock
+alreadyLocked = The node at location '{0}' is already locked
+parentAlreadyLocked = The node at location '{0}' cannot be locked because the parent node at location '{1}' is already locked
+notLocked = The node at location '{0}' is not locked
+lockTokenNotHeld = The node at location '{0}' is locked and this session does not hold its lock token
+lockTokenAlreadyHeld = The lock token '{0}' is already held by another session. It must be removed from that session before it can be added to another session.
+uuidRequiredForLock = Only referenceable nodes can be locked. The node at location '(0}' is not referenceable.
Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd 2009-10-19 19:54:55 UTC (rev 1295)
@@ -43,8 +43,21 @@
[dna:nodeTypes] > nt:base
+ * (nt:nodeType) = nt:nodeType protected version
+[dna:lock] > nt:base
+- dna:lockedNode (string) protected ignore
+- jcr:lockOwner (string) protected ignore
+- jcr:uuid (string) protected ignore
+- dna:sessionScope (boolean) protected ignore
+- jcr:isDeep (boolean) protected ignore
+- dna:isHeldBySession (boolean) protected ignore
+- dna:workspace (string) protected ignore
+
+[dna:locks] > nt:base
++ * (dna:lock) = dna:lock protected ignore
+
[dna:system] > nt:base
+ dna:namespaces (dna:namespaces) = dna:namespaces autocreated mandatory protected version
++ dna:locks (dna:locks) = dna:locks autocreated mandatory protected ignore
+ jcr:nodeTypes (dna:nodeTypes) = dna:nodeTypes autocreated mandatory protected version
[dna:root] > nt:base, mix:referenceable orderable
Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd 2009-10-19 19:54:55 UTC (rev 1295)
@@ -98,6 +98,6 @@
- jcr:statement (string)
- jcr:language (string)
-[mix:lockable] mixin
+[mix:lockable] > mix:referenceable mixin
- jcr:lockOwner (string) protected ignore
- jcr:lockIsDeep (boolean) protected ignore
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -39,6 +39,7 @@
import javax.jcr.Repository;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Workspace;
+import javax.jcr.lock.LockException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.version.Version;
import org.jboss.dna.graph.Graph;
@@ -402,17 +403,12 @@
hybrid.getBaseVersion();
}
- @Test( expected = UnsupportedRepositoryOperationException.class )
- public void shouldNotAllowGetLock() throws Exception {
+ @Test( expected = LockException.class )
+ public void shouldNotAllowGetLockIfNoLock() throws Exception {
hybrid.getLock();
}
@Test
- public void shouldNotAllowHoldsLock() throws Exception {
- assertThat(hybrid.holdsLock(), is(false));
- }
-
- @Test
public void shouldNotAllowIsCheckedOut() throws Exception {
assertThat(hybrid.isCheckedOut(), is(false));
}
@@ -422,10 +418,11 @@
assertThat(hybrid.isLocked(), is(false));
}
- @Test( expected = UnsupportedRepositoryOperationException.class )
- public void shouldNotAllowLock() throws Exception {
- hybrid.lock(false, false);
- }
+ // Now tested in TCK
+ // @Test( expected = UnsupportedRepositoryOperationException.class )
+ // public void shouldAllowLock() throws Exception {
+ // hybrid.lock(false, false);
+ // }
@Test( expected = UnsupportedOperationException.class )
public void shouldNotAllowMerge() throws Exception {
@@ -457,10 +454,11 @@
hybrid.restoreByLabel(null, false);
}
- @Test( expected = UnsupportedRepositoryOperationException.class )
- public void shouldNotAllowUnlock() throws Exception {
- hybrid.unlock();
- }
+ // Now tested in TCK
+ // @Test( expected = UnsupportedRepositoryOperationException.class )
+ // public void shouldNotAllowUnlock() throws Exception {
+ // hybrid.unlock();
+ // }
/*
* Primary-type and -item methods
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -41,6 +41,7 @@
import org.jboss.dna.graph.connector.RepositorySourceException;
import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.property.PathFactory;
import org.jboss.dna.jcr.nodetype.NodeTypeTemplate;
import org.mockito.MockitoAnnotations;
@@ -66,6 +67,7 @@
protected Map<String, Object> sessionAttributes;
protected Map<JcrRepository.Option, String> options;
protected NamespaceRegistry registry;
+ protected WorkspaceLockManager workspaceLockManager;
@Mock
protected JcrRepository repository;
@@ -140,7 +142,21 @@
return graph;
}
});
+ stub(repository.createSystemGraph()).toAnswer(new Answer<Graph>() {
+ public Graph answer( InvocationOnMock invocation ) throws Throwable {
+ return graph;
+ }
+ });
+ Path locksPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, DnaLexicon.LOCKS);
+ workspaceLockManager = new WorkspaceLockManager(context, repository, workspaceName, locksPath);
+
+ stub(repository.getLockManager(anyString())).toAnswer(new Answer<WorkspaceLockManager>() {
+ public WorkspaceLockManager answer( InvocationOnMock invocation ) throws Throwable {
+ return workspaceLockManager;
+ }
+ });
+
initializeOptions();
stub(repository.getOptions()).toReturn(options);
@@ -148,7 +164,7 @@
// Set up the session attributes ...
sessionAttributes = new HashMap<String, Object>();
sessionAttributes.put("attribute1", "value1");
-
+
// Now create the workspace ...
SecurityContext mockSecurityContext = new MockSecurityContext(null,
Collections.singleton(JcrSession.DNA_WRITE_PERMISSION));
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -432,7 +432,7 @@
private void testDescriptorValues( Repository repository ) {
assertThat(repository.getDescriptor(Repository.LEVEL_1_SUPPORTED), is("true"));
assertThat(repository.getDescriptor(Repository.LEVEL_2_SUPPORTED), is("true"));
- assertThat(repository.getDescriptor(Repository.OPTION_LOCKING_SUPPORTED), is("false"));
+ assertThat(repository.getDescriptor(Repository.OPTION_LOCKING_SUPPORTED), is("true"));
assertThat(repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED), is("false"));
assertThat(repository.getDescriptor(Repository.OPTION_QUERY_SQL_SUPPORTED), is("false"));
assertThat(repository.getDescriptor(Repository.OPTION_TRANSACTIONS_SUPPORTED), is("false"));
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -58,6 +58,7 @@
import javax.security.auth.login.LoginContext;
import org.jboss.dna.graph.JaasSecurityContext;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -98,7 +99,7 @@
}
}
- @Test( expected = UnsupportedOperationException.class )
+ @Test( expected = IllegalArgumentException.class )
public void shouldNotAllowAddLockToken() throws Exception {
session.addLockToken(null);
}
@@ -158,6 +159,9 @@
sessionAttributes = new HashMap<String, Object>();
// Now create the workspace ...
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+ Path locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, DnaLexicon.LOCKS);
+ WorkspaceLockManager wlm = new WorkspaceLockManager(context, repository, workspaceName, locksPath);
workspace = new JcrWorkspace(repository, workspaceName, context, sessionAttributes);
// Create the session and log in ...
@@ -394,7 +398,7 @@
@Test( expected = UnsupportedRepositoryOperationException.class )
public void shouldNotProvideUuidIfNotReferenceable() throws Exception {
// The b node was not set up to be referenceable in this test, but does have a mixin type
- Node node = session.getRootNode().getNode("a");
+ Node node = session.getRootNode().getNode("a").getNode("b").getNode("c");
node.getUUID();
}
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -64,7 +64,9 @@
import org.apache.jackrabbit.test.api.SetValueValueFormatExceptionTest;
import org.apache.jackrabbit.test.api.SetValueVersionExceptionTest;
import org.apache.jackrabbit.test.api.ValueFactoryTest;
+import org.apache.jackrabbit.test.api.WorkspaceCloneReferenceableTest;
import org.apache.jackrabbit.test.api.WorkspaceCloneSameNameSibsTest;
+import org.apache.jackrabbit.test.api.WorkspaceCloneTest;
import org.apache.jackrabbit.test.api.WorkspaceCloneVersionableTest;
import org.apache.jackrabbit.test.api.WorkspaceCopyBetweenWorkspacesReferenceableTest;
import org.apache.jackrabbit.test.api.WorkspaceCopyBetweenWorkspacesSameNameSibsTest;
@@ -72,9 +74,11 @@
import org.apache.jackrabbit.test.api.WorkspaceCopyBetweenWorkspacesVersionableTest;
import org.apache.jackrabbit.test.api.WorkspaceCopyReferenceableTest;
import org.apache.jackrabbit.test.api.WorkspaceCopySameNameSibsTest;
+import org.apache.jackrabbit.test.api.WorkspaceCopyTest;
import org.apache.jackrabbit.test.api.WorkspaceCopyVersionableTest;
import org.apache.jackrabbit.test.api.WorkspaceMoveReferenceableTest;
import org.apache.jackrabbit.test.api.WorkspaceMoveSameNameSibsTest;
+import org.apache.jackrabbit.test.api.WorkspaceMoveTest;
import org.apache.jackrabbit.test.api.WorkspaceMoveVersionableTest;
/**
@@ -230,9 +234,9 @@
addTestSuite(NodeCanAddMixinTest.class);
addTestSuite(NodeRemoveMixinTest.class);
- // DNA-518 addTestSuite(WorkspaceCloneReferenceableTest.class);
+ addTestSuite(WorkspaceCloneReferenceableTest.class);
addTestSuite(WorkspaceCloneSameNameSibsTest.class);
- // addTestSuite(WorkspaceCloneTest.class);
+ addTestSuite(WorkspaceCloneTest.class);
addTestSuite(WorkspaceCloneVersionableTest.class);
addTestSuite(WorkspaceCopyBetweenWorkspacesReferenceableTest.class);
addTestSuite(WorkspaceCopyBetweenWorkspacesSameNameSibsTest.class);
@@ -240,11 +244,11 @@
addTestSuite(WorkspaceCopyBetweenWorkspacesVersionableTest.class);
addTestSuite(WorkspaceCopyReferenceableTest.class);
addTestSuite(WorkspaceCopySameNameSibsTest.class);
- // addTestSuite(WorkspaceCopyTest.class);
+ addTestSuite(WorkspaceCopyTest.class);
addTestSuite(WorkspaceCopyVersionableTest.class);
addTestSuite(WorkspaceMoveReferenceableTest.class);
addTestSuite(WorkspaceMoveSameNameSibsTest.class);
- // addTestSuite(WorkspaceMoveTest.class);
+ addTestSuite(WorkspaceMoveTest.class);
addTestSuite(WorkspaceMoveVersionableTest.class);
addTestSuite(RepositoryLoginTest.class);
@@ -269,7 +273,7 @@
// addTest(org.apache.jackrabbit.test.api.observation.TestAll.suite());
// addTest(org.apache.jackrabbit.test.api.version.TestAll.suite());
- // addTest(org.apache.jackrabbit.test.api.lock.TestAll.suite());
+ addTest(org.apache.jackrabbit.test.api.lock.TestAll.suite());
addTest(org.apache.jackrabbit.test.api.util.TestAll.suite());
}
}
Modified: trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java
===================================================================
--- trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java 2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java 2009-10-19 19:54:55 UTC (rev 1295)
@@ -236,8 +236,9 @@
assertThat(properties.getString("jcr:primaryType"), is("dna:system"));
JSONArray namespaces = system.getJSONArray("children");
- assertThat(namespaces.length(), is(1));
+ assertThat(namespaces.length(), is(2));
assertThat(namespaces.getString(0), is("dna:namespaces"));
+ assertThat(namespaces.getString(1), is("dna:locks"));
assertThat(connection.getResponseCode(), is(HttpURLConnection.HTTP_OK));
connection.disconnect();
@@ -260,10 +261,12 @@
assertThat(properties.getString("jcr:primaryType"), is("dna:system"));
JSONObject children = body.getJSONObject("children");
- assertThat(children.length(), is(1));
+ assertThat(children.length(), is(2));
JSONObject namespaces = children.getJSONObject("dna:namespaces");
assertThat(namespaces.length(), is(2));
+ JSONObject locks = children.getJSONObject("dna:locks");
+ assertThat(locks.length(), is(1));
properties = namespaces.getJSONObject("properties");
assertThat(properties.length(), is(1));
14 years, 7 months
DNA SVN: r1294 - in trunk: dna-common/src/test/java/org/jboss/dna/common/collection and 10 other directories.
by dna-commits@lists.jboss.org
Author: rhauch
Date: 2009-10-15 15:57:20 -0400 (Thu, 15 Oct 2009)
New Revision: 1294
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java
Modified:
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java
trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
Log:
DNA-467 Added more query optimizer rules, and changed the way that the query plans are handled so that access nodes always are in the shape of a valid query.
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -47,17 +47,17 @@
addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, params, null, null, throwable));
}
- public void addError( I18n message,
- String resource,
+ public void addError( String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, params, resource, location, null));
}
public void addError( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, params, resource, location, throwable));
}
@@ -76,18 +76,18 @@
}
public void addError( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.ERROR, code, message, params, resource, location, null));
}
public void addError( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.ERROR, code, message, params, resource, location, throwable));
}
@@ -103,17 +103,17 @@
addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message, params, null, null, throwable));
}
- public void addWarning( I18n message,
- String resource,
+ public void addWarning( String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message, params, resource, location, null));
}
public void addWarning( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message, params, resource, location, throwable));
}
@@ -132,18 +132,18 @@
}
public void addWarning( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.WARNING, code, message, params, resource, location, null));
}
public void addWarning( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.WARNING, code, message, params, resource, location, throwable));
}
@@ -159,17 +159,17 @@
addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, params, null, null, throwable));
}
- public void addInfo( I18n message,
- String resource,
+ public void addInfo( String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, params, resource, location, null));
}
public void addInfo( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, params, resource, location, throwable));
}
@@ -188,18 +188,18 @@
}
public void addInfo( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.INFO, code, message, params, resource, location, null));
}
public void addInfo( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
addProblem(new Problem(Problem.Status.INFO, code, message, params, resource, location, throwable));
}
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -54,12 +54,12 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addError(org.jboss.dna.common.i18n.I18n, java.lang.String, java.lang.String,
+ * @see org.jboss.dna.common.collection.Problems#addError(java.lang.String, java.lang.String, org.jboss.dna.common.i18n.I18n,
* java.lang.Object[])
*/
- public void addError( I18n message,
- String resource,
+ public void addError( String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -78,13 +78,12 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addError(int, org.jboss.dna.common.i18n.I18n, java.lang.String,
- * java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addError(int, String, String, I18n, Object...)
*/
public void addError( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -104,13 +103,13 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, org.jboss.dna.common.i18n.I18n,
- * java.lang.String, java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n,java.lang.Object[])
*/
public void addError( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -131,14 +130,14 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, int, org.jboss.dna.common.i18n.I18n,
- * java.lang.String, java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, int, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n, java.lang.Object[])
*/
public void addError( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -156,12 +155,12 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addInfo(org.jboss.dna.common.i18n.I18n, java.lang.String, java.lang.String,
+ * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.String, java.lang.String, org.jboss.dna.common.i18n.I18n,
* java.lang.Object[])
*/
- public void addInfo( I18n message,
- String resource,
+ public void addInfo( String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -180,13 +179,13 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addInfo(int, org.jboss.dna.common.i18n.I18n, java.lang.String,
- * java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addInfo(int, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n, java.lang.Object[])
*/
public void addInfo( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -206,13 +205,13 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, org.jboss.dna.common.i18n.I18n,
- * java.lang.String, java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n, java.lang.Object[])
*/
public void addInfo( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -233,14 +232,14 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, int, org.jboss.dna.common.i18n.I18n,
- * java.lang.String, java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, int, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n, java.lang.Object[])
*/
public void addInfo( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -258,12 +257,12 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addWarning(org.jboss.dna.common.i18n.I18n, java.lang.String,
- * java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n, java.lang.Object[])
*/
- public void addWarning( I18n message,
- String resource,
+ public void addWarning( String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -282,13 +281,13 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addWarning(int, org.jboss.dna.common.i18n.I18n, java.lang.String,
- * java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addWarning(int, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n, java.lang.Object[])
*/
public void addWarning( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -308,13 +307,13 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, org.jboss.dna.common.i18n.I18n,
- * java.lang.String, java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, java.lang.String, java.lang.String ,
+ * org.jboss.dna.common.i18n.I18n,java.lang.Object[])
*/
public void addWarning( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
@@ -335,14 +334,14 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, int, org.jboss.dna.common.i18n.I18n,
- * java.lang.String, java.lang.String, java.lang.Object[])
+ * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, int, java.lang.String, java.lang.String,
+ * org.jboss.dna.common.i18n.I18n,java.lang.Object[])
*/
public void addWarning( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params ) {
throw new UnsupportedOperationException();
}
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -56,14 +56,14 @@
* Add an error message with a description of the resource, its location, and the parameters that should be used when
* localizing the message
*
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
- void addError( I18n message,
- String resource,
+ void addError( String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -71,15 +71,15 @@
* when localizing the message
*
* @param throwable the exception that represents the error; may be null
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addError( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -111,15 +111,15 @@
* localizing the message
*
* @param code the error code
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addError( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -128,16 +128,16 @@
*
* @param throwable the exception that represents the error; may be null
* @param code the error code
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addError( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -164,14 +164,14 @@
* Add a warning message with a description of the resource, its location, and the parameters that should be used when
* localizing the message
*
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
- void addWarning( I18n message,
- String resource,
+ void addWarning( String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -179,15 +179,15 @@
* used when localizing the message
*
* @param throwable the exception that represents the warning; may be null
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addWarning( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -219,15 +219,15 @@
* localizing the message
*
* @param code the problem code
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addWarning( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -236,16 +236,16 @@
*
* @param throwable the exception that represents the warning; may be null
* @param code the problem code
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addWarning( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -272,14 +272,14 @@
* Add an informational message with a description of the resource, its location, and the parameters that should be used when
* localizing the message
*
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
- void addInfo( I18n message,
- String resource,
+ void addInfo( String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -287,15 +287,15 @@
* be used when localizing the message
*
* @param throwable the exception that represents the problem; may be null
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addInfo( Throwable throwable,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -327,15 +327,15 @@
* localizing the message
*
* @param code the problem code
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addInfo( int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
@@ -344,16 +344,16 @@
*
* @param throwable the exception that represents the problem; may be null
* @param code the problem code
- * @param message the internationalized message describing the problem
* @param resource the description of the resource; may be null
* @param location the location of the resource; may be null
+ * @param message the internationalized message describing the problem
* @param params the values for the parameters in the message
*/
void addInfo( Throwable throwable,
int code,
- I18n message,
String resource,
String location,
+ I18n message,
Object... params );
/**
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -23,6 +23,7 @@
*/
package org.jboss.dna.common.collection;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import net.jcip.annotations.NotThreadSafe;
@@ -57,4 +58,23 @@
protected List<Problem> getProblems() {
return this.problems != null ? problems : EMPTY_PROBLEMS;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ Iterator<Problem> iter = getProblems().iterator();
+ if (iter.hasNext()) {
+ sb.append(iter.next());
+ while (iter.hasNext()) {
+ sb.append("\n");
+ sb.append(iter.next());
+ }
+ }
+ return sb.toString();
+ }
}
Modified: trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java
===================================================================
--- trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -104,7 +104,7 @@
@Test
public void shouldAddErrorByMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
error = new Problem(Status.ERROR, Problem.DEFAULT_CODE, message, new Object[] {"error msg"}, resource, location, null);
- problems.addError(error.getMessage(), error.getResource(), error.getLocation(), error.getParameters());
+ problems.addError(error.getResource(), error.getLocation(), error.getMessage(), error.getParameters());
assertThat(problems.iterator().next(), is(error));
assertThat(problems.isEmpty(), is(false));
assertThat(problems.size(), is(1));
@@ -113,7 +113,7 @@
@Test
public void shouldAddErrorByCodeAndMesssageAndResourceAndLocationAndParameters() {
error = new Problem(Status.ERROR, 1, message, new Object[] {"error msg"}, resource, location, null);
- problems.addError(error.getCode(), error.getMessage(), error.getResource(), error.getLocation(), error.getParameters());
+ problems.addError(error.getCode(), error.getResource(), error.getLocation(), error.getMessage(), error.getParameters());
assertThat(problems.iterator().next(), is(error));
assertThat(problems.isEmpty(), is(false));
assertThat(problems.size(), is(1));
@@ -124,9 +124,9 @@
error = new Problem(Status.ERROR, Problem.DEFAULT_CODE, message, new Object[] {"error msg"}, resource, location,
throwable);
problems.addError(error.getThrowable(),
- error.getMessage(),
error.getResource(),
error.getLocation(),
+ error.getMessage(),
error.getParameters());
assertThat(problems.iterator().next(), is(error));
assertThat(problems.isEmpty(), is(false));
@@ -138,9 +138,9 @@
error = new Problem(Status.ERROR, 1, message, new Object[] {"error msg"}, resource, location, throwable);
problems.addError(error.getThrowable(),
error.getCode(),
- error.getMessage(),
error.getResource(),
error.getLocation(),
+ error.getMessage(),
error.getParameters());
assertThat(problems.iterator().next(), is(error));
assertThat(problems.isEmpty(), is(false));
@@ -186,7 +186,7 @@
public void shouldAddWarningByMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
warning = new Problem(Status.WARNING, Problem.DEFAULT_CODE, message, new Object[] {"warning msg"}, resource, location,
null);
- problems.addWarning(warning.getMessage(), warning.getResource(), warning.getLocation(), warning.getParameters());
+ problems.addWarning(warning.getResource(), warning.getLocation(), warning.getMessage(), warning.getParameters());
assertThat(problems.iterator().next(), is(warning));
assertThat(problems.isEmpty(), is(false));
assertThat(problems.size(), is(1));
@@ -196,9 +196,9 @@
public void shouldAddWarningByCodeAndMesssageAndResourceAndLocationAndParameters() {
warning = new Problem(Status.WARNING, 1, message, new Object[] {"warning msg"}, resource, location, null);
problems.addWarning(warning.getCode(),
- warning.getMessage(),
warning.getResource(),
warning.getLocation(),
+ warning.getMessage(),
warning.getParameters());
assertThat(problems.iterator().next(), is(warning));
assertThat(problems.isEmpty(), is(false));
@@ -210,9 +210,9 @@
warning = new Problem(Status.WARNING, Problem.DEFAULT_CODE, message, new Object[] {"warning msg"}, resource, location,
throwable);
problems.addWarning(warning.getThrowable(),
- warning.getMessage(),
warning.getResource(),
warning.getLocation(),
+ warning.getMessage(),
warning.getParameters());
assertThat(problems.iterator().next(), is(warning));
assertThat(problems.isEmpty(), is(false));
@@ -224,9 +224,9 @@
warning = new Problem(Status.WARNING, 1, message, new Object[] {"warning msg"}, resource, location, throwable);
problems.addWarning(warning.getThrowable(),
warning.getCode(),
- warning.getMessage(),
warning.getResource(),
warning.getLocation(),
+ warning.getMessage(),
warning.getParameters());
assertThat(problems.iterator().next(), is(warning));
assertThat(problems.isEmpty(), is(false));
@@ -271,7 +271,7 @@
@Test
public void shouldAddInfoByMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
info = new Problem(Status.INFO, Problem.DEFAULT_CODE, message, new Object[] {"info msg"}, resource, location, null);
- problems.addInfo(info.getMessage(), info.getResource(), info.getLocation(), info.getParameters());
+ problems.addInfo(info.getResource(), info.getLocation(), info.getMessage(), info.getParameters());
assertThat(problems.iterator().next(), is(info));
assertThat(problems.isEmpty(), is(false));
assertThat(problems.size(), is(1));
@@ -280,7 +280,7 @@
@Test
public void shouldAddInfoByCodeAndMesssageAndResourceAndLocationAndParameters() {
info = new Problem(Status.INFO, 1, message, new Object[] {"info msg"}, resource, location, null);
- problems.addInfo(info.getCode(), info.getMessage(), info.getResource(), info.getLocation(), info.getParameters());
+ problems.addInfo(info.getCode(), info.getResource(), info.getLocation(), info.getMessage(), info.getParameters());
assertThat(problems.iterator().next(), is(info));
assertThat(problems.isEmpty(), is(false));
assertThat(problems.size(), is(1));
@@ -289,7 +289,7 @@
@Test
public void shouldAddInfoByThrowableAndMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
info = new Problem(Status.INFO, Problem.DEFAULT_CODE, message, new Object[] {"info msg"}, resource, location, throwable);
- problems.addInfo(info.getThrowable(), info.getMessage(), info.getResource(), info.getLocation(), info.getParameters());
+ problems.addInfo(info.getThrowable(), info.getResource(), info.getLocation(), info.getMessage(), info.getParameters());
assertThat(problems.iterator().next(), is(info));
assertThat(problems.isEmpty(), is(false));
assertThat(problems.size(), is(1));
@@ -300,9 +300,9 @@
info = new Problem(Status.INFO, 1, message, new Object[] {"info msg"}, resource, location, throwable);
problems.addInfo(info.getThrowable(),
info.getCode(),
- info.getMessage(),
info.getResource(),
info.getLocation(),
+ info.getMessage(),
info.getParameters());
assertThat(problems.iterator().next(), is(info));
assertThat(problems.isEmpty(), is(false));
@@ -318,9 +318,9 @@
assertThat(problems.size(), is(0));
problems.addWarning(warning.getThrowable(),
warning.getCode(),
- warning.getMessage(),
warning.getResource(),
warning.getLocation(),
+ warning.getMessage(),
warning.getParameters());
assertThat(problems.hasErrors(), is(false));
assertThat(problems.hasWarnings(), is(true));
@@ -329,9 +329,9 @@
assertThat(problems.size(), is(1));
problems.addError(error.getThrowable(),
error.getCode(),
- error.getMessage(),
error.getResource(),
error.getLocation(),
+ error.getMessage(),
error.getParameters());
assertThat(problems.hasErrors(), is(true));
assertThat(problems.hasWarnings(), is(true));
@@ -340,9 +340,9 @@
assertThat(problems.size(), is(2));
problems.addInfo(info.getThrowable(),
info.getCode(),
- info.getMessage(),
info.getResource(),
info.getLocation(),
+ info.getMessage(),
info.getParameters());
assertThat(problems.hasErrors(), is(true));
assertThat(problems.hasWarnings(), is(true));
@@ -351,27 +351,27 @@
assertThat(problems.size(), is(3));
problems.addWarning(warning.getThrowable(),
warning.getCode(),
- warning.getMessage(),
warning.getResource(),
warning.getLocation(),
+ warning.getMessage(),
warning.getParameters());
problems.addError(error.getThrowable(),
error.getCode(),
- error.getMessage(),
error.getResource(),
error.getLocation(),
+ error.getMessage(),
error.getParameters());
problems.addWarning(warning.getThrowable(),
warning.getCode(),
- warning.getMessage(),
warning.getResource(),
warning.getLocation(),
+ warning.getMessage(),
warning.getParameters());
problems.addError(error.getThrowable(),
error.getCode(),
- error.getMessage(),
error.getResource(),
error.getLocation(),
+ error.getMessage(),
error.getParameters());
assertThat(problems.hasErrors(), is(true));
assertThat(problems.hasWarnings(), is(true));
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -23,6 +23,7 @@
*/
package org.jboss.dna.graph.query.model;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -32,6 +33,7 @@
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NamespaceRegistry;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.ValueFactory;
/**
* A set of common visitors that can be reused or extended, and methods that provide easy construction and calling of visitors.
@@ -51,7 +53,7 @@
*/
public static <StrategyVisitor extends Visitor> StrategyVisitor visitAll( Visitable visitable,
StrategyVisitor strategyVisitor ) {
- visitable.accept(new WalkAllVisitor(strategyVisitor));
+ if (visitable != null) visitable.accept(new WalkAllVisitor(strategyVisitor));
return strategyVisitor;
}
@@ -66,7 +68,7 @@
*/
public static <GeneralVisitor extends Visitor> GeneralVisitor visit( Visitable visitable,
GeneralVisitor visitor ) {
- visitable.accept(visitor);
+ if (visitable != null) visitable.accept(visitor);
return visitor;
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -32,8 +32,8 @@
import org.jboss.dna.graph.query.plan.PlanNode.Type;
/**
- * An {@link OptimizerRule optimizer rule} that inserts an ACCESS above each SOURCE node in a query plan. This rule is often the
- * first rule to run against a {@link CanonicalPlanner canonical plan} (see
+ * An {@link OptimizerRule optimizer rule} that inserts an ACCESS above each SOURCE leaf node in a query plan. This rule is often
+ * the first rule to run against a {@link CanonicalPlanner canonical plan} (see
* {@link RuleBasedOptimizer#populateRuleStack(LinkedList, PlanHints)}.
* <p>
* Before:
@@ -72,7 +72,7 @@
LinkedList<OptimizerRule> ruleStack ) {
// On each of the source nodes ...
for (PlanNode source : plan.findAllAtOrBelow(Type.SOURCE)) {
- // The source node should have no children ...
+ // The source node may have children if it is a view ...
if (source.getChildCount() != 0) continue;
// Create the ACCESS node, set the selectors, and insert above the source node ...
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,192 @@
+/*
+ * 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.graph.query.optimize;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitable;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that copies SELECT nodes that apply to one side of a equi-join condition so that they
+ * also apply to the other side fo the equi-join condition.
+ */
+@Immutable
+public class CopyCriteria implements OptimizerRule {
+
+ public static final CopyCriteria INSTANCE = new CopyCriteria();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+ */
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack ) {
+ Set<PlanNode> copiedSelectNodes = new HashSet<PlanNode>();
+
+ for (PlanNode join : plan.findAllAtOrBelow(Type.JOIN)) {
+ // Get the join condition ...
+ JoinCondition joinCondition = join.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+ if (joinCondition instanceof EquiJoinCondition) {
+ EquiJoinCondition equiJoinCondition = (EquiJoinCondition)joinCondition;
+ SelectorName selector1 = equiJoinCondition.getSelector1Name();
+ SelectorName selector2 = equiJoinCondition.getSelector2Name();
+ Name property1 = equiJoinCondition.getProperty1Name();
+ Name property2 = equiJoinCondition.getProperty2Name();
+
+ // Walk up the tree looking for SELECT nodes that apply to one of the sides ...
+ PlanNode node = join.getParent();
+ while (node != null) {
+ if (!copiedSelectNodes.contains(node)) {
+ PlanNode copy = copySelectNode(context, node, selector1, property1, selector2, property2);
+ if (copy != null) {
+ node.insertAsParent(copy);
+ copiedSelectNodes.add(node);
+ copiedSelectNodes.add(copy);
+ } else {
+ copy = copySelectNode(context, node, selector2, property2, selector1, property1);
+ if (copy != null) {
+ node.insertAsParent(copy);
+ copiedSelectNodes.add(node);
+ copiedSelectNodes.add(copy);
+ }
+ }
+ }
+ node = node.getParent();
+ }
+ }
+ }
+ return plan;
+ }
+
+ protected PlanNode copySelectNode( QueryContext context,
+ PlanNode selectNode,
+ SelectorName selectorName,
+ Name propertyName,
+ SelectorName copySelectorName,
+ Name copyPropertyName ) {
+ if (selectNode.isNot(Type.SELECT)) return null;
+ if (selectNode.getSelectors().size() != 1 || !selectNode.getSelectors().contains(selectorName)) return null;
+
+ Constraint constraint = selectNode.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+ Set<Column> columns = getColumnsReferencedBy(constraint, context.getExecutionContext());
+ if (columns.size() != 1) return null;
+ Column column = columns.iterator().next();
+ if (!column.getSelectorName().equals(selectorName)) return null;
+ if (!column.getPropertyName().equals(propertyName)) return null;
+
+ // We know that this constraint ONLY applies to the referenced selector and property,
+ // so we will duplicate this constraint ...
+
+ // Create the new node ...
+ PlanNode copy = new PlanNode(Type.SELECT, copySelectorName);
+
+ // Copy the constraint, but change the references to the copy selector and property ...
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ PlanUtil.ColumnMapping mappings = new PlanUtil.ColumnMapping(selectorName);
+ mappings.map(stringFactory.create(propertyName), new Column(copySelectorName, copyPropertyName,
+ stringFactory.create(copyPropertyName)));
+ Constraint newCriteria = PlanUtil.replaceReferences(context, constraint, mappings, copy);
+ copy.setProperty(Property.SELECT_CRITERIA, newCriteria);
+
+ return copy;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ /**
+ * Get the set of Column objects that represent those columns referenced by the visitable object.
+ *
+ * @param visitable the object to be visited
+ * @param context the context; may not be null
+ * @return the set of Column objects, with column names that always are the string-form of the
+ * {@link Column#getPropertyName() property name}; never null
+ */
+ public static Set<Column> getColumnsReferencedBy( Visitable visitable,
+ ExecutionContext context ) {
+ if (visitable == null) return Collections.emptySet();
+ final ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory();
+ final Set<Column> symbols = new HashSet<Column>();
+ // Walk the entire structure, so only supply a StrategyVisitor (that does no navigation) ...
+ Visitors.visitAll(visitable, new AbstractVisitor() {
+ protected void addColumnFor( SelectorName selectorName,
+ Name property ) {
+ symbols.add(new Column(selectorName, property, stringFactory.create(property)));
+ }
+
+ @Override
+ public void visit( Column column ) {
+ symbols.add(column);
+ }
+
+ @Override
+ public void visit( EquiJoinCondition joinCondition ) {
+ addColumnFor(joinCondition.getSelector1Name(), joinCondition.getProperty1Name());
+ addColumnFor(joinCondition.getSelector2Name(), joinCondition.getProperty2Name());
+ }
+
+ @Override
+ public void visit( PropertyExistence prop ) {
+ addColumnFor(prop.getSelectorName(), prop.getPropertyName());
+ }
+
+ @Override
+ public void visit( PropertyValue prop ) {
+ addColumnFor(prop.getSelectorName(), prop.getPropertyName());
+ }
+ });
+ return symbols;
+ }
+
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -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.graph.query.optimize;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * This rule attempts to ensure the proper location of {@link Type#PROJECT} nodes. For example, every {@link Type#ACCESS} node
+ * needs to have a PROJECT node below it and above any other nodes (e.g., {@link Type#SELECT} or {@link Type#SOURCE} nodes). This
+ * rule ensures that the PROJECT exists, but it also attempts to reduce any unnecessary columns in existing PROJECT nodes.
+ */
+public class PushProjects implements OptimizerRule {
+
+ public static final PushProjects INSTANCE = new PushProjects();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+ */
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack ) {
+
+ // Find all of the ACCESS nodes to make sure there is a PROJECT ...
+ for (PlanNode access : plan.findAllAtOrBelow(Type.ACCESS)) {
+ // Find the first node that is below any LIMIT, SORT, or DUP_REMOVE ...
+ PlanNode parentOfProject = access;
+ while (parentOfProject.isOneOf(Type.LIMIT, Type.SORT, Type.DUP_REMOVE)) {
+ parentOfProject = parentOfProject.getParent();
+ }
+
+ // Is there already a PROJECT here ?
+ assert parentOfProject.getChildCount() == 1; // should only have one child ...
+ PlanNode child = parentOfProject.getFirstChild(); // should only have one child ...
+ if (child.is(Type.PROJECT)) continue;
+
+ // If the parent of the ACCESS node is a PROJECT, then we can simply move it to here ...
+ PlanNode accessParent = access.getParent();
+ if (accessParent != null && accessParent.is(Type.PROJECT)) {
+ PlanNode project = accessParent;
+ if (project == plan) {
+ // The PROJECT node is the root, so the ACCESS node will be the root ...
+ plan = access;
+ access.removeFromParent();
+ } else {
+ project.extractFromParent();
+ }
+ child.insertAsParent(project);
+ if (plan == access) return plan;
+ continue;
+ }
+
+ // There is no PROJECT, so find the columns that are required by the plan above this point ...
+ List<Column> requiredColumns = PlanUtil.findRequiredColumns(context, child);
+
+ // And insert the PROJECT ...
+ PlanNode projectNode = new PlanNode(Type.PROJECT);
+ projectNode.setProperty(Property.PROJECT_COLUMNS, requiredColumns);
+ projectNode.addSelectors(getSelectorsFor(requiredColumns));
+ child.insertAsParent(projectNode);
+ }
+ return plan;
+ }
+
+ protected Set<SelectorName> getSelectorsFor( List<Column> columns ) {
+ Set<SelectorName> selectors = new HashSet<SelectorName>();
+ for (Column column : columns) {
+ selectors.add(column.getSelectorName());
+ }
+ return selectors;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -64,10 +64,10 @@
* <pre>
* ...
* |
+ * ACCESS
+ * |
* PROJECT with the list of columns being SELECTed
* |
- * ACCESS
- * |
* SELECT1
* | One or more SELECT plan nodes that each have
* SELECT2 a single non-join constraint that are then all AND-ed
@@ -104,6 +104,9 @@
* ACCESS ACCESS
* ('s1') ('s2')
* | |
+ * PROJECT PROJECT
+ * ('s1') ('s2')
+ * | |
* SOURCE SOURCE
* ('s1') ('s2')
* </pre>
@@ -123,6 +126,9 @@
* ACCESS ACCESS
* ('s1') ('s2')
* | |
+ * PROJECT PROJECT
+ * ('s1') ('s2')
+ * | |
* SELECT1 SELECT2
* ('s1') ('s2')
* | |
@@ -134,6 +140,9 @@
* </pre>
*
* </p>
+ * <p>
+ * Also, any SELECT that applies to one side of an equi-join will be applied to <i>both</i> sides of the JOIN.
+ * </p>
*/
@Immutable
public class PushSelectCriteria implements OptimizerRule {
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,132 @@
+/*
+ * 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.graph.query.optimize;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that replaces any SOURCE nodes that happen to be {@link View views}. This rewriting
+ * changes all of the elements of the plan that reference the SOURCE and it's columns, including criteria, project nodes, etc.
+ */
+@Immutable
+public class ReplaceViews implements OptimizerRule {
+
+ public static final ReplaceViews INSTANCE = new ReplaceViews();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+ */
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack ) {
+ // Prepare a cache of the view plans ...
+ Map<SelectorName, PlanNode> viewPlanCache = new HashMap<SelectorName, PlanNode>();
+ CanonicalPlanner planner = new CanonicalPlanner();
+
+ // Prepare the maps that will record the old-to-new mappings from the old view SOURCE nodes to the table SOURCEs ...
+
+ // For each of the SOURCE nodes ...
+ Schemata schemata = context.getSchemata();
+ Set<PlanNode> processedSources = new HashSet<PlanNode>();
+ boolean foundViews = false;
+ do {
+ foundViews = false;
+ for (PlanNode sourceNode : plan.findAllAtOrBelow(Type.SOURCE)) {
+ if (processedSources.contains(sourceNode)) continue;
+ processedSources.add(sourceNode);
+
+ // Resolve the node to find the definition in the schemata ...
+ SelectorName tableName = sourceNode.getProperty(Property.SOURCE_NAME, SelectorName.class);
+ Table table = schemata.getTable(tableName);
+ if (table instanceof View) {
+ View view = (View)table;
+ PlanNode viewPlan = viewPlanCache.get(tableName);
+ if (viewPlan == null) {
+ viewPlan = planner.createPlan(context, view.getDefinition());
+ if (viewPlan != null) viewPlanCache.put(tableName, viewPlan);
+ }
+ if (viewPlan == null) continue; // there were likely errors when creating the plan
+ viewPlan = viewPlan.clone();
+
+ // Insert the view plan under the parent SOURCE node ...
+ sourceNode.addLastChild(viewPlan);
+
+ // Update the plan above this node to replace references to the view columns with references to the
+ // tables/views used by the view ...
+ PlanUtil.ColumnMapping viewMappings = PlanUtil.createMappingFor(view, viewPlan);
+ PlanUtil.replaceViewReferences(context, viewPlan, viewMappings);
+
+ if (viewPlan.is(Type.PROJECT)) {
+ // The PROJECT from the plan may actually not be needed if there is another PROJECT above it ...
+ PlanNode node = viewPlan.getParent();
+ while (node != null) {
+ if (node.is(Type.PROJECT) && viewPlan.getSelectors().containsAll(node.getSelectors())) {
+ viewPlan.extractFromParent();
+ break;
+ }
+ node = node.getParent();
+ }
+ }
+ foundViews = true;
+ }
+ }
+ } while (foundViews);
+
+ if (foundViews) {
+ // We re-wrote at least one SOURCE, but the resulting plan tree for the view could actually reference
+ // other views. Therefore, re-run this rule ...
+ ruleStack.addFirst(this);
+ }
+ return plan;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -24,46 +24,17 @@
package org.jboss.dna.graph.query.optimize;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
-import java.util.Set;
import net.jcip.annotations.Immutable;
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.model.And;
-import org.jboss.dna.graph.query.model.ChildNode;
-import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
-import org.jboss.dna.graph.query.model.Column;
-import org.jboss.dna.graph.query.model.Comparison;
-import org.jboss.dna.graph.query.model.Constraint;
-import org.jboss.dna.graph.query.model.DescendantNode;
-import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
-import org.jboss.dna.graph.query.model.DynamicOperand;
import org.jboss.dna.graph.query.model.EquiJoinCondition;
-import org.jboss.dna.graph.query.model.FullTextSearch;
-import org.jboss.dna.graph.query.model.FullTextSearchScore;
import org.jboss.dna.graph.query.model.JoinCondition;
-import org.jboss.dna.graph.query.model.Length;
-import org.jboss.dna.graph.query.model.LowerCase;
-import org.jboss.dna.graph.query.model.NodeDepth;
-import org.jboss.dna.graph.query.model.NodeLocalName;
-import org.jboss.dna.graph.query.model.NodeName;
-import org.jboss.dna.graph.query.model.NodePath;
-import org.jboss.dna.graph.query.model.Not;
-import org.jboss.dna.graph.query.model.Or;
-import org.jboss.dna.graph.query.model.Ordering;
-import org.jboss.dna.graph.query.model.PropertyExistence;
-import org.jboss.dna.graph.query.model.PropertyValue;
-import org.jboss.dna.graph.query.model.SameNode;
-import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
import org.jboss.dna.graph.query.model.SelectorName;
-import org.jboss.dna.graph.query.model.StaticOperand;
-import org.jboss.dna.graph.query.model.UpperCase;
import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
import org.jboss.dna.graph.query.plan.PlanNode.Property;
import org.jboss.dna.graph.query.plan.PlanNode.Type;
import org.jboss.dna.graph.query.validate.Schemata;
@@ -170,7 +141,7 @@
ruleStack.addFirst(this);
// Now rewrite the various portions of the plan that make use of the now-removed selectors ...
- replaceReferencesToRemovedSource(context, plan, rewrittenSelectors);
+ PlanUtil.replaceReferencesToRemovedSource(context, plan, rewrittenSelectors);
} else {
// There are no-untouched JOIN nodes, which means the sole JOIN node was rewritten as a single SOURCE node
assert plan.findAllAtOrBelow(Type.JOIN).isEmpty();
@@ -206,255 +177,6 @@
}
}
- protected void replaceReferencesToRemovedSource( QueryContext context,
- PlanNode planNode,
- Map<SelectorName, SelectorName> rewrittenSelectors ) {
- switch (planNode.getType()) {
- case PROJECT:
- List<Column> columns = planNode.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
- for (int i = 0; i != columns.size(); ++i) {
- Column column = columns.get(i);
- SelectorName replacement = rewrittenSelectors.get(column.getSelectorName());
- if (replacement != null) {
- columns.set(i, new Column(replacement, column.getPropertyName(), column.getColumnName()));
- }
- }
- break;
- case SELECT:
- Constraint constraint = planNode.getProperty(Property.SELECT_CRITERIA, Constraint.class);
- Constraint newConstraint = replaceReferencesToRemovedSource(context, constraint, rewrittenSelectors);
- if (constraint != newConstraint) {
- planNode.setProperty(Property.SELECT_CRITERIA, newConstraint);
- }
- break;
- case SORT:
- List<Object> orderBys = planNode.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
- if (orderBys != null && !orderBys.isEmpty()) {
- if (orderBys.get(0) instanceof SelectorName) {
- for (int i = 0; i != orderBys.size(); ++i) {
- SelectorName selectorName = (SelectorName)orderBys.get(i);
- SelectorName replacement = rewrittenSelectors.get(selectorName);
- if (replacement != null) {
- orderBys.set(i, replacement);
- }
- }
- } else {
- for (int i = 0; i != orderBys.size(); ++i) {
- Ordering ordering = (Ordering)orderBys.get(i);
- DynamicOperand operand = ordering.getOperand();
- orderBys.set(i, replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
- }
- }
- }
- break;
- case JOIN:
- // Update the join condition ...
- JoinCondition joinCondition = planNode.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
- JoinCondition newCondition = replaceReferencesToRemovedSource(context, joinCondition, rewrittenSelectors);
- if (joinCondition != newCondition) {
- planNode.setProperty(Property.JOIN_CONDITION, newCondition);
- }
-
- // Update the join criteria (if there are some) ...
- List<Constraint> constraints = planNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
- if (constraints != null && !constraints.isEmpty()) {
- for (int i = 0; i != constraints.size(); ++i) {
- Constraint old = constraints.get(i);
- Constraint replacement = replaceReferencesToRemovedSource(context, old, rewrittenSelectors);
- if (replacement != old) {
- constraints.set(i, replacement);
- }
- }
- }
- break;
- case GROUP:
- case SET_OPERATION:
- case DUP_REMOVE:
- case LIMIT:
- case NULL:
- case SOURCE:
- case ACCESS:
- // None of these have to be changed ...
- break;
- }
-
- // Update the selectors referenced by the node ...
- Set<SelectorName> selectorsToAdd = null;
- for (Iterator<SelectorName> iter = planNode.getSelectors().iterator(); iter.hasNext();) {
- SelectorName replacement = rewrittenSelectors.get(iter.next());
- if (replacement != null) {
- iter.remove();
- if (selectorsToAdd == null) selectorsToAdd = new HashSet<SelectorName>();
- selectorsToAdd.add(replacement);
- }
- }
- if (selectorsToAdd != null) planNode.getSelectors().addAll(selectorsToAdd);
-
- // Now call recursively on the children ...
- for (PlanNode child : planNode) {
- replaceReferencesToRemovedSource(context, child, rewrittenSelectors);
- }
- }
-
- protected DynamicOperand replaceReferencesToRemovedSource( QueryContext context,
- DynamicOperand operand,
- Map<SelectorName, SelectorName> rewrittenSelectors ) {
- if (operand instanceof FullTextSearchScore) {
- FullTextSearchScore score = (FullTextSearchScore)operand;
- SelectorName replacement = rewrittenSelectors.get(score.getSelectorName());
- if (replacement == null) return score;
- return new FullTextSearchScore(replacement);
- }
- if (operand instanceof Length) {
- Length operation = (Length)operand;
- PropertyValue wrapped = operation.getPropertyValue();
- SelectorName replacement = rewrittenSelectors.get(wrapped.getSelectorName());
- if (replacement == null) return operand;
- return new Length(new PropertyValue(replacement, wrapped.getPropertyName()));
- }
- if (operand instanceof LowerCase) {
- LowerCase operation = (LowerCase)operand;
- SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
- if (replacement == null) return operand;
- return new LowerCase(replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
- }
- if (operand instanceof UpperCase) {
- UpperCase operation = (UpperCase)operand;
- SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
- if (replacement == null) return operand;
- return new UpperCase(replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
- }
- if (operand instanceof NodeName) {
- NodeName name = (NodeName)operand;
- SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
- if (replacement == null) return name;
- return new NodeName(replacement);
- }
- if (operand instanceof NodeLocalName) {
- NodeLocalName name = (NodeLocalName)operand;
- SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
- if (replacement == null) return name;
- return new NodeLocalName(replacement);
- }
- if (operand instanceof PropertyValue) {
- PropertyValue value = (PropertyValue)operand;
- SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
- if (replacement == null) return operand;
- return new PropertyValue(replacement, value.getPropertyName());
- }
- if (operand instanceof NodeDepth) {
- NodeDepth depth = (NodeDepth)operand;
- SelectorName replacement = rewrittenSelectors.get(depth.getSelectorName());
- if (replacement == null) return operand;
- return new NodeDepth(replacement);
- }
- if (operand instanceof NodePath) {
- NodePath path = (NodePath)operand;
- SelectorName replacement = rewrittenSelectors.get(path.getSelectorName());
- if (replacement == null) return operand;
- return new NodePath(replacement);
- }
- return operand;
- }
-
- protected Constraint replaceReferencesToRemovedSource( QueryContext context,
- Constraint constraint,
- Map<SelectorName, SelectorName> rewrittenSelectors ) {
- if (constraint instanceof And) {
- And and = (And)constraint;
- Constraint left = replaceReferencesToRemovedSource(context, and.getLeft(), rewrittenSelectors);
- Constraint right = replaceReferencesToRemovedSource(context, and.getRight(), rewrittenSelectors);
- if (left == and.getLeft() && right == and.getRight()) return and;
- return new And(left, right);
- }
- if (constraint instanceof Or) {
- Or or = (Or)constraint;
- Constraint left = replaceReferencesToRemovedSource(context, or.getLeft(), rewrittenSelectors);
- Constraint right = replaceReferencesToRemovedSource(context, or.getRight(), rewrittenSelectors);
- if (left == or.getLeft() && right == or.getRight()) return or;
- return new Or(left, right);
- }
- if (constraint instanceof Not) {
- Not not = (Not)constraint;
- Constraint wrapped = replaceReferencesToRemovedSource(context, not.getConstraint(), rewrittenSelectors);
- if (wrapped == not.getConstraint()) return not;
- return new Not(wrapped);
- }
- if (constraint instanceof SameNode) {
- SameNode sameNode = (SameNode)constraint;
- SelectorName replacement = rewrittenSelectors.get(sameNode.getSelectorName());
- if (replacement == null) return sameNode;
- return new SameNode(replacement, sameNode.getPath());
- }
- if (constraint instanceof ChildNode) {
- ChildNode childNode = (ChildNode)constraint;
- SelectorName replacement = rewrittenSelectors.get(childNode.getSelectorName());
- if (replacement == null) return childNode;
- return new ChildNode(replacement, childNode.getParentPath());
- }
- if (constraint instanceof DescendantNode) {
- DescendantNode descendantNode = (DescendantNode)constraint;
- SelectorName replacement = rewrittenSelectors.get(descendantNode.getSelectorName());
- if (replacement == null) return descendantNode;
- return new DescendantNode(replacement, descendantNode.getAncestorPath());
- }
- if (constraint instanceof PropertyExistence) {
- PropertyExistence existence = (PropertyExistence)constraint;
- SelectorName replacement = rewrittenSelectors.get(existence.getSelectorName());
- if (replacement == null) return existence;
- return new PropertyExistence(replacement, existence.getPropertyName());
- }
- if (constraint instanceof FullTextSearch) {
- FullTextSearch search = (FullTextSearch)constraint;
- SelectorName replacement = rewrittenSelectors.get(search.getSelectorName());
- if (replacement == null) return search;
- return new PropertyExistence(replacement, search.getPropertyName());
- }
- if (constraint instanceof Comparison) {
- Comparison comparison = (Comparison)constraint;
- DynamicOperand lhs = comparison.getOperand1();
- StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector
- DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors);
- if (lhs == newLhs) return comparison;
- return new Comparison(newLhs, comparison.getOperator(), rhs);
- }
- return constraint;
- }
-
- protected JoinCondition replaceReferencesToRemovedSource( QueryContext context,
- JoinCondition joinCondition,
- Map<SelectorName, SelectorName> rewrittenSelectors ) {
- if (joinCondition instanceof EquiJoinCondition) {
- EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
- SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
- SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
- if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
- return new EquiJoinCondition(replacement1, condition.getProperty1Name(), replacement2, condition.getProperty2Name());
- }
- if (joinCondition instanceof SameNodeJoinCondition) {
- SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
- SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
- SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
- if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
- return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
- }
- if (joinCondition instanceof ChildNodeJoinCondition) {
- ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
- SelectorName childSelector = rewrittenSelectors.get(condition.getChildSelectorName());
- SelectorName parentSelector = rewrittenSelectors.get(condition.getParentSelectorName());
- if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
- return new ChildNodeJoinCondition(parentSelector, childSelector);
- }
- if (joinCondition instanceof DescendantNodeJoinCondition) {
- DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
- SelectorName ancestor = rewrittenSelectors.get(condition.getAncestorSelectorName());
- SelectorName descendant = rewrittenSelectors.get(condition.getDescendantSelectorName());
- if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
- return new ChildNodeJoinCondition(ancestor, descendant);
- }
- return joinCondition;
- }
-
/**
* {@inheritDoc}
*
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -72,10 +72,15 @@
if (hints.hasJoin) {
ruleStack.addFirst(ChooseJoinAlgorithm.USE_ONLY_NESTED_JOIN_ALGORITHM);
}
+ ruleStack.addFirst(PushProjects.INSTANCE);
if (hints.hasCriteria) {
ruleStack.addFirst(PushSelectCriteria.INSTANCE);
}
ruleStack.addFirst(AddAccessNodes.INSTANCE);
ruleStack.addFirst(RightOuterToLeftOuterJoins.INSTANCE);
+ ruleStack.addFirst(CopyCriteria.INSTANCE);
+ if (hints.hasView) {
+ ruleStack.addFirst(ReplaceViews.INSTANCE);
+ }
}
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -26,7 +26,7 @@
import org.jboss.dna.common.util.CheckArg;
/**
- * An exception signalling that an XPath query is invalid (but typically well-formed)
+ * An exception signalling that a query is invalid (but typically well-formed)
*/
public class InvalidQueryException extends RuntimeException {
@@ -34,38 +34,38 @@
*/
private static final long serialVersionUID = 1L;
- private final String xpath;
+ private final String query;
/**
* Create an exception with the invalid query.
*
- * @param xpath the XPath query that is invalid
+ * @param query the query that is invalid
*/
- public InvalidQueryException( String xpath ) {
+ public InvalidQueryException( String query ) {
super();
- this.xpath = xpath;
+ this.query = query;
}
/**
* Create an exception with the invalid query and a message.
*
- * @param xpath the XPath query that is invalid
+ * @param query the query that is invalid
* @param message
*/
- public InvalidQueryException( String xpath,
+ public InvalidQueryException( String query,
String message ) {
super(message);
- CheckArg.isNotNull(xpath, "xpath");
- this.xpath = xpath;
+ CheckArg.isNotNull(query, "query");
+ this.query = query;
}
/**
* Get the XPath query that is invalid.
*
- * @return the query
+ * @return the query; never null
*/
- public String getXPath() {
- return xpath;
+ public String getQuery() {
+ return query;
}
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -31,6 +31,7 @@
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.model.AllNodes;
import org.jboss.dna.graph.query.model.And;
@@ -53,6 +54,7 @@
import org.jboss.dna.graph.query.plan.PlanNode.Type;
import org.jboss.dna.graph.query.validate.Schemata;
import org.jboss.dna.graph.query.validate.Schemata.Table;
+import org.jboss.dna.graph.query.validate.Schemata.View;
/**
* The planner that produces a canonical query plan given a {@link QueryCommand query command}.
@@ -85,7 +87,7 @@
* from the top node of the plan.
* </p>
* <p>
- * This canonical plan, however, is optimized and rearranged so that it performs faster.
+ * This canonical plan, however, is later optimized and rearranged so that it performs faster.
* </p>
*/
public class CanonicalPlanner implements Planner {
@@ -190,14 +192,15 @@
}
// Validate the source name and set the available columns ...
Table table = context.getSchemata().getTable(selector.getName());
- if (table == null) {
- context.getProblems().addError(GraphI18n.tableDoesNotExist,
- selector.getName().getString(context.getExecutionContext()));
- } else {
+ if (table != null) {
+ if (table instanceof View) context.getHints().hasView = true;
if (usedSelectors.put(selector.getAliasOrName(), table) != null) {
// There was already a table with this alias or name ...
}
node.setProperty(Property.SOURCE_COLUMNS, table.getColumns());
+ } else {
+ context.getProblems().addError(GraphI18n.tableDoesNotExist,
+ selector.getName().getString(context.getExecutionContext()));
}
return node;
}
@@ -217,7 +220,8 @@
// Handle each child
Source[] clauses = new Source[] {join.getLeft(), join.getRight()};
for (int i = 0; i < 2; i++) {
- node.addLastChild(createPlanNode(context, clauses[i], usedSelectors));
+ PlanNode sourceNode = createPlanNode(context, clauses[i], usedSelectors);
+ node.addLastChild(sourceNode);
// Add selectors to the joinNode
for (PlanNode child : node.getChildren()) {
@@ -269,6 +273,7 @@
}
});
+ criteriaNode.addFirstChild(plan);
plan = criteriaNode;
}
return plan;
@@ -379,6 +384,7 @@
}
} else {
// Add the selector used by each column ...
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
for (Column column : columns) {
SelectorName tableName = column.getSelectorName();
// Add the selector that is being used ...
@@ -391,9 +397,11 @@
tableName.getString(context.getExecutionContext()));
} else {
// Make sure that the column is in the table ...
- if (table.getColumn(column.getColumnName()) == null) {
+ Name columnName = column.getPropertyName();
+ String name = stringFactory.create(columnName);
+ if (table.getColumn(name) == null) {
context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable,
- column.getColumnName(),
+ name,
tableName.getString(context.getExecutionContext()));
}
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -28,19 +28,19 @@
@NotThreadSafe
public final class PlanHints {
- // This flag indicates that the plan has a criteria somewhere
+ /** This flag indicates that the plan has a criteria somewhere */
public boolean hasCriteria = false;
- // This flag indicates that the plan has a join somewhere
+ /** This flag indicates that the plan has a join somewhere */
public boolean hasJoin = false;
- // This flag indicates that the plan has a sort somewhere
+ /** This flag indicates that the plan has a sort somewhere */
public boolean hasSort = false;
// List of groups to make dependent
// public List makeDepGroups = null;
- // flag indicates that the plan has a union somewhere
+ /** flag indicates that the plan has a union somewhere */
public boolean hasSetQuery = false;
// flag indicating that the plan has a grouping node somewhere
@@ -55,6 +55,9 @@
public boolean hasFullTextSearch = false;
+ /** Flag indicates that the plan has at least one view somewhere */
+ public boolean hasView = false;
+
public PlanHints() {
}
@@ -62,6 +65,7 @@
public String toString() {
StringBuilder sb = new StringBuilder("PlanHints {");
sb.append(" hasCriteria=").append(hasCriteria);
+ sb.append(", hasView=").append(hasView);
sb.append(", hasJoin=").append(hasJoin);
sb.append(", hasSort=").append(hasSort);
sb.append(", hasSetQuery=").append(hasSetQuery);
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -36,6 +36,7 @@
import java.util.Set;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.Command;
@@ -54,7 +55,7 @@
* A representation of a single node within a plan tree.
*/
@NotThreadSafe
-public final class PlanNode implements Iterable<PlanNode>, Readable {
+public final class PlanNode implements Iterable<PlanNode>, Readable, Cloneable {
/**
* An enumeration dictating the type of plan tree nodes.
@@ -298,6 +299,70 @@
}
/**
+ * Return true if this node's type does not match the supplied type
+ *
+ * @param type the type to compare
+ * @return true if this node's type is different than that supplied, or false if they are the same
+ */
+ public boolean isNot( Type type ) {
+ return this.type != type;
+ }
+
+ /**
+ * Return true if this node's type does not match any of the supplied types
+ *
+ * @param first the type to compare
+ * @param rest the additional types to compare
+ * @return true if this node's type is different than all of those supplied, or false if matches one of the supplied types
+ */
+ public boolean isNotOneOf( Type first,
+ Type... rest ) {
+ return isNotOneOf(EnumSet.of(first, rest));
+ }
+
+ /**
+ * Return true if this node's type does not match any of the supplied types
+ *
+ * @param types the types to compare
+ * @return true if this node's type is different than all of those supplied, or false if matches one of the supplied types
+ */
+ public boolean isNotOneOf( Set<Type> types ) {
+ return !types.contains(type);
+ }
+
+ /**
+ * Return true if this node's type does match the supplied type
+ *
+ * @param type the type to compare
+ * @return true if this node's type is the same as that supplied, or false if the types are different
+ */
+ public boolean is( Type type ) {
+ return this.type == type;
+ }
+
+ /**
+ * Return true if this node's type matches one of the supplied types
+ *
+ * @param first the type to compare
+ * @param rest the additional types to compare
+ * @return true if this node's type is one of those supplied, or false otherwise
+ */
+ public boolean isOneOf( Type first,
+ Type... rest ) {
+ return isOneOf(EnumSet.of(first, rest));
+ }
+
+ /**
+ * Return true if this node's type matches one of the supplied types
+ *
+ * @param types the types to compare
+ * @return true if this node's type is one of those supplied, or false otherwise
+ */
+ public boolean isOneOf( Set<Type> types ) {
+ return types.contains(type);
+ }
+
+ /**
* Get the parent of this node.
*
* @return the parent node, or null if this node has no parent
@@ -357,6 +422,7 @@
*/
public void insertAsParent( PlanNode newParent ) {
if (newParent == null) return;
+ newParent.removeFromParent();
if (this.parent != null) {
this.parent.replaceChild(this, newParent);
}
@@ -832,6 +898,34 @@
}
/**
+ * {@inheritDoc}
+ * <p>
+ * This class returns a new clone of the plan tree rooted at this node. However, the top node of the resulting plan tree (that
+ * is, the node returned from this method) has no parent.
+ * </p>
+ *
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public PlanNode clone() {
+ return cloneWithoutNewParent();
+ }
+
+ protected PlanNode cloneWithoutNewParent() {
+ PlanNode result = new PlanNode(this.type, null, this.selectors);
+ if (this.nodeProperties != null && !this.nodeProperties.isEmpty()) {
+ result.nodeProperties = new HashMap<Property, Object>(this.nodeProperties);
+ }
+ // Clone the children ...
+ for (PlanNode child : children) {
+ PlanNode childClone = child.cloneWithoutNewParent();
+ // The child has no parent, so add the child to the new result ...
+ result.addLastChild(childClone);
+ }
+ return result;
+ }
+
+ /**
* Determine whether the supplied plan is equivalent to this plan.
*
* @param other the other plan to compare with this instance
@@ -840,9 +934,15 @@
public boolean isSameAs( PlanNode other ) {
if (other == null) return false;
if (this.getType() != other.getType()) return false;
- if (!this.nodeProperties.equals(other.nodeProperties)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.nodeProperties, other.nodeProperties)) return false;
if (!this.getSelectors().equals(other.getSelectors())) return false;
- return this.getChildren().equals(other.getChildren());
+ if (this.getChildCount() != other.getChildCount()) return false;
+ Iterator<PlanNode> thisChildren = this.getChildren().iterator();
+ Iterator<PlanNode> thatChildren = other.getChildren().iterator();
+ while (thisChildren.hasNext() && thatChildren.hasNext()) {
+ if (!thisChildren.next().isSameAs(thatChildren.next())) return false;
+ }
+ return true;
}
/**
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,842 @@
+/*
+ * 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.graph.query.plan;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeDepth;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.StaticOperand;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+
+/**
+ * Utilities for working with {@link PlanNode}s.
+ */
+public class PlanUtil {
+
+ /**
+ * Collected the minimum set of columns from the supplied table that are required by or used within the plan at the supplied
+ * node or above. This method first looks to see if the supplied node is a {@link Type#PROJECT} node, and if so immediately
+ * returns that node's list of {@link Property#PROJECT_COLUMNS projected columns}. Otherwise, this method finds all of the
+ * {@link Type#SOURCE} nodes below the supplied plan node, and accumulates the set of sources for which the columns are to be
+ * found. The method then walks up the plan, looking for references to columns of the supplied table, starting with the
+ * supplied node and walking up until a {@link PlanNode.Type#PROJECT PROJECT} node is found or the top of the plan is reached.
+ * <p>
+ * The resulting column list will always contain at the front the {@link Property#PROJECT_COLUMNS columns} from the nearest
+ * PROJECT ancestor, followed by any columns required by criteria. This is done so that the processing of the nearest PROJECT
+ * ancestor node can simply use the sublist of each tuple.
+ * </p>
+ *
+ * @param context the query context; may not be null
+ * @param planNode the plan node at which the required columns need to be found; may not be null
+ * @return the list of {@link Column} objects that are used in the plan; never null but possibly empty if no columns are
+ * actually needed
+ */
+ public static List<Column> findRequiredColumns( QueryContext context,
+ PlanNode planNode ) {
+ List<Column> columns = null;
+ PlanNode node = planNode;
+ PlanNode projectNode = null;
+ // First find the columns from the nearest PROJECT ancestor ...
+ do {
+ switch (node.getType()) {
+ case PROJECT:
+ columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ projectNode = node;
+ node = null;
+ break;
+ default:
+ node = node.getParent();
+ break;
+ }
+ } while (node != null);
+
+ // If the supplied node is a PROJECT, just return the PROJECT node's column list ...
+ if (projectNode == planNode) return columns;
+
+ // Find the names of the selectors ...
+ Set<SelectorName> names = new HashSet<SelectorName>();
+ for (PlanNode source : planNode.findAllAtOrBelow(Type.SOURCE)) {
+ names.add(source.getProperty(Property.SOURCE_NAME, SelectorName.class));
+ names.add(source.getProperty(Property.SOURCE_ALIAS, SelectorName.class));
+ }
+
+ // Add the PROJECT columns first ...
+ RequiredColumnVisitor collectionVisitor = new RequiredColumnVisitor(context.getExecutionContext(), names);
+ if (columns != null) {
+ for (Column projectedColumn : columns) {
+ collectionVisitor.visit(projectedColumn);
+ }
+ }
+
+ // Now add the columns from the JOIN and SELECT ancestors ...
+ node = planNode;
+ do {
+ switch (node.getType()) {
+ case JOIN:
+ Constraint criteria = node.getProperty(Property.JOIN_CONSTRAINTS, Constraint.class);
+ JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+ Visitors.visitAll(criteria, collectionVisitor);
+ Visitors.visitAll(joinCondition, collectionVisitor);
+ break;
+ case SELECT:
+ criteria = node.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+ Visitors.visitAll(criteria, collectionVisitor);
+ break;
+ case PROJECT:
+ // Already handled above, but we can stop looking for columns ...
+ return collectionVisitor.getRequiredColumns();
+ default:
+ break;
+ }
+ node = node.getParent();
+ } while (node != null);
+ return collectionVisitor.getRequiredColumns();
+ }
+
+ protected static class RequiredColumnVisitor extends AbstractVisitor {
+ private final ExecutionContext context;
+ private final Set<SelectorName> names;
+ private final List<Column> columns = new LinkedList<Column>();
+ private final Set<Name> requiredColumnNames = new HashSet<Name>();
+
+ protected RequiredColumnVisitor( ExecutionContext context,
+ Set<SelectorName> names ) {
+ this.names = names;
+ this.context = context;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.PropertyExistence)
+ */
+ @Override
+ public void visit( PropertyExistence existence ) {
+ requireColumn(existence.getSelectorName(), existence.getPropertyName());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.PropertyValue)
+ */
+ @Override
+ public void visit( PropertyValue value ) {
+ requireColumn(value.getSelectorName(), value.getPropertyName());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.EquiJoinCondition)
+ */
+ @Override
+ public void visit( EquiJoinCondition condition ) {
+ requireColumn(condition.getSelector1Name(), condition.getProperty1Name());
+ requireColumn(condition.getSelector2Name(), condition.getProperty2Name());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.Column)
+ */
+ @Override
+ public void visit( Column column ) {
+ requireColumn(column.getSelectorName(), column.getPropertyName());
+ }
+
+ protected void requireColumn( SelectorName selector,
+ Name propertyName ) {
+ if (names.contains(selector)) {
+ // The column is part of the table we're interested in ...
+ if (requiredColumnNames.add(propertyName)) {
+ String alias = context.getValueFactories().getStringFactory().create(propertyName);
+ columns.add(new Column(selector, propertyName, alias));
+ }
+ }
+ }
+
+ /**
+ * Get the columns that are required.
+ *
+ * @return the columns; never null
+ */
+ public List<Column> getRequiredColumns() {
+ return columns;
+ }
+ }
+
+ public static void replaceReferencesToRemovedSource( QueryContext context,
+ PlanNode planNode,
+ Map<SelectorName, SelectorName> rewrittenSelectors ) {
+ switch (planNode.getType()) {
+ case PROJECT:
+ List<Column> columns = planNode.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ for (int i = 0; i != columns.size(); ++i) {
+ Column column = columns.get(i);
+ SelectorName replacement = rewrittenSelectors.get(column.getSelectorName());
+ if (replacement != null) {
+ columns.set(i, new Column(replacement, column.getPropertyName(), column.getColumnName()));
+ }
+ }
+ break;
+ case SELECT:
+ Constraint constraint = planNode.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+ Constraint newConstraint = replaceReferencesToRemovedSource(context, constraint, rewrittenSelectors);
+ if (constraint != newConstraint) {
+ planNode.setProperty(Property.SELECT_CRITERIA, newConstraint);
+ }
+ break;
+ case SORT:
+ List<Object> orderBys = planNode.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
+ if (orderBys != null && !orderBys.isEmpty()) {
+ if (orderBys.get(0) instanceof SelectorName) {
+ for (int i = 0; i != orderBys.size(); ++i) {
+ SelectorName selectorName = (SelectorName)orderBys.get(i);
+ SelectorName replacement = rewrittenSelectors.get(selectorName);
+ if (replacement != null) {
+ orderBys.set(i, replacement);
+ }
+ }
+ } else {
+ for (int i = 0; i != orderBys.size(); ++i) {
+ Ordering ordering = (Ordering)orderBys.get(i);
+ DynamicOperand operand = ordering.getOperand();
+ orderBys.set(i, replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
+ }
+ }
+ }
+ break;
+ case JOIN:
+ // Update the join condition ...
+ JoinCondition joinCondition = planNode.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+ JoinCondition newCondition = replaceReferencesToRemovedSource(context, joinCondition, rewrittenSelectors);
+ if (joinCondition != newCondition) {
+ planNode.setProperty(Property.JOIN_CONDITION, newCondition);
+ }
+
+ // Update the join criteria (if there are some) ...
+ List<Constraint> constraints = planNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
+ if (constraints != null && !constraints.isEmpty()) {
+ for (int i = 0; i != constraints.size(); ++i) {
+ Constraint old = constraints.get(i);
+ Constraint replacement = replaceReferencesToRemovedSource(context, old, rewrittenSelectors);
+ if (replacement != old) {
+ constraints.set(i, replacement);
+ }
+ }
+ }
+ break;
+ case GROUP:
+ case SET_OPERATION:
+ case DUP_REMOVE:
+ case LIMIT:
+ case NULL:
+ case SOURCE:
+ case ACCESS:
+ // None of these have to be changed ...
+ break;
+ }
+
+ // Update the selectors referenced by the node ...
+ Set<SelectorName> selectorsToAdd = null;
+ for (Iterator<SelectorName> iter = planNode.getSelectors().iterator(); iter.hasNext();) {
+ SelectorName replacement = rewrittenSelectors.get(iter.next());
+ if (replacement != null) {
+ iter.remove();
+ if (selectorsToAdd == null) selectorsToAdd = new HashSet<SelectorName>();
+ selectorsToAdd.add(replacement);
+ }
+ }
+ if (selectorsToAdd != null) planNode.getSelectors().addAll(selectorsToAdd);
+
+ // Now call recursively on the children ...
+ for (PlanNode child : planNode) {
+ replaceReferencesToRemovedSource(context, child, rewrittenSelectors);
+ }
+ }
+
+ public static DynamicOperand replaceReferencesToRemovedSource( QueryContext context,
+ DynamicOperand operand,
+ Map<SelectorName, SelectorName> rewrittenSelectors ) {
+ if (operand instanceof FullTextSearchScore) {
+ FullTextSearchScore score = (FullTextSearchScore)operand;
+ SelectorName replacement = rewrittenSelectors.get(score.getSelectorName());
+ if (replacement == null) return score;
+ return new FullTextSearchScore(replacement);
+ }
+ if (operand instanceof Length) {
+ Length operation = (Length)operand;
+ PropertyValue wrapped = operation.getPropertyValue();
+ SelectorName replacement = rewrittenSelectors.get(wrapped.getSelectorName());
+ if (replacement == null) return operand;
+ return new Length(new PropertyValue(replacement, wrapped.getPropertyName()));
+ }
+ if (operand instanceof LowerCase) {
+ LowerCase operation = (LowerCase)operand;
+ SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
+ if (replacement == null) return operand;
+ return new LowerCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors));
+ }
+ if (operand instanceof UpperCase) {
+ UpperCase operation = (UpperCase)operand;
+ SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
+ if (replacement == null) return operand;
+ return new UpperCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors));
+ }
+ if (operand instanceof NodeName) {
+ NodeName name = (NodeName)operand;
+ SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
+ if (replacement == null) return name;
+ return new NodeName(replacement);
+ }
+ if (operand instanceof NodeLocalName) {
+ NodeLocalName name = (NodeLocalName)operand;
+ SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
+ if (replacement == null) return name;
+ return new NodeLocalName(replacement);
+ }
+ if (operand instanceof PropertyValue) {
+ PropertyValue value = (PropertyValue)operand;
+ SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
+ if (replacement == null) return operand;
+ return new PropertyValue(replacement, value.getPropertyName());
+ }
+ if (operand instanceof NodeDepth) {
+ NodeDepth depth = (NodeDepth)operand;
+ SelectorName replacement = rewrittenSelectors.get(depth.getSelectorName());
+ if (replacement == null) return operand;
+ return new NodeDepth(replacement);
+ }
+ if (operand instanceof NodePath) {
+ NodePath path = (NodePath)operand;
+ SelectorName replacement = rewrittenSelectors.get(path.getSelectorName());
+ if (replacement == null) return operand;
+ return new NodePath(replacement);
+ }
+ return operand;
+ }
+
+ public static Constraint replaceReferencesToRemovedSource( QueryContext context,
+ Constraint constraint,
+ Map<SelectorName, SelectorName> rewrittenSelectors ) {
+ if (constraint instanceof And) {
+ And and = (And)constraint;
+ Constraint left = replaceReferencesToRemovedSource(context, and.getLeft(), rewrittenSelectors);
+ Constraint right = replaceReferencesToRemovedSource(context, and.getRight(), rewrittenSelectors);
+ if (left == and.getLeft() && right == and.getRight()) return and;
+ return new And(left, right);
+ }
+ if (constraint instanceof Or) {
+ Or or = (Or)constraint;
+ Constraint left = replaceReferencesToRemovedSource(context, or.getLeft(), rewrittenSelectors);
+ Constraint right = replaceReferencesToRemovedSource(context, or.getRight(), rewrittenSelectors);
+ if (left == or.getLeft() && right == or.getRight()) return or;
+ return new Or(left, right);
+ }
+ if (constraint instanceof Not) {
+ Not not = (Not)constraint;
+ Constraint wrapped = replaceReferencesToRemovedSource(context, not.getConstraint(), rewrittenSelectors);
+ if (wrapped == not.getConstraint()) return not;
+ return new Not(wrapped);
+ }
+ if (constraint instanceof SameNode) {
+ SameNode sameNode = (SameNode)constraint;
+ SelectorName replacement = rewrittenSelectors.get(sameNode.getSelectorName());
+ if (replacement == null) return sameNode;
+ return new SameNode(replacement, sameNode.getPath());
+ }
+ if (constraint instanceof ChildNode) {
+ ChildNode childNode = (ChildNode)constraint;
+ SelectorName replacement = rewrittenSelectors.get(childNode.getSelectorName());
+ if (replacement == null) return childNode;
+ return new ChildNode(replacement, childNode.getParentPath());
+ }
+ if (constraint instanceof DescendantNode) {
+ DescendantNode descendantNode = (DescendantNode)constraint;
+ SelectorName replacement = rewrittenSelectors.get(descendantNode.getSelectorName());
+ if (replacement == null) return descendantNode;
+ return new DescendantNode(replacement, descendantNode.getAncestorPath());
+ }
+ if (constraint instanceof PropertyExistence) {
+ PropertyExistence existence = (PropertyExistence)constraint;
+ SelectorName replacement = rewrittenSelectors.get(existence.getSelectorName());
+ if (replacement == null) return existence;
+ return new PropertyExistence(replacement, existence.getPropertyName());
+ }
+ if (constraint instanceof FullTextSearch) {
+ FullTextSearch search = (FullTextSearch)constraint;
+ SelectorName replacement = rewrittenSelectors.get(search.getSelectorName());
+ if (replacement == null) return search;
+ return new FullTextSearch(replacement, search.getPropertyName(), search.getFullTextSearchExpression());
+ }
+ if (constraint instanceof Comparison) {
+ Comparison comparison = (Comparison)constraint;
+ DynamicOperand lhs = comparison.getOperand1();
+ StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector
+ DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors);
+ if (lhs == newLhs) return comparison;
+ return new Comparison(newLhs, comparison.getOperator(), rhs);
+ }
+ return constraint;
+ }
+
+ public static JoinCondition replaceReferencesToRemovedSource( QueryContext context,
+ JoinCondition joinCondition,
+ Map<SelectorName, SelectorName> rewrittenSelectors ) {
+ if (joinCondition instanceof EquiJoinCondition) {
+ EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
+ SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
+ SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
+ if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+ return new EquiJoinCondition(replacement1, condition.getProperty1Name(), replacement2, condition.getProperty2Name());
+ }
+ if (joinCondition instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
+ SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
+ SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
+ if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+ return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
+ }
+ if (joinCondition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
+ SelectorName childSelector = rewrittenSelectors.get(condition.getChildSelectorName());
+ SelectorName parentSelector = rewrittenSelectors.get(condition.getParentSelectorName());
+ if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
+ return new ChildNodeJoinCondition(parentSelector, childSelector);
+ }
+ if (joinCondition instanceof DescendantNodeJoinCondition) {
+ DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
+ SelectorName ancestor = rewrittenSelectors.get(condition.getAncestorSelectorName());
+ SelectorName descendant = rewrittenSelectors.get(condition.getDescendantSelectorName());
+ if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
+ return new ChildNodeJoinCondition(ancestor, descendant);
+ }
+ return joinCondition;
+ }
+
+ public static void replaceViewReferences( QueryContext context,
+ PlanNode topOfViewInPlan,
+ ColumnMapping mappings ) {
+ assert topOfViewInPlan != null;
+ SelectorName viewName = mappings.getOriginalName();
+
+ // Walk up the plan to change any references to the view columns into references to the source columns...
+ PlanNode node = topOfViewInPlan;
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ List<PlanNode> potentiallyRemovableSources = new LinkedList<PlanNode>();
+ do {
+ // Remove the view from the selectors ...
+ if (node.getSelectors().contains(viewName)) {
+ switch (node.getType()) {
+ case PROJECT:
+ // Adjust the columns ...
+ node.getSelectors().remove(viewName);
+ List<Column> columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ if (columns != null) {
+ for (int i = 0; i != columns.size(); ++i) {
+ Column column = columns.get(i);
+ if (column.getSelectorName().equals(viewName)) {
+ // This column references the view ...
+ String columnName = stringFactory.create(column.getPropertyName());
+ String columnAlias = column.getColumnName();
+ // Find the source column that this view column corresponds to ...
+ Column sourceColumn = mappings.getMappedColumn(columnName);
+ if (sourceColumn != null) {
+ SelectorName sourceName = sourceColumn.getSelectorName();
+ // Replace the view column with one that uses the same alias but that references the
+ // source
+ // column ...
+ columns.set(i, new Column(sourceName, sourceColumn.getPropertyName(), columnAlias));
+ node.addSelector(sourceName);
+ } else {
+ node.addSelector(column.getSelectorName());
+ }
+ } else {
+ node.addSelector(column.getSelectorName());
+ }
+ }
+ }
+ break;
+ case SELECT:
+ Constraint constraint = node.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+ Constraint newConstraint = replaceReferences(context, constraint, mappings, node);
+ if (constraint != newConstraint) {
+ node.getSelectors().clear();
+ node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint));
+ node.setProperty(Property.SELECT_CRITERIA, newConstraint);
+ }
+ break;
+ case SET_OPERATION:
+ break;
+ case SOURCE:
+ SelectorName sourceName = node.getProperty(Property.SOURCE_NAME, SelectorName.class);
+ assert sourceName.equals(sourceName); // selector name already matches
+ potentiallyRemovableSources.add(node);
+ break;
+ default:
+ break;
+ }
+ if (!node.getSelectors().contains(viewName)) {
+ // Used to reference view, so it needs to at least reference
+ }
+ }
+ // Move to the parent ...
+ node = node.getParent();
+ } while (node != null);
+
+ // Remove any source node that was the view but that is no longer used in any higher node ...
+ for (PlanNode sourceNode : potentiallyRemovableSources) {
+ boolean stillRequired = false;
+ node = sourceNode.getParent();
+ while (node != null) {
+ if (node.getSelectors().contains(viewName)) {
+ stillRequired = true;
+ break;
+ }
+ node = node.getParent();
+ }
+ if (!stillRequired) {
+ assert sourceNode.getParent() != null;
+ sourceNode.extractFromParent();
+ }
+ }
+ }
+
+ public static Constraint replaceReferences( QueryContext context,
+ Constraint constraint,
+ ColumnMapping mapping,
+ PlanNode node ) {
+ if (constraint instanceof And) {
+ And and = (And)constraint;
+ Constraint left = replaceReferences(context, and.getLeft(), mapping, node);
+ Constraint right = replaceReferences(context, and.getRight(), mapping, node);
+ if (left == and.getLeft() && right == and.getRight()) return and;
+ return new And(left, right);
+ }
+ if (constraint instanceof Or) {
+ Or or = (Or)constraint;
+ Constraint left = replaceReferences(context, or.getLeft(), mapping, node);
+ Constraint right = replaceReferences(context, or.getRight(), mapping, node);
+ if (left == or.getLeft() && right == or.getRight()) return or;
+ return new Or(left, right);
+ }
+ if (constraint instanceof Not) {
+ Not not = (Not)constraint;
+ Constraint wrapped = replaceReferences(context, not.getConstraint(), mapping, node);
+ return wrapped == not.getConstraint() ? not : new Not(wrapped);
+ }
+ if (constraint instanceof SameNode) {
+ SameNode sameNode = (SameNode)constraint;
+ if (!mapping.getOriginalName().equals(sameNode.getSelectorName())) return sameNode;
+ if (!mapping.isMappedToSingleSelector()) return sameNode;
+ SelectorName selector = mapping.getSingleMappedSelectorName();
+ node.addSelector(selector);
+ return new SameNode(selector, sameNode.getPath());
+ }
+ if (constraint instanceof ChildNode) {
+ ChildNode childNode = (ChildNode)constraint;
+ if (!mapping.getOriginalName().equals(childNode.getSelectorName())) return childNode;
+ if (!mapping.isMappedToSingleSelector()) return childNode;
+ SelectorName selector = mapping.getSingleMappedSelectorName();
+ node.addSelector(selector);
+ return new ChildNode(selector, childNode.getParentPath());
+ }
+ if (constraint instanceof DescendantNode) {
+ DescendantNode descendantNode = (DescendantNode)constraint;
+ if (!mapping.getOriginalName().equals(descendantNode.getSelectorName())) return descendantNode;
+ if (!mapping.isMappedToSingleSelector()) return descendantNode;
+ SelectorName selector = mapping.getSingleMappedSelectorName();
+ node.addSelector(selector);
+ return new DescendantNode(selector, descendantNode.getAncestorPath());
+ }
+ if (constraint instanceof PropertyExistence) {
+ PropertyExistence existence = (PropertyExistence)constraint;
+ if (!mapping.getOriginalName().equals(existence.getSelectorName())) return existence;
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ Column sourceColumn = mapping.getMappedColumn(stringFactory.create(existence.getPropertyName()));
+ if (sourceColumn == null) return existence;
+ node.addSelector(sourceColumn.getSelectorName());
+ return new PropertyExistence(sourceColumn.getSelectorName(), sourceColumn.getPropertyName());
+ }
+ if (constraint instanceof FullTextSearch) {
+ FullTextSearch search = (FullTextSearch)constraint;
+ if (!mapping.getOriginalName().equals(search.getSelectorName())) return search;
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ Column sourceColumn = mapping.getMappedColumn(stringFactory.create(search.getPropertyName()));
+ if (sourceColumn == null) return search;
+ node.addSelector(sourceColumn.getSelectorName());
+ return new FullTextSearch(sourceColumn.getSelectorName(), sourceColumn.getPropertyName(),
+ search.getFullTextSearchExpression());
+ }
+ if (constraint instanceof Comparison) {
+ Comparison comparison = (Comparison)constraint;
+ DynamicOperand lhs = comparison.getOperand1();
+ StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector
+ DynamicOperand newLhs = replaceViewReferences(context, lhs, mapping, node);
+ if (lhs == newLhs) return comparison;
+ return new Comparison(newLhs, comparison.getOperator(), rhs);
+ }
+ return constraint;
+ }
+
+ public static DynamicOperand replaceViewReferences( QueryContext context,
+ DynamicOperand operand,
+ ColumnMapping mapping,
+ PlanNode node ) {
+ if (operand instanceof FullTextSearchScore) {
+ FullTextSearchScore score = (FullTextSearchScore)operand;
+ if (!mapping.getOriginalName().equals(score.getSelectorName())) return score;
+ if (!mapping.isMappedToSingleSelector()) return score;
+ return new FullTextSearchScore(mapping.getSingleMappedSelectorName());
+ }
+ if (operand instanceof Length) {
+ Length operation = (Length)operand;
+ return new Length((PropertyValue)replaceViewReferences(context, operation.getPropertyValue(), mapping, node));
+ }
+ if (operand instanceof LowerCase) {
+ LowerCase operation = (LowerCase)operand;
+ return new LowerCase(replaceViewReferences(context, operation.getOperand(), mapping, node));
+ }
+ if (operand instanceof UpperCase) {
+ UpperCase operation = (UpperCase)operand;
+ return new UpperCase(replaceViewReferences(context, operation.getOperand(), mapping, node));
+ }
+ if (operand instanceof NodeName) {
+ NodeName name = (NodeName)operand;
+ if (!mapping.getOriginalName().equals(name.getSelectorName())) return name;
+ if (!mapping.isMappedToSingleSelector()) return name;
+ node.addSelector(mapping.getSingleMappedSelectorName());
+ return new NodeName(mapping.getSingleMappedSelectorName());
+ }
+ if (operand instanceof NodeLocalName) {
+ NodeLocalName name = (NodeLocalName)operand;
+ if (!mapping.getOriginalName().equals(name.getSelectorName())) return name;
+ if (!mapping.isMappedToSingleSelector()) return name;
+ node.addSelector(mapping.getSingleMappedSelectorName());
+ return new NodeLocalName(mapping.getSingleMappedSelectorName());
+ }
+ if (operand instanceof PropertyValue) {
+ PropertyValue value = (PropertyValue)operand;
+ if (!mapping.getOriginalName().equals(value.getSelectorName())) return value;
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ Column sourceColumn = mapping.getMappedColumn(stringFactory.create(value.getPropertyName()));
+ if (sourceColumn == null) return value;
+ node.addSelector(sourceColumn.getSelectorName());
+ return new PropertyValue(sourceColumn.getSelectorName(), sourceColumn.getPropertyName());
+ }
+ if (operand instanceof NodeDepth) {
+ NodeDepth depth = (NodeDepth)operand;
+ if (!mapping.getOriginalName().equals(depth.getSelectorName())) return depth;
+ if (!mapping.isMappedToSingleSelector()) return depth;
+ node.addSelector(mapping.getSingleMappedSelectorName());
+ return new NodeDepth(mapping.getSingleMappedSelectorName());
+ }
+ if (operand instanceof NodePath) {
+ NodePath path = (NodePath)operand;
+ if (!mapping.getOriginalName().equals(path.getSelectorName())) return path;
+ if (!mapping.isMappedToSingleSelector()) return path;
+ node.addSelector(mapping.getSingleMappedSelectorName());
+ return new NodePath(mapping.getSingleMappedSelectorName());
+ }
+ return operand;
+ }
+
+ public static JoinCondition replaceReferences( QueryContext context,
+ JoinCondition joinCondition,
+ ColumnMapping mapping,
+ PlanNode node ) {
+ if (joinCondition instanceof EquiJoinCondition) {
+ EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
+ SelectorName replacement1 = condition.getSelector1Name();
+ SelectorName replacement2 = condition.getSelector2Name();
+ Name property1 = condition.getProperty1Name();
+ Name property2 = condition.getProperty2Name();
+ if (replacement1.equals(mapping.getOriginalName())) {
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ Column sourceColumn = mapping.getMappedColumn(stringFactory.create(property1));
+ if (sourceColumn != null) {
+ replacement1 = sourceColumn.getSelectorName();
+ property1 = sourceColumn.getPropertyName();
+ }
+ }
+ if (replacement2.equals(mapping.getOriginalName())) {
+ ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+ Column sourceColumn = mapping.getMappedColumn(stringFactory.create(property2));
+ if (sourceColumn != null) {
+ replacement2 = sourceColumn.getSelectorName();
+ property2 = sourceColumn.getPropertyName();
+ }
+ }
+ if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+ node.addSelector(replacement1, replacement2);
+ return new EquiJoinCondition(replacement1, property1, replacement2, property2);
+ }
+ // All the remaining conditions can only be rewritten if there is a single source ...
+ if (!mapping.isMappedToSingleSelector()) return joinCondition;
+
+ // There is only a single source ...
+ SelectorName viewName = mapping.getOriginalName();
+ SelectorName sourceName = mapping.getSingleMappedSelectorName();
+
+ if (joinCondition instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
+ SelectorName replacement1 = condition.getSelector1Name();
+ SelectorName replacement2 = condition.getSelector2Name();
+ if (replacement1.equals(viewName)) replacement1 = sourceName;
+ if (replacement2.equals(viewName)) replacement2 = sourceName;
+ if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+ node.addSelector(replacement1, replacement2);
+ return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
+ }
+ if (joinCondition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
+ SelectorName childSelector = condition.getChildSelectorName();
+ SelectorName parentSelector = condition.getParentSelectorName();
+ if (childSelector.equals(viewName)) childSelector = sourceName;
+ if (parentSelector.equals(viewName)) parentSelector = sourceName;
+ if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
+ node.addSelector(childSelector, parentSelector);
+ return new ChildNodeJoinCondition(parentSelector, childSelector);
+ }
+ if (joinCondition instanceof DescendantNodeJoinCondition) {
+ DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
+ SelectorName ancestor = condition.getAncestorSelectorName();
+ SelectorName descendant = condition.getDescendantSelectorName();
+ if (ancestor.equals(viewName)) ancestor = sourceName;
+ if (descendant.equals(viewName)) descendant = sourceName;
+ if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
+ node.addSelector(ancestor, descendant);
+ return new ChildNodeJoinCondition(ancestor, descendant);
+ }
+ return joinCondition;
+ }
+
+ public static ColumnMapping createMappingFor( View view,
+ PlanNode viewPlan ) {
+ ColumnMapping mapping = new ColumnMapping(view.getName());
+
+ // Find the PROJECT node in the view plan ...
+ PlanNode project = viewPlan.findAtOrBelow(Type.PROJECT);
+ assert project != null;
+
+ // Get the Columns from the PROJECT in the plan node ...
+ List<Column> projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+
+ // Get the Schemata columns defined by the view ...
+ List<org.jboss.dna.graph.query.validate.Schemata.Column> viewColumns = view.getColumns();
+ assert viewColumns.size() == projectedColumns.size();
+
+ for (int i = 0; i != viewColumns.size(); ++i) {
+ Column projectedColunn = projectedColumns.get(i);
+ String viewColumnName = viewColumns.get(i).getName();
+ mapping.map(viewColumnName, projectedColunn);
+ }
+ return mapping;
+ }
+
+ /**
+ * Defines how the view columns are mapped (or resolved) into the columns from the source tables.
+ */
+ public static class ColumnMapping {
+ private final SelectorName originalName;
+ private final Map<String, Column> mappedColumnsByOriginalColumnName = new HashMap<String, Column>();
+ private final Set<SelectorName> mappedSelectorNames = new HashSet<SelectorName>();
+
+ public ColumnMapping( SelectorName originalName ) {
+ this.originalName = originalName;
+ }
+
+ public void map( String originalColumnName,
+ Column projectedColumn ) {
+ mappedColumnsByOriginalColumnName.put(originalColumnName, projectedColumn);
+ mappedSelectorNames.add(projectedColumn.getSelectorName());
+ }
+
+ public SelectorName getOriginalName() {
+ return originalName;
+ }
+
+ public Column getMappedColumn( String viewColumnName ) {
+ return mappedColumnsByOriginalColumnName.get(viewColumnName);
+ }
+
+ public boolean isMappedToSingleSelector() {
+ return mappedSelectorNames.size() == 1;
+ }
+
+ /**
+ * @return tableNames
+ */
+ public Set<SelectorName> getMappedSelectorNames() {
+ return mappedSelectorNames;
+ }
+
+ public SelectorName getSingleMappedSelectorName() {
+ return isMappedToSingleSelector() ? mappedSelectorNames.iterator().next() : null;
+ }
+ }
+
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -32,10 +32,23 @@
import java.util.Set;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.text.ParsingException;
import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.graph.query.parse.SqlQueryParser;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
/**
* An immutable {@link Schemata} implementation.
@@ -46,10 +59,13 @@
/**
* Obtain a new instance for building Schemata objects.
*
+ * @param context the execution context that can be used when building the schema
* @return the new builder; never null
+ * @throws IllegalArgumentException if the context is null
*/
- public static Builder createBuilder() {
- return new Builder();
+ public static Builder createBuilder( ExecutionContext context ) {
+ CheckArg.isNotNull(context, "context");
+ return new Builder(context);
}
/**
@@ -58,8 +74,14 @@
@NotThreadSafe
public static class Builder {
- private Map<SelectorName, ImmutableTable> tables = new HashMap<SelectorName, ImmutableTable>();
+ private final ExecutionContext context;
+ private final Map<SelectorName, ImmutableTable> tables = new HashMap<SelectorName, ImmutableTable>();
+ private final Map<SelectorName, QueryCommand> viewDefinitions = new HashMap<SelectorName, QueryCommand>();
+ protected Builder( ExecutionContext context ) {
+ this.context = context;
+ }
+
/**
* Add a table with the supplied name and column names. Each column will be given a type of {@link PropertyType#STRING}.
* The table will also overwrite any existing table definition with the same name.
@@ -254,6 +276,43 @@
}
/**
+ * Add a view with the supplied name and SQL string definition. The column names and types will be inferred from the
+ * source table(s) and views(s) used in the definition.
+ *
+ * @param name the name of the new view
+ * @param definition the SQL definition of the view
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the view name is null or empty or the definition is null
+ * @throws ParsingException if the supplied definition is cannot be parsed as a SQL query
+ */
+ public Builder addView( String name,
+ String definition ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotEmpty(definition, "definition");
+ SqlQueryParser parser = new SqlQueryParser();
+ QueryCommand command = parser.parseQuery(definition, context);
+ this.viewDefinitions.put(new SelectorName(name), command);
+ return this;
+ }
+
+ /**
+ * Add a view with the supplied name and definition. The column names and types will be inferred from the source table(s)
+ * used in the definition.
+ *
+ * @param name the name of the new view
+ * @param definition the definition of the view
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the view name is null or empty or the definition is null
+ */
+ public Builder addView( String name,
+ QueryCommand definition ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotNull(definition, "definition");
+ this.viewDefinitions.put(new SelectorName(name), definition);
+ return this;
+ }
+
+ /**
* Add a column with the supplied name and type to the named table. Any existing column with that name will be replaced
* with the new column. If the table does not yet exist, it will be added.
*
@@ -320,9 +379,70 @@
* tables (with their columns) as they exist at the moment this method is called.
*
* @return the new Schemata; never null
+ * @throws InvalidQueryException if any of the view definitions is invalid and cannot be resolved
*/
public Schemata build() {
- return new ImmutableSchemata(new HashMap<SelectorName, Table>(tables));
+ ImmutableSchemata schemata = new ImmutableSchemata(new HashMap<SelectorName, Table>(tables));
+
+ // Make a copy of the view definitions, and create the views ...
+ Map<SelectorName, QueryCommand> definitions = new HashMap<SelectorName, QueryCommand>(viewDefinitions);
+ ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory();
+ boolean added = false;
+ do {
+ added = false;
+ Set<SelectorName> viewNames = new HashSet<SelectorName>(definitions.keySet());
+ for (SelectorName name : viewNames) {
+ QueryCommand command = definitions.get(name);
+ // Create the canonical plan for the definition ...
+ QueryContext queryContext = new QueryContext(context, new PlanHints(), schemata);
+ CanonicalPlanner planner = new CanonicalPlanner();
+ PlanNode plan = planner.createPlan(queryContext, command);
+ if (queryContext.getProblems().hasErrors()) continue;
+
+ // Get the columns from the top-level PROJECT ...
+ PlanNode project = plan.findAtOrBelow(Type.PROJECT);
+ assert project != null;
+ List<org.jboss.dna.graph.query.model.Column> columns = project.getPropertyAsList(Property.PROJECT_COLUMNS,
+ org.jboss.dna.graph.query.model.Column.class);
+ assert !columns.isEmpty();
+
+ // Go through all the columns and look up the types ...
+ List<Column> viewColumns = new ArrayList<Column>(columns.size());
+ for (org.jboss.dna.graph.query.model.Column column : columns) {
+ // Find the table that the column came from ...
+ Table source = schemata.getTable(column.getSelectorName());
+ if (source == null) break;
+ String viewColumnName = column.getColumnName();
+ String sourceColumnName = stringFactory.create(column.getPropertyName()); // getColumnName() returns alias
+ Column sourceColumn = source.getColumn(sourceColumnName);
+ if (sourceColumn == null) {
+ throw new InvalidQueryException(Visitors.readable(command),
+ "The view references a non-existant column '"
+ + column.getColumnName() + "' in '" + source.getName() + "'");
+ }
+ viewColumns.add(new ImmutableColumn(viewColumnName, sourceColumn.getPropertyType()));
+ }
+ if (viewColumns.size() != columns.size()) {
+ // We weren't able to resolve all of the columns,
+ // so maybe the columns were referencing yet-to-be-built views ...
+ continue;
+ }
+
+ // If we could resolve the definition ...
+ ImmutableView view = new ImmutableView(name, viewColumns, command);
+ definitions.remove(name);
+ schemata = schemata.with(view);
+ added = true;
+ }
+ } while (added && !definitions.isEmpty());
+
+ if (!definitions.isEmpty()) {
+ QueryCommand command = definitions.values().iterator().next();
+ throw new InvalidQueryException(Visitors.readable(command), "The view definition cannot be resolved: "
+ + Visitors.readable(command));
+ }
+
+ return schemata;
}
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -187,6 +187,14 @@
return new ImmutableTable(getName(), newColumns);
}
+ public ImmutableTable withColumns( Iterable<Column> columns ) {
+ List<Column> newColumns = new LinkedList<Column>(this.getColumns());
+ for (Column column : columns) {
+ newColumns.add(new ImmutableColumn(column.getName(), column.getPropertyType()));
+ }
+ return new ImmutableTable(getName(), newColumns);
+ }
+
public ImmutableTable with( SelectorName name ) {
return new ImmutableTable(name, columnsByName, columns, keys);
}
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,107 @@
+/*
+ * 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.graph.query.validate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.validate.Schemata.Column;
+import org.jboss.dna.graph.query.validate.Schemata.Key;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+
+/**
+ *
+ */
+@Immutable
+class ImmutableView extends ImmutableTable implements View {
+
+ private final QueryCommand definition;
+
+ protected ImmutableView( SelectorName name,
+ Iterable<Column> columns,
+ QueryCommand definition ) {
+ super(name, columns);
+ this.definition = definition;
+ }
+
+ protected ImmutableView( SelectorName name,
+ Iterable<Column> columns,
+ QueryCommand definition,
+ Iterable<Column>... keyColumns ) {
+ super(name, columns, keyColumns);
+ this.definition = definition;
+ }
+
+ protected ImmutableView( SelectorName name,
+ Map<String, Column> columnsByName,
+ List<Column> columns,
+ QueryCommand definition,
+ Set<Key> keys ) {
+ super(name, columnsByName, columns, keys);
+ this.definition = definition;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.View#getDefinition()
+ */
+ public QueryCommand getDefinition() {
+ return definition;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(getName().getName());
+ sb.append('(');
+ boolean first = true;
+ for (Column column : getColumns()) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(column);
+ }
+ sb.append(") AS '");
+ sb.append(Visitors.readable(definition));
+ sb.append('\'');
+ if (!getKeys().isEmpty()) {
+ sb.append(" with keys ");
+ first = true;
+ for (Key key : getKeys()) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(key);
+ }
+ }
+ return sb.toString();
+ }
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -28,6 +28,7 @@
import java.util.Map;
import java.util.Set;
import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.QueryCommand;
import org.jboss.dna.graph.query.model.SelectorName;
/**
@@ -36,10 +37,14 @@
public interface Schemata {
/**
- * Get the information for the table with the supplied name within this schema.
+ * Get the information for the table or view with the supplied name within this schema.
+ * <p>
+ * The resulting definition is immutable, though subsequent calls to this method with the same argument may result in a
+ * different definition.
+ * </p>
*
- * @param name the table name; may not be null
- * @return the table information, or null if there is no such table
+ * @param name the table or view name; may not be null
+ * @return the table or view information, or null if there is no such table
*/
Table getTable( SelectorName name );
@@ -56,6 +61,9 @@
/**
* Get the information for a column with the supplied name within this table.
+ * <p>
+ * The resulting column definition is immutable.
+ * </p>
*
* @param name the column name; may not be null
* @return the column information, or null if there is no such column
@@ -65,21 +73,21 @@
/**
* Get the queryable columns in this table.
*
- * @return the map of column objects by their name; never null
+ * @return the immutable map of immutable column objects by their name; never null
*/
Map<String, Column> getColumnsByName();
/**
* Get the queryable columns in this table.
*
- * @return the ordered column objects; never null
+ * @return the immutable, ordered list of immutable column objects; never null
*/
List<Column> getColumns();
/**
* Get the collection of keys for this table.
*
- * @return the immutable collection of keys; never null, but possibly empty
+ * @return the immutable collection of immutable keys; never null, but possibly empty
*/
Collection<Key> getKeys();
@@ -101,6 +109,9 @@
/**
* Obtain this table's {@link #getKeys() key} that contains exactly those columns listed.
+ * <p>
+ * The resulting key definition is immutable.
+ * </p>
*
* @param columns the columns for the key
* @return the key that uses exactly the supplied columns, or null if there is no such key
@@ -109,6 +120,9 @@
/**
* Obtain this table's {@link #getKeys() key} that contains exactly those columns listed.
+ * <p>
+ * The resulting key definition is immutable.
+ * </p>
*
* @param columns the columns for the key
* @return the key that uses exactly the supplied columns, or null if there is no such key
@@ -117,6 +131,18 @@
}
/**
+ * Information about a view that is defined in terms of other views/tables.
+ */
+ public interface View extends Table {
+ /**
+ * Get the {@link QueryCommand query} that is the definition of the view.
+ *
+ * @return the view definition; never null
+ */
+ QueryCommand getDefinition();
+ }
+
+ /**
* Information about a queryable column.
*/
public interface Column {
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -25,8 +25,9 @@
* This package provides the interfaces that define the tables and columns that can be queried. Though the query framework does not prescribe
* the semantics of a table or column, it does provide
* a {@link org.jboss.dna.graph.query.validate.Schemata} interface that provides access to the immutable
- * {@link org.jboss.dna.graph.query.validate.Schemata.Table} definitions (that then contain the
- * {@link org.jboss.dna.graph.query.validate.Schemata.Column} and {@link org.jboss.dna.graph.query.validate.Schemata.Key} definitions).
+ * {@link org.jboss.dna.graph.query.validate.Schemata.Table} and {@link org.jboss.dna.graph.query.validate.Schemata.View}
+ * definitions (that then contain the {@link org.jboss.dna.graph.query.validate.Schemata.Column}
+ * and {@link org.jboss.dna.graph.query.validate.Schemata.Key} definitions).
* <p>
* Although any Schemata implementaiton can be used, the query framework provides an {@link org.jboss.dna.graph.query.validate.ImmutableSchemata}
* class with a {@link org.jboss.dna.graph.query.validate.ImmutableSchemata.Builder builder} with a fluent API
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -27,6 +27,7 @@
import static org.hamcrest.core.IsSame.sameInstance;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -51,6 +52,14 @@
}
}
+ protected void assertSameChildren( PlanNode node,
+ PlanNode... children ) {
+ assertThat(node.getChildCount(), is(children.length));
+ for (int i = 0; i != node.getChildCount(); ++i) {
+ assertThat(node.getChild(i).isSameAs(children[i]), is(true));
+ }
+ }
+
protected void assertSelectors( PlanNode node,
String... selectors ) {
Set<SelectorName> selectorSet = new HashSet<SelectorName>();
@@ -60,6 +69,19 @@
assertThat("Selectors don't match", node.getSelectors(), is(selectorSet));
}
+ protected void assertProperty( PlanNode node,
+ Property name,
+ Object value ) {
+ assertThat("Property value doesn't match", node.getProperty(name), is(value));
+ }
+
+ protected <T> void assertPropertyIsList( PlanNode node,
+ Property name,
+ Class<T> valueType,
+ T... values ) {
+ assertThat("Property value doesn't match", node.getPropertyAsList(name, valueType), is(Arrays.asList(values)));
+ }
+
protected void assertSortOrderBy( PlanNode sortNode,
String... selectors ) {
List<SelectorName> expected = new ArrayList<SelectorName>(selectors.length);
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java (rev 0)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,165 @@
+/*
+ * 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.graph.query.optimize;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.ImmutableSchemata;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class ReplaceViewsTest extends AbstractQueryTest {
+
+ private ReplaceViews rule;
+ private QueryContext context;
+ private Schemata schemata;
+ private ImmutableSchemata.Builder builder;
+
+ @Before
+ public void beforeEach() {
+ ExecutionContext execContext = new ExecutionContext();
+ rule = ReplaceViews.INSTANCE;
+ builder = ImmutableSchemata.createBuilder(execContext);
+ builder.addTable("t1", "c11", "c12", "c13");
+ builder.addTable("t2", "c21", "c22", "c23");
+ builder.addView("v1", "SELECT c11, c12 FROM t1 WHERE c13 < CAST('3' AS LONG)");
+ builder.addView("v2", "SELECT t1.c11, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11 = t2.c21");
+ schemata = builder.build();
+ context = new QueryContext(execContext, new PlanHints(), schemata);
+ }
+
+ /**
+ * Before:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together
+ * SELECT3
+ * |
+ * SOURCE A SOURCE that uses the view
+ * </pre>
+ *
+ * And after:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together
+ * SELECT3
+ * |
+ * SOURCE
+ * |
+ * PROJECT with the list of columns in the SELECT of the view
+ * |
+ * SELECT1 One or more SELECT plan nodes that each have a single non-join constraint
+ * | that are then all AND-ed together (as defined by the view plan
+ * |
+ * SOURCE A SOURCE that the view uses
+ * </pre>
+ */
+ @Test
+ public void shouldReplaceViewWithPlanForViewWithSingleTable() {
+ // Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
+ PlanNode project = new PlanNode(Type.PROJECT, selector("v1"));
+ PlanNode select1 = new PlanNode(Type.SELECT, project, selector("v1"));
+ PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("v1"));
+ PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("v1"));
+ PlanNode source = new PlanNode(Type.SOURCE, select3, selector("v1"));
+ source.setProperty(Property.SOURCE_NAME, selector("v1"));
+
+ // Create the equivalent plan nodes for what should be created ...
+ PlanNode viewProject = new PlanNode(Type.PROJECT, selector("t1"));
+ PlanNode viewSelect = new PlanNode(Type.SELECT, viewProject, selector("t1"));
+ PlanNode viewSource = new PlanNode(Type.SOURCE, viewSelect, selector("t1"));
+ viewProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+ viewSelect.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c13")),
+ Operator.LESS_THAN, new Literal(3L)));
+ viewSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ viewSource.setProperty(Property.SOURCE_COLUMNS, schemata.getTable(selector("t1")).getColumns());
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, project, new LinkedList<OptimizerRule>());
+ System.out.println(result);
+ assertThat(result, is(sameInstance(project)));
+ assertChildren(project, select1);
+ assertChildren(select1, select2);
+ assertChildren(select2, select3);
+ assertChildren(select3, source);
+ assertSameChildren(source, viewSelect);
+ assertSameChildren(source.getFirstChild(), viewSource);
+ assertSameChildren(source.getFirstChild().getFirstChild());
+ }
+
+ protected List<Column> columns( Column... columns ) {
+ return Arrays.asList(columns);
+ }
+
+ protected Column column( String table,
+ String columnName ) {
+ return new Column(new SelectorName(table), name(columnName), columnName);
+ }
+
+ protected Column column( String table,
+ String columnName,
+ String alias ) {
+ return new Column(new SelectorName(table), name(columnName), alias);
+ }
+
+ protected Name name( String name ) {
+ return context.getExecutionContext().getValueFactories().getNameFactory().create(name);
+ }
+}
Property changes on: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -25,18 +25,33 @@
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+import org.jboss.dna.common.collection.Problems;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.AbstractQueryTest;
import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.optimize.OptimizerRule;
-import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.parse.SqlQueryParser;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.JoinAlgorithm;
import org.jboss.dna.graph.query.plan.PlanHints;
import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.ImmutableSchemata;
import org.jboss.dna.graph.query.validate.Schemata;
import org.junit.Before;
import org.junit.Test;
@@ -44,7 +59,7 @@
/**
*
*/
-public class RuleBasedOptimizerTest {
+public class RuleBasedOptimizerTest extends AbstractQueryTest {
private RuleBasedOptimizer optimizer;
private List<OptimizerRule> rules;
@@ -54,7 +69,15 @@
@Before
public void beforeEach() {
- context = new QueryContext(new ExecutionContext(), new PlanHints(), mock(Schemata.class));
+ ExecutionContext execContext = new ExecutionContext();
+ ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(execContext);
+ builder.addTable("t1", "c11", "c12", "c13");
+ builder.addTable("t2", "c21", "c22", "c23");
+ builder.addView("v1", "SELECT c11, c12 AS c2 FROM t1 WHERE c13 < CAST('3' AS LONG)");
+ builder.addView("v2", "SELECT t1.c11, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11 = t2.c21");
+ Schemata schemata = builder.build();
+ context = new QueryContext(execContext, new PlanHints(), schemata);
+
node = new PlanNode(Type.ACCESS);
ruleExecutionOrder = new ArrayList<Integer>();
@@ -85,6 +108,10 @@
};
}
+ // ----------------------------------------------------------------------------------------------------------------
+ // Test the rule stack logic
+ // ----------------------------------------------------------------------------------------------------------------
+
@Test
public void shouldExecuteEachRuleInSequence() {
optimizer.optimize(context, node);
@@ -111,4 +138,184 @@
assertThat(ruleExecutionOrder.get(2), is(2));
assertThat(ruleExecutionOrder.size(), is(3));
}
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // Test the actual rules
+ // ----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldOptimizePlanForSimpleQueryWithSelectColumns() {
+ node = optimize("SELECT c11,c12 FROM t1");
+ // Create the expected plan ...
+ PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+ PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+ PlanNode source = new PlanNode(Type.SOURCE, project, selector("t1"));
+ source.setProperty(Property.SOURCE_NAME, selector("t1"));
+ source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(expected), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForSimpleQueryWithSelectStar() {
+ node = optimize("SELECT * FROM t1");
+ // Create the expected plan ...
+ PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+ PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12"), column("t1", "c13")));
+ PlanNode source = new PlanNode(Type.SOURCE, project, selector("t1"));
+ source.setProperty(Property.SOURCE_NAME, selector("t1"));
+ source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(expected), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForSimpleQueryWithPropertyValueCriteria() {
+ node = optimize("SELECT c11, c12 FROM t1 WHERE c13 < CAST('3' AS LONG)");
+ // Create the expected plan ...
+ PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+ PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+ PlanNode select = new PlanNode(Type.SELECT, project, selector("t1"));
+ select.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c13")),
+ Operator.LESS_THAN, new Literal(3L)));
+ PlanNode source = new PlanNode(Type.SOURCE, select, selector("t1"));
+ source.setProperty(Property.SOURCE_NAME, selector("t1"));
+ source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(expected), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForEquiJoinQuery() {
+ node = optimize("SELECT t1.c11, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11 = t2.c21");
+
+ // Create the expected plan ...
+ PlanNode project = new PlanNode(Type.PROJECT, selector("t2"), selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12"), column("t2", "c23")));
+ PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"), selector("t1"));
+ join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ join.setProperty(Property.JOIN_CONDITION, new EquiJoinCondition(selector("t1"), name("c11"), selector("t2"), name("c21")));
+
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftProject, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+
+ PlanNode rightAccess = new PlanNode(Type.ACCESS, join, selector("t2"));
+ PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess, selector("t2"));
+ rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2", "c23"), column("t2", "c21")));
+ PlanNode rightSource = new PlanNode(Type.SOURCE, rightProject, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(project), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForQueryUsingView() {
+ node = optimize("SELECT v1.c11 AS c1 FROM v1 WHERE v1.c11 = 'x' AND v1.c2 = 'y'");
+
+ // Create the expected plan ...
+ PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
+ PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1")));
+ PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
+ select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c11")),
+ Operator.EQUAL_TO, new Literal('x')));
+ PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
+ select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c12")),
+ Operator.EQUAL_TO, new Literal('y')));
+ PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
+ select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c13")),
+ Operator.LESS_THAN, new Literal(3L)));
+ PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
+ source.setProperty(Property.SOURCE_NAME, selector("t1"));
+ source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(access), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForQueryUsingViewContainingJoin() {
+ node = optimize("SELECT v2.c11 AS c1 FROM v2 WHERE v2.c11 = 'x' AND v2.c12 = 'y'");
+
+ // Create the expected plan ...
+ PlanNode project = new PlanNode(Type.PROJECT, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1")));
+ PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"), selector("t1"));
+ join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ join.setProperty(Property.JOIN_CONDITION, new EquiJoinCondition(selector("t1"), name("c11"), selector("t2"), name("c21")));
+
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c11")),
+ Operator.EQUAL_TO, new Literal('x')));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c12")),
+ Operator.EQUAL_TO, new Literal('y')));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+
+ PlanNode rightAccess = new PlanNode(Type.ACCESS, join, selector("t2"));
+ PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess, selector("t2"));
+ rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2", "c21")));
+ PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject, selector("t2"));
+ rightSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t2"), name("c21")),
+ Operator.EQUAL_TO, new Literal('x')));
+ PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect1, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(project), is(true));
+ }
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // Utility methods ...
+ // ----------------------------------------------------------------------------------------------------------------
+
+ protected List<Column> columns( Column... columns ) {
+ return Arrays.asList(columns);
+ }
+
+ protected Column column( String table,
+ String columnName ) {
+ return new Column(new SelectorName(table), name(columnName), columnName);
+ }
+
+ protected Column column( String table,
+ String columnName,
+ String alias ) {
+ return new Column(new SelectorName(table), name(columnName), alias);
+ }
+
+ protected Name name( String name ) {
+ return context.getExecutionContext().getValueFactories().getNameFactory().create(name);
+ }
+
+ protected PlanNode optimize( String sql ) {
+ QueryCommand query = new SqlQueryParser().parseQuery(sql, context.getExecutionContext());
+ Problems problems = context.getProblems();
+ assertThat("Problems parsing query: " + sql + "\n" + problems, problems.hasErrors(), is(false));
+ PlanNode plan = new CanonicalPlanner().createPlan(context, query);
+ assertThat("Problems planning query: " + sql + "\n" + problems, problems.hasErrors(), is(false));
+ PlanNode optimized = new RuleBasedOptimizer().optimize(context, plan);
+ assertThat("Problems optimizing query: " + sql + "\n" + problems, problems.hasErrors(), is(false));
+ System.out.println();
+ System.out.println(sql);
+ System.out.println(optimized);
+ return optimized;
+ }
}
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -35,9 +35,6 @@
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.QueryCommand;
import org.jboss.dna.graph.query.model.SelectorName;
-import org.jboss.dna.graph.query.plan.CanonicalPlanner;
-import org.jboss.dna.graph.query.plan.PlanHints;
-import org.jboss.dna.graph.query.plan.PlanNode;
import org.jboss.dna.graph.query.plan.PlanNode.Property;
import org.jboss.dna.graph.query.plan.PlanNode.Type;
import org.jboss.dna.graph.query.validate.ImmutableSchemata;
@@ -68,7 +65,7 @@
hints = new PlanHints();
builder = new QueryBuilder(context);
problems = new SimpleProblems();
- schemataBuilder = ImmutableSchemata.createBuilder();
+ schemataBuilder = ImmutableSchemata.createBuilder(context);
}
protected SelectorName selector( String name ) {
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java 2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -69,7 +69,7 @@
protected Schemata schemataFor( Columns columns,
PropertyType... types ) {
- ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder();
+ ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(executionContext);
for (String selectorName : columns.getSelectorNames()) {
final SelectorName selector = selector(selectorName);
int i = 0;
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java (rev 0)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java 2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,179 @@
+/*
+ * 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.graph.query.validate;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class ImmutableSchemataBuilderTest {
+
+ private ExecutionContext context;
+ private ImmutableSchemata.Builder builder;
+ private Schemata schemata;
+
+ @Before
+ public void beforeEach() {
+ context = new ExecutionContext();
+ builder = ImmutableSchemata.createBuilder(context);
+ }
+
+ @After
+ public void afterEach() {
+ context = null;
+ builder = null;
+ schemata = null;
+ }
+
+ @Test
+ public void shouldBuildSchemaForSingleTableWithDefaultTypeForColumns() {
+ builder.addTable("t1", "c1", "c2", "c3");
+ schemata = builder.build();
+ Table table = schemata.getTable(selector("t1"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+ }
+
+ @Test
+ public void shouldBuildSchemaForMultipleTablesWithDefaultTypeForColumns() {
+ builder.addTable("t1", "c11", "c12", "c13");
+ builder.addTable("t2", "c21", "c22", "c23");
+ schemata = builder.build();
+ Table table = schemata.getTable(selector("t1"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c11").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c13").getPropertyType(), is(PropertyType.STRING));
+ table = schemata.getTable(selector("t2"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c21").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c22").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+ }
+
+ @Test
+ public void shouldBuildSchemaContainingViewOfSingleTable() {
+ builder.addTable("t1", "c1", "c2", "c3");
+ builder.addView("t2", "SELECT * FROM t1 WHERE c1=3");
+ schemata = builder.build();
+ Table table = schemata.getTable(selector("t1"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+ table = schemata.getTable(selector("t2"));
+ assertThat(table, is(instanceOf(View.class)));
+ assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+ }
+
+ @Test
+ public void shouldBuildSchemaContainingViewUsingAliasesOfColumns() {
+ builder.addTable("t1", "c1", "c2", "c3");
+ builder.addView("t2", "SELECT c1 as v1, c2 FROM t1 WHERE c1=3");
+ schemata = builder.build();
+ Table table = schemata.getTable(selector("t1"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+ table = schemata.getTable(selector("t2"));
+ assertThat(table, is(instanceOf(View.class)));
+ assertThat(table.getColumn("v1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+ }
+
+ @Test
+ public void shouldBuildSchemaContainingViewOfMultiTableJoin() {
+ builder.addTable("t1", "c11", "c12", "c13");
+ builder.addTable("t2", "c21", "c22", "c23");
+ builder.addView("v1", "SELECT t1.c11 as x1, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11=t2.c21 WHERE t1.c11=3");
+ schemata = builder.build();
+ Table table = schemata.getTable(selector("t1"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c11").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c13").getPropertyType(), is(PropertyType.STRING));
+ table = schemata.getTable(selector("t2"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c21").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c22").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+ schemata = builder.build();
+ table = schemata.getTable(selector("v1"));
+ assertThat(table, is(instanceOf(View.class)));
+ assertThat(table.getColumn("x1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+ }
+
+ @Test
+ public void shouldBuildSchemaContainingViewUsingView() {
+ builder.addTable("t1", "c11", "c12", "c13");
+ builder.addTable("t2", "c21", "c22", "c23");
+ builder.addView("v2", "SELECT x1, c12 FROM v1 WHERE x1=3");
+ builder.addView("v1", "SELECT t1.c11 as x1, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11=t2.c21 WHERE t1.c11=3");
+ schemata = builder.build();
+ Table table = schemata.getTable(selector("t1"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c11").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c13").getPropertyType(), is(PropertyType.STRING));
+ table = schemata.getTable(selector("t2"));
+ assertThat(table, is(notNullValue()));
+ assertThat(table.getColumn("c21").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c22").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+ schemata = builder.build();
+ table = schemata.getTable(selector("v1"));
+ assertThat(table, is(instanceOf(View.class)));
+ assertThat(table.getColumn("x1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+ table = schemata.getTable(selector("v2"));
+ assertThat(table, is(instanceOf(View.class)));
+ assertThat(table.getColumn("x1").getPropertyType(), is(PropertyType.STRING));
+ assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+ }
+
+ protected SelectorName selector( String name ) {
+ return new SelectorName(name);
+ }
+
+}
Property changes on: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
14 years, 7 months
DNA SVN: r1293 - in trunk/dna-graph/src: test/java/org/jboss/dna/graph/query/parse and 1 other directory.
by dna-commits@lists.jboss.org
Author: rhauch
Date: 2009-10-15 15:55:58 -0400 (Thu, 15 Oct 2009)
New Revision: 1293
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
Log:
DNA-467 Add search/query support to the graph API
Updated test cases for SqlQueryParser.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-15 19:55:26 UTC (rev 1292)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-15 19:55:58 UTC (rev 1293)
@@ -446,9 +446,9 @@
return constraint;
}
- protected Collection<StaticOperand> parseInClause( TokenStream tokens,
- ExecutionContext context ) {
- Collection<StaticOperand> result = new ArrayList<StaticOperand>();
+ protected List<StaticOperand> parseInClause( TokenStream tokens,
+ ExecutionContext context ) {
+ List<StaticOperand> result = new ArrayList<StaticOperand>();
tokens.consume("IN");
tokens.consume("(");
if (!tokens.canConsume(")")) {
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-10-15 19:55:26 UTC (rev 1292)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-10-15 19:55:58 UTC (rev 1293)
@@ -622,6 +622,58 @@
}
// ----------------------------------------------------------------------------------------------------------------
+ // parseInClause
+ // ----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseInClauseFromStringWithSingleValidLiteral() {
+ List<StaticOperand> result = parser.parseInClause(tokens("IN ('value1')"), context);
+ assertThat(result.size(), is(1));
+ assertThat(result.get(0), is((StaticOperand)literal("value1")));
+ }
+
+ @Test
+ public void shouldParseInClauseFromStringWithTwoValidLiteral() {
+ List<StaticOperand> result = parser.parseInClause(tokens("IN ('value1','value2')"), context);
+ assertThat(result.size(), is(2));
+ assertThat(result.get(0), is((StaticOperand)literal("value1")));
+ assertThat(result.get(1), is((StaticOperand)literal("value2")));
+ }
+
+ @Test
+ public void shouldParseInClauseFromStringWithThreeValidLiteral() {
+ List<StaticOperand> result = parser.parseInClause(tokens("IN ('value1','value2','value3')"), context);
+ assertThat(result.size(), is(3));
+ assertThat(result.get(0), is((StaticOperand)literal("value1")));
+ assertThat(result.get(1), is((StaticOperand)literal("value2")));
+ assertThat(result.get(2), is((StaticOperand)literal("value3")));
+ }
+
+ @Test
+ public void shouldParseInClauseFromStringWithSingleValidLiteralCast() {
+ List<StaticOperand> result = parser.parseInClause(tokens("IN (CAST('value1' AS STRING))"), context);
+ assertThat(result.size(), is(1));
+ assertThat(result.iterator().next(), is((StaticOperand)literal("value1")));
+
+ result = parser.parseInClause(tokens("IN (CAST('3' AS LONG))"), context);
+ assertThat(result.size(), is(1));
+ assertThat(result.iterator().next(), is((StaticOperand)literal(new Long(3))));
+ }
+
+ @Test
+ public void shouldParseInClauseFromStringWithMultipleValidLiteralCasts() {
+ List<StaticOperand> result = parser.parseInClause(tokens("IN (CAST('value1' AS STRING),CAST('3' AS LONG),'4')"), context);
+ assertThat(result.size(), is(3));
+ assertThat(result.get(0), is((StaticOperand)literal("value1")));
+ assertThat(result.get(1), is((StaticOperand)literal(new Long(3))));
+ assertThat(result.get(2), is((StaticOperand)literal("4")));
+ }
+
+ protected Literal literal( Object literalValue ) {
+ return new Literal(literalValue);
+ }
+
+ // ----------------------------------------------------------------------------------------------------------------
// parseFullTextSearchExpression
// ----------------------------------------------------------------------------------------------------------------
14 years, 7 months
DNA SVN: r1292 - in trunk/dna-graph/src: main/java/org/jboss/dna/graph/query/model and 2 other directories.
by dna-commits@lists.jboss.org
Author: rhauch
Date: 2009-10-15 15:55:26 -0400 (Thu, 15 Oct 2009)
New Revision: 1292
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetCriteria.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
Log:
DNA-467 Add search/query support to the graph API
Added support for 'IN' clauses (called set criteria) in the Abstract Query Model. Also added similar support to the SQL parser, along with support for 'NOT IN'.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-10-15 19:54:48 UTC (rev 1291)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-10-15 19:55:26 UTC (rev 1292)
@@ -25,6 +25,8 @@
import java.math.BigDecimal;
import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -78,8 +80,10 @@
import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
import org.jboss.dna.graph.query.model.Selector;
import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.SetCriteria;
import org.jboss.dna.graph.query.model.SetQuery;
import org.jboss.dna.graph.query.model.Source;
+import org.jboss.dna.graph.query.model.StaticOperand;
import org.jboss.dna.graph.query.model.UpperCase;
import org.jboss.dna.graph.query.model.Visitors;
import org.jboss.dna.graph.query.model.SetQuery.Operation;
@@ -1693,6 +1697,17 @@
this.constraintBuilder = constraintBuilder;
}
+ public ConstraintBuilder isIn( Object firstLiteral,
+ Object... additionalLiterals ) {
+ CheckArg.isNotNull(firstLiteral, "firstLiteral");
+ Collection<StaticOperand> right = new ArrayList<StaticOperand>();
+ right.add(firstLiteral instanceof Literal ? (Literal)firstLiteral : new Literal(firstLiteral));
+ for (Object literal : additionalLiterals) {
+ right.add(literal instanceof Literal ? (Literal)literal : new Literal(literal));
+ }
+ return this.constraintBuilder.setConstraint(new SetCriteria(left, right));
+ }
+
/**
* Define the operator that will be used in the comparison, returning an interface that can be used to define the
* right-hand-side of the comparison.
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetCriteria.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetCriteria.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetCriteria.java 2009-10-15 19:55:26 UTC (rev 1292)
@@ -0,0 +1,96 @@
+/*
+ * 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.graph.query.model;
+
+import java.util.Collection;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A constraint that evaluates to true when the defined operation evaluates to true.
+ */
+@Immutable
+public class SetCriteria extends Constraint {
+
+ private final DynamicOperand left;
+ private final Collection<StaticOperand> setOperands;
+
+ public SetCriteria( DynamicOperand left,
+ Collection<StaticOperand> setOperands ) {
+ CheckArg.isNotNull(left, "left");
+ CheckArg.isNotNull(setOperands, "setOperands");
+ this.left = left;
+ this.setOperands = setOperands;
+ }
+
+ /**
+ * @return operand1
+ */
+ public final DynamicOperand getLeftOperand() {
+ return left;
+ }
+
+ /**
+ * @return operand2
+ */
+ public final Collection<StaticOperand> getRightOperands() {
+ return setOperands;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SetCriteria) {
+ SetCriteria that = (SetCriteria)obj;
+ if (!this.left.equals(that.left)) return false;
+ if (!this.setOperands.equals(that.setOperands)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetCriteria.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-10-15 19:54:48 UTC (rev 1291)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-10-15 19:55:26 UTC (rev 1292)
@@ -88,6 +88,8 @@
void visit( SameNodeJoinCondition obj );
+ void visit( SetCriteria obj );
+
void visit( SetQuery obj );
void visit( UpperCase obj );
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-10-15 19:54:48 UTC (rev 1291)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-10-15 19:55:26 UTC (rev 1292)
@@ -24,6 +24,7 @@
package org.jboss.dna.graph.query.model;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import org.jboss.dna.common.util.CheckArg;
@@ -155,12 +156,12 @@
@Override
public void visit( NodeDepth depth ) {
- super.visit(depth);
+ symbols.add(depth.getSelectorName());
}
@Override
public void visit( NodePath path ) {
- super.visit(path);
+ symbols.add(path.getSelectorName());
}
@Override
@@ -462,6 +463,14 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetCriteria)
+ */
+ public void visit( SetCriteria obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetQuery)
*/
public void visit( SetQuery obj ) {
@@ -853,6 +862,20 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetCriteria)
+ */
+ public void visit( SetCriteria setCriteria ) {
+ strategy.visit(setCriteria);
+ enqueue(setCriteria.getLeftOperand());
+ for (StaticOperand right : setCriteria.getRightOperands()) {
+ enqueue(right);
+ }
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetQuery)
*/
public void visit( SetQuery setQuery ) {
@@ -1331,6 +1354,25 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetCriteria)
+ */
+ public void visit( SetCriteria criteria ) {
+ criteria.getLeftOperand().accept(this);
+ append(" IN (");
+ Iterator<StaticOperand> iter = criteria.getRightOperands().iterator();
+ if (iter.hasNext()) {
+ iter.next().accept(this);
+ while (iter.hasNext()) {
+ append(',');
+ iter.next().accept(this);
+ }
+ }
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetQuery)
*/
public void visit( SetQuery query ) {
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-15 19:54:48 UTC (rev 1291)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-15 19:55:26 UTC (rev 1292)
@@ -25,6 +25,7 @@
import static org.jboss.dna.common.text.TokenStream.ANY_VALUE;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -84,6 +85,7 @@
import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
import org.jboss.dna.graph.query.model.Selector;
import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.SetCriteria;
import org.jboss.dna.graph.query.model.SetQuery;
import org.jboss.dna.graph.query.model.Source;
import org.jboss.dna.graph.query.model.StaticOperand;
@@ -414,9 +416,18 @@
String msg = GraphI18n.expectingConstraintCondition.text(name, pos2.getLine(), pos2.getColumn());
throw new ParsingException(pos, msg);
}
- Operator operator = parseComparisonOperator(tokens);
- StaticOperand right = parseStaticOperand(tokens, context);
- constraint = new Comparison(left, operator, right);
+ if (tokens.matches("IN", "(")) {
+ Collection<StaticOperand> staticOperands = parseInClause(tokens, context);
+ constraint = new SetCriteria(left, staticOperands);
+ } else if (tokens.matches("NOT", "IN", "(")) {
+ tokens.consume("NOT");
+ Collection<StaticOperand> staticOperands = parseInClause(tokens, context);
+ constraint = new Not(new SetCriteria(left, staticOperands));
+ } else {
+ Operator operator = parseComparisonOperator(tokens);
+ StaticOperand right = parseStaticOperand(tokens, context);
+ constraint = new Comparison(left, operator, right);
+ }
}
// else continue ...
}
@@ -435,6 +446,21 @@
return constraint;
}
+ protected Collection<StaticOperand> parseInClause( TokenStream tokens,
+ ExecutionContext context ) {
+ Collection<StaticOperand> result = new ArrayList<StaticOperand>();
+ tokens.consume("IN");
+ tokens.consume("(");
+ if (!tokens.canConsume(")")) {
+ // Not empty, so read the static operands ...
+ do {
+ result.add(parseStaticOperand(tokens, context));
+ } while (tokens.canConsume(','));
+ tokens.consume(")");
+ }
+ return result;
+ }
+
protected Term parseFullTextSearchExpression( String expression,
Position position ) {
try {
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-10-15 19:54:48 UTC (rev 1291)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-10-15 19:55:26 UTC (rev 1292)
@@ -1325,4 +1325,17 @@
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
"WHERE LOWER(UPPER(NAME(nodes))) = 'literal'"));
}
+
+ @Test
+ public void shouldBuilderQueryWithSetCriteria() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isIn("value1", "value2", "value3")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) IN ('value1','value2','value3')"));
+ }
}
14 years, 7 months
DNA SVN: r1291 - in trunk: dna-graph/src/main/java/org/jboss/dna/graph/query/parse and 3 other directories.
by dna-commits@lists.jboss.org
Author: rhauch
Date: 2009-10-15 15:54:48 -0400 (Thu, 15 Oct 2009)
New Revision: 1291
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/Queryable.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/QueryParsersTest.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
Log:
DNA-467 Add search/query support to the graph API
Broke out the language parsing from the QueryEngine and into a new QueryParsers. The QueryEngine should do everything in terms of a QueryCommand, since it's possible for the query engines to be layered, and parsing languages is completely orthogonal to execution.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java 2009-10-15 19:53:51 UTC (rev 1290)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -23,18 +23,11 @@
*/
package org.jboss.dna.graph.query;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import net.jcip.annotations.ThreadSafe;
-import org.jboss.dna.common.text.ParsingException;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.query.QueryResults.Statistics;
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.Constraint;
@@ -44,8 +37,6 @@
import org.jboss.dna.graph.query.model.Visitors;
import org.jboss.dna.graph.query.optimize.Optimizer;
import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
-import org.jboss.dna.graph.query.parse.InvalidQueryException;
-import org.jboss.dna.graph.query.parse.QueryParser;
import org.jboss.dna.graph.query.plan.CanonicalPlanner;
import org.jboss.dna.graph.query.plan.PlanHints;
import org.jboss.dna.graph.query.plan.PlanNode;
@@ -61,19 +52,30 @@
* A query engine that is able to execute formal queries expressed in the Graph API's {@link QueryCommand Abstract Query Model}.
*/
@ThreadSafe
-public class QueryEngine {
+public class QueryEngine implements Queryable {
private final Planner planner;
private final Optimizer optimizer;
private final Processor processor;
private final Schemata schemata;
- private final ConcurrentMap<String, QueryParser> parsers = new ConcurrentHashMap<String, QueryParser>();
+ /**
+ * Create a new query engine given the {@link Planner planner}, {@link Optimizer optimizer}, {@link Processor processor}, and
+ * {@link Schemata schemata}.
+ *
+ * @param planner the planner that should be used to generate canonical query plans for the queries; may be null if the
+ * {@link CanonicalPlanner} should be used
+ * @param optimizer the optimizer that should be used to optimize the canonical query plan; may be null if the
+ * {@link RuleBasedOptimizer} should be used
+ * @param processor the processor implementation that should be used to process the planned query and return the results
+ * @param schemata the schemata implementation, or null if an empty schema should be used (resulting in errors when named
+ * tables are queried)
+ * @throws IllegalArgumentException if the processor reference is null
+ */
public QueryEngine( Planner planner,
Optimizer optimizer,
Processor processor,
- Schemata schemata,
- QueryParser... parsers ) {
+ Schemata schemata ) {
CheckArg.isNotNull(processor, "processor");
this.planner = planner != null ? planner : new CanonicalPlanner();
this.optimizer = optimizer != null ? optimizer : new RuleBasedOptimizer();
@@ -85,93 +87,24 @@
return null;
}
};
- for (QueryParser parser : parsers) {
- if (parser != null) addLanguage(parser);
- }
}
/**
- * Add a language to this engine by supplying its parser.
+ * {@inheritDoc}
*
- * @param languageParser the query parser for the language
- * @throws IllegalArgumentException if the language parser is null
+ * @see org.jboss.dna.graph.query.Queryable#execute(org.jboss.dna.graph.ExecutionContext,
+ * org.jboss.dna.graph.query.model.QueryCommand)
*/
- public void addLanguage( QueryParser languageParser ) {
- CheckArg.isNotNull(languageParser, "languageParser");
- this.parsers.put(languageParser.getLanguage().toLowerCase(), languageParser);
- }
-
- /**
- * Remove from this engine the language with the given name.
- *
- * @param language the name of the language, which is to match the {@link QueryParser#getLanguage() language} of the parser
- * @return the parser for the language, or null if the engine had no support for the named language
- * @throws IllegalArgumentException if the language is null
- */
- public QueryParser removeLanguage( String language ) {
- CheckArg.isNotNull(language, "language");
- return this.parsers.remove(language.toLowerCase());
- }
-
- /**
- * Get the set of languages that this engine is capable of parsing.
- *
- * @return the unmodifiable copy of the set of languages; never null but possibly empty
- */
- public Set<String> getLanguages() {
- Set<String> result = new HashSet<String>();
- for (QueryParser parser : parsers.values()) {
- result.add(parser.getLanguage());
- }
- return Collections.unmodifiableSet(result);
- }
-
- /**
- * Execute the supplied query by planning, optimizing, and then processing it.
- *
- * @param context the context in which the query should be executed
- * @param language the language in which the query is expressed; must be one of the supported {@link #getLanguages()
- * languages}
- * @param query the query that is to be executed
- * @return the query results; never null
- * @throws IllegalArgumentException if the language, context or query references are null, or if the language is not know
- * @throws ParsingException if there is an error parsing the supplied query
- * @throws InvalidQueryException if the supplied query can be parsed but is invalid
- */
public QueryResults execute( ExecutionContext context,
- String language,
- String query ) {
- CheckArg.isNotNull(language, "language");
- CheckArg.isNotNull(context, "context");
- CheckArg.isNotNull(query, "query");
- QueryParser parser = parsers.get(language.toLowerCase());
- if (parser == null) {
- throw new IllegalArgumentException(GraphI18n.unknownQueryLanguage.text(language));
- }
- return execute(context, parser.parseQuery(query, context));
- }
-
- /**
- * Execute the supplied query by planning, optimizing, and then processing it.
- *
- * @param context the context in which the query should be executed
- * @param query the query that is to be executed
- * @return the query results; never null
- * @throws IllegalArgumentException if the context or query references are null
- */
- public QueryResults execute( ExecutionContext context,
QueryCommand query ) {
return execute(context, query, new PlanHints());
}
/**
- * Execute the supplied query by planning, optimizing, and then processing it.
+ * {@inheritDoc}
*
- * @param context the context in which the query should be executed
- * @param query the query that is to be executed
- * @param hints the hints for the execution; may be null if there are no hints
- * @return the query results; never null
- * @throws IllegalArgumentException if the context or query references are null
+ * @see org.jboss.dna.graph.query.Queryable#execute(org.jboss.dna.graph.ExecutionContext,
+ * org.jboss.dna.graph.query.model.QueryCommand, org.jboss.dna.graph.query.plan.PlanHints)
*/
public QueryResults execute( ExecutionContext context,
QueryCommand query,
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/Queryable.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/Queryable.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/Queryable.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -0,0 +1,58 @@
+/*
+ * 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.graph.query;
+
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.plan.PlanHints;
+
+/**
+ * An interface defining the ability to submit a query and obtain results.
+ */
+public interface Queryable {
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param context the context in which the query should be executed
+ * @param query the query that is to be executed
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ */
+ QueryResults execute( ExecutionContext context,
+ QueryCommand query );
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param context the context in which the query should be executed
+ * @param query the query that is to be executed
+ * @param hints the hints for the execution; may be null if there are no hints
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ */
+ QueryResults execute( ExecutionContext context,
+ QueryCommand query,
+ PlanHints hints );
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/Queryable.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -41,6 +41,7 @@
* model using the {@link QueryBuilder}, which provides a fluent API that makes it easy to create a query
* with Java code. The second (and more common) approach is to use a {@link org.jboss.dna.graph.query.parse.QueryParser}
* that parses a query represented in a specific language (like SQL or XPath) and then creates the query's equivalent abstract query model.
+ * There's even a {@link org.jboss.dna.graph.query.parse.QueryParsers} class that can manage the parsers for multiple languages.
* </p>
* <p>
* The abstract query model classes are immutable, making them very easily shared or reused if that is advantageous.
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -0,0 +1,215 @@
+/*
+ * 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.graph.query.parse;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.query.model.QueryCommand;
+
+/**
+ * A thread-safe collection of {@link QueryParser} implementations that can be used to parse queries by language.
+ */
+@ThreadSafe
+public class QueryParsers {
+
+ private final ConcurrentMap<String, QueryParser> parsers = new ConcurrentHashMap<String, QueryParser>();
+
+ /**
+ * Create a new collection of the supplied {@link QueryParser} objects.
+ *
+ * @param parsers the parsers
+ */
+ public QueryParsers( QueryParser... parsers ) {
+ if (parsers != null) {
+ for (QueryParser parser : parsers) {
+ if (parser != null) addLanguage(parser);
+ }
+ }
+ }
+
+ /**
+ * Create a new collection of the supplied {@link QueryParser} objects.
+ *
+ * @param parsers the parsers
+ */
+ public QueryParsers( Iterable<QueryParser> parsers ) {
+ if (parsers != null) addLanguages(parsers);
+ }
+
+ /**
+ * Add a language to this engine by supplying its parser.
+ *
+ * @param languageParser the query parser for the language
+ * @throws IllegalArgumentException if the language parser is null
+ */
+ public void addLanguage( QueryParser languageParser ) {
+ CheckArg.isNotNull(languageParser, "languageParser");
+ this.parsers.put(languageParser.getLanguage().toLowerCase(), languageParser);
+ }
+
+ /**
+ * Add one or more languages to this engine by supplying the corresponding parsers.
+ *
+ * @param firstLanguage the query parser for the first language
+ * @param additionalLanguages the query parsers for the additional languages
+ * @throws IllegalArgumentException if the language parser is null
+ */
+ public void addLanguages( QueryParser firstLanguage,
+ QueryParser... additionalLanguages ) {
+ addLanguage(firstLanguage);
+ for (QueryParser language : additionalLanguages) {
+ addLanguage(language);
+ }
+ }
+
+ /**
+ * Add one or more languages to this engine by supplying the corresponding parsers.
+ *
+ * @param languages the query parsers for the languages that are to be added
+ * @throws IllegalArgumentException if the iterable reference is null
+ */
+ public void addLanguages( Iterable<QueryParser> languages ) {
+ CheckArg.isNotNull(languages, "languages");
+ for (QueryParser language : languages) {
+ addLanguage(language);
+ }
+ }
+
+ /**
+ * Remove from this engine the language with the given name.
+ *
+ * @param language the name of the language, which is to match the {@link QueryParser#getLanguage() language} of the parser
+ * @return the parser for the language, or null if the engine had no support for the named language
+ * @throws IllegalArgumentException if the language is null
+ */
+ public QueryParser removeLanguage( String language ) {
+ CheckArg.isNotNull(language, "language");
+ return this.parsers.remove(language.toLowerCase());
+ }
+
+ /**
+ * Remove from this engine the language with the given name.
+ *
+ * @param firstLanguage the name of the first language to remove, which must match the {@link QueryParser#getLanguage()
+ * language} of the parser
+ * @param additionalLanguages the names of the additional languages to remove, which must match the
+ * {@link QueryParser#getLanguage() language} of the parser
+ * @return the parser for the language, or null if the engine had no support for the named language
+ * @throws IllegalArgumentException if the language is null
+ */
+ public Collection<QueryParser> removeLanguages( String firstLanguage,
+ String... additionalLanguages ) {
+ Collection<QueryParser> removed = new HashSet<QueryParser>();
+ QueryParser parser = removeLanguage(firstLanguage);
+ if (parser != null) removed.add(parser);
+ for (String language : additionalLanguages) {
+ parser = removeLanguage(language);
+ if (parser != null) removed.add(parser);
+ }
+ return removed;
+ }
+
+ /**
+ * Get the set of languages that this engine is capable of parsing.
+ *
+ * @return the unmodifiable copy of the set of languages; never null but possibly empty
+ */
+ public Set<String> getLanguages() {
+ Set<String> result = new HashSet<String>();
+ for (QueryParser parser : parsers.values()) {
+ result.add(parser.getLanguage());
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param context the context in which the query should be executed
+ * @param language the language in which the query is expressed; must be one of the supported {@link #getLanguages()
+ * languages}
+ * @param query the query that is to be executed
+ * @return the parsed query command; never null
+ * @throws IllegalArgumentException if the language, context or query references are null, or if the language is not known
+ * @throws ParsingException if there is an error parsing the supplied query
+ * @throws InvalidQueryException if the supplied query can be parsed but is invalid
+ */
+ public QueryCommand parse( ExecutionContext context,
+ String language,
+ String query ) {
+ CheckArg.isNotNull(language, "language");
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(query, "query");
+ QueryParser parser = parsers.get(language.toLowerCase());
+ if (parser == null) {
+ throw new IllegalArgumentException(GraphI18n.unknownQueryLanguage.text(language));
+ }
+ return parser.parseQuery(query, context);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ // There won't be too many instances of QueryParsers, so have them all return the same hash code
+ return 1;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof QueryParsers) {
+ QueryParsers that = (QueryParsers)obj;
+ return this.getLanguages().equals(that.getLanguages());
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Query parsers: " + parsers.keySet();
+ }
+}
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-15 19:53:51 UTC (rev 1290)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -118,6 +118,31 @@
/**
* {@inheritDoc}
*
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return LANGUAGE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof QueryParser) {
+ QueryParser that = (QueryParser)obj;
+ return this.getLanguage().equals(that.getLanguage());
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(String, ExecutionContext)
*/
public QueryCommand parseQuery( String query,
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/QueryParsersTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/QueryParsersTest.java (rev 0)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/QueryParsersTest.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -0,0 +1,181 @@
+/*
+ * 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.graph.query.parse;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class QueryParsersTest {
+
+ private QueryParsers parsers;
+ private QueryParser parser1;
+ private QueryParser parser2;
+ private QueryParser parser3;
+
+ @Before
+ public void beforeEach() {
+ parsers = new QueryParsers();
+ parser1 = new MockParser("language 1");
+ parser2 = new MockParser("language 2");
+ parser3 = new MockParser("language 3");
+ }
+
+ @Test
+ public void shouldAllowNullArrayInConstructor() {
+ parsers = new QueryParsers((QueryParser[])null);
+ assertThat(parsers.getLanguages().isEmpty(), is(true));
+ }
+
+ @Test
+ public void shouldAllowNullIterableInConstructor() {
+ parsers = new QueryParsers((Iterable<QueryParser>)null);
+ assertThat(parsers.getLanguages().isEmpty(), is(true));
+ }
+
+ @Test
+ public void shouldAllowOneQueryParserReferenceInConstructor() {
+ parsers = new QueryParsers(parser1);
+ assertThat(parsers.getLanguages().size(), is(1));
+ assertThat(parsers.getLanguages().contains(parser1.getLanguage()), is(true));
+ }
+
+ @Test
+ public void shouldAllowTwoQueryParserReferencesInConstructor() {
+ parsers = new QueryParsers(parser1, parser2);
+ assertThat(parsers.getLanguages().size(), is(2));
+ assertThat(parsers.getLanguages().contains(parser1.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser2.getLanguage()), is(true));
+ }
+
+ @Test
+ public void shouldAllowThreeQueryParserReferencesInConstructor() {
+ parsers = new QueryParsers(parser1, parser2, parser3);
+ assertThat(parsers.getLanguages().size(), is(3));
+ assertThat(parsers.getLanguages().contains(parser1.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser2.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser3.getLanguage()), is(true));
+ }
+
+ @Test
+ public void shouldOverwriteQueryParsersWithSameLanguageInConstructor() {
+ parser2 = new MockParser(parser1.getLanguage());
+ parsers = new QueryParsers(parser1, parser2, parser3);
+ assertThat(parsers.getLanguages().size(), is(2));
+ assertThat(parsers.getLanguages().contains(parser2.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser3.getLanguage()), is(true));
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullQueryParserReferenceInAddLanguagesMethod() {
+ parsers.addLanguages((QueryParser)null);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldAllowNullIterableInAddLanguagesMethod() {
+ parsers.addLanguages((Iterable<QueryParser>)null);
+ }
+
+ @Test
+ public void shouldAddOneQueryParserReference() {
+ parsers.addLanguage(parser1);
+ assertThat(parsers.getLanguages().size(), is(1));
+ assertThat(parsers.getLanguages().contains(parser1.getLanguage()), is(true));
+ }
+
+ @Test
+ public void shouldAddTwoQueryParserReferences() {
+ parsers.addLanguages(parser1, parser2);
+ assertThat(parsers.getLanguages().size(), is(2));
+ assertThat(parsers.getLanguages().contains(parser1.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser2.getLanguage()), is(true));
+ }
+
+ @Test
+ public void shouldAddThreeQueryParserReferences() {
+ parsers.addLanguages(parser1, parser2, parser3);
+ assertThat(parsers.getLanguages().size(), is(3));
+ assertThat(parsers.getLanguages().contains(parser1.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser2.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser3.getLanguage()), is(true));
+ }
+
+ @Test
+ public void shouldOverwriteQueryParsersWithSameLanguageWhenAddingQueryParser() {
+ parser2 = new MockParser(parser1.getLanguage());
+ parsers.addLanguages(parser1, parser2, parser3);
+ assertThat(parsers.getLanguages().size(), is(2));
+ assertThat(parsers.getLanguages().contains(parser2.getLanguage()), is(true));
+ assertThat(parsers.getLanguages().contains(parser3.getLanguage()), is(true));
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailToParseUnknownLanguage() {
+ parsers.parse(new ExecutionContext(), "unknown language", "This is a bogus query");
+ }
+
+ protected static class MockParser implements QueryParser {
+ private final String language;
+
+ protected MockParser( String language ) {
+ this.language = language;
+ assert this.language != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.parse.QueryParser#getLanguage()
+ */
+ public String getLanguage() {
+ return language;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(java.lang.String, org.jboss.dna.graph.ExecutionContext)
+ */
+ public QueryCommand parseQuery( String query,
+ ExecutionContext context ) throws InvalidQueryException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Mock parser for " + language;
+ }
+ }
+}
Property changes on: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/QueryParsersTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java 2009-10-15 19:53:51 UTC (rev 1290)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -51,6 +51,31 @@
/**
* {@inheritDoc}
*
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return LANGUAGE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof QueryParser) {
+ QueryParser that = (QueryParser)obj;
+ return this.getLanguage().equals(that.getLanguage());
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(java.lang.String, org.jboss.dna.graph.ExecutionContext)
*/
public QueryCommand parseQuery( String query,
Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java 2009-10-15 19:53:51 UTC (rev 1290)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java 2009-10-15 19:54:48 UTC (rev 1291)
@@ -25,10 +25,7 @@
import java.io.IOException;
import java.util.LinkedList;
-import java.util.Set;
import org.apache.lucene.queryParser.ParseException;
-import org.jboss.dna.common.text.ParsingException;
-import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.QueryEngine;
import org.jboss.dna.graph.query.QueryResults;
@@ -37,8 +34,6 @@
import org.jboss.dna.graph.query.optimize.Optimizer;
import org.jboss.dna.graph.query.optimize.OptimizerRule;
import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
-import org.jboss.dna.graph.query.parse.InvalidQueryException;
-import org.jboss.dna.graph.query.parse.QueryParser;
import org.jboss.dna.graph.query.plan.CanonicalPlanner;
import org.jboss.dna.graph.query.plan.PlanHints;
import org.jboss.dna.graph.query.plan.PlanNode;
@@ -58,66 +53,10 @@
engine = new QueryEngine(new CanonicalPlanner(), new LuceneOptimizer(), new LuceneProcessor(), schemata);
}
- public LuceneQueryEngine( Schemata schemata,
- QueryParser... languages ) {
- engine = new QueryEngine(new CanonicalPlanner(), new LuceneOptimizer(), new LuceneProcessor(), schemata, languages);
- }
-
/**
- * Add a language to this engine by supplying its parser.
- *
- * @param languageParser the query parser for the language
- * @throws IllegalArgumentException if the language parser is null
- */
- public void addLanguage( QueryParser languageParser ) {
- this.engine.addLanguage(languageParser);
- }
-
- /**
- * Remove from this engine the language with the given name.
- *
- * @param language the name of the language, which is to match the {@link QueryParser#getLanguage() language} of the parser
- * @return the parser for the language, or null if the engine had no support for the named language
- * @throws IllegalArgumentException if the language is null
- */
- public QueryParser removeLanguage( String language ) {
- return this.engine.removeLanguage(language);
- }
-
- /**
- * Get the set of languages that this engine is capable of parsing.
- *
- * @return the unmodifiable copy of the set of languages; never null but possibly empty
- */
- public Set<String> getLanguages() {
- return this.engine.getLanguages();
- }
-
- /**
* Execute the supplied query by planning, optimizing, and then processing it.
*
- * @param indexes the context in which the query should be executed
- * @param language the language in which the query is expressed; must be one of the supported {@link #getLanguages()
- * languages}
* @param query the query that is to be executed
- * @return the query results; never null
- * @throws IllegalArgumentException if the language, context or query references are null, or if the language is not know
- * @throws ParsingException if there is an error parsing the supplied query
- * @throws InvalidQueryException if the supplied query can be parsed but is invalid
- */
- public QueryResults execute( IndexContext indexes,
- String language,
- String query ) {
- CheckArg.isNotNull(language, "language");
- CheckArg.isNotNull(indexes, "indexes");
- CheckArg.isNotNull(query, "query");
- return engine.execute(indexes.context(), language, query);
- }
-
- /**
- * Execute the supplied query by planning, optimizing, and then processing it.
- *
- * @param query the query that is to be executed
* @param indexes the indexes that should be used to execute the query; never null
* @return the query results; never null
* @throws IllegalArgumentException if the context or query references are null
14 years, 7 months
DNA SVN: r1290 - in trunk/dna-graph/src/main/java/org/jboss/dna/graph/query: model and 5 other directories.
by dna-commits@lists.jboss.org
Author: rhauch
Date: 2009-10-15 15:53:51 -0400 (Thu, 15 Oct 2009)
New Revision: 1290
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/package-info.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
Log:
DNA-467 Add search/query support to the graph API
Added package documentation for the dna-graph packages.
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+/**
+ * <p>The Abstract Query Model is a vocabulary that can be used to construct a language-neutral representation of a query.
+ * It consists of a family of Java classes each represent the important semantic elements
+ * needed to fully define a query. A {@link QueryCommand} is the interface that represents a top-level query,
+ * with the {@link Query} and {@link SetQuery} classes being the two implementations.
+ * </p>
+ * <p>
+ * There are two ways to construct abstract query models. The first is to programmatically construct a query
+ * model using the {@link org.jboss.dna.graph.query.QueryBuilder}, which provides a fluent API that makes it easy to create a query
+ * with Java code. The second (and more common) approach is to use a {@link org.jboss.dna.graph.query.parse.QueryParser}
+ * that parses a query represented in a specific language (like SQL or XPath) and then creates the query's equivalent abstract query model.
+ * </p>
+ * <p>
+ * The abstract query model classes are immutable, making them very easily shared or reused if that is advantageous.
+ * </p>
+ */
+
+package org.jboss.dna.graph.query.model;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+/**
+ * This package contains the Optimizer interface, a rule-based optimizer implementation, and library of optimization rules.
+ * The Optimizer is responsible for evaluating, validating, and manipulating a canonical query plan to produce a more
+ * a single optimized query processing plan. The query plan is often changed in situ, although this is not required
+ * of the {@link org.jboss.dna.graph.query.optimize.Optimizer} implementations. A library of existing
+ * {@link org.jboss.dna.graph.query.optimize.OptimizerRule} classes is provided, though it's very easy to
+ * add more optimizer rules.
+ * </p>
+ * <p>
+ * The {@link org.jboss.dna.graph.query.optimize.RuleBasedOptimizer} is an implementation that optimizes a query
+ * using a stack of rules. A new stack is created for each rule, though the rules are required to be immutable and thus
+ * often shared and reused. And, the RuleBasedOptimizer is easily subclassed to define a custom stack of rules.
+ * </p>
+ */
+
+package org.jboss.dna.graph.query.optimize;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+/**
+ * <p>The Query API provides a mechanism for building and executing queries. The framework
+ * provides a reusable and extensible query engine that is capable of planning, validating, optimizing, and executing queries
+ * against a generic back-end system. Simply subclass the {@link org.jboss.dna.graph.query.process.QueryProcessor}
+ * that creates a {@link org.jboss.dna.graph.query.process.ProcessingComponent}
+ * to operates against the back-end system, and assemble a {@link QueryEngine} that can be used to provide a rich
+ * query capability.
+ * </p>
+ * <h3>Abstract query model</h3>
+ * <p>
+ * At the heart of the entire query system is a single representation of what constitutes a query. The
+ * {@link org.jboss.dna.graph.query.model abstract query model} defines a language-independent vocabulary
+ * for queries, and consists of a family of Java classes each represent the important semantic elements
+ * needed to fully define a query.
+ * </p>
+ * <p>
+ * There are two ways to construct abstract query models. The first is to programmatically construct a query
+ * model using the {@link QueryBuilder}, which provides a fluent API that makes it easy to create a query
+ * with Java code. The second (and more common) approach is to use a {@link org.jboss.dna.graph.query.parse.QueryParser}
+ * that parses a query represented in a specific language (like SQL or XPath) and then creates the query's equivalent abstract query model.
+ * </p>
+ * <p>
+ * The abstract query model classes are immutable, making them very easily shared or reused if that is advantageous.
+ * </p>
+ * <h3>SQL language</h3>
+ * <p>
+ * One of the {@link org.jboss.dna.graph.query.parse.QueryParser} implementation provided out of the box is
+ * the {@link org.jboss.dna.graph.query.parse.SqlQueryParser}, which understands a subset of SQL.
+ * </p>
+ * <h3>QueryEngine</h3>
+ * <p>
+ * The {@link QueryEngine} is the component that accepts and executes queries expressed as abstract query models.
+ * Each submitted query is planned, validated, optimized, and then processed to compute and return the final
+ * {@link QueryResults query results}.
+ * </p>
+ * <p>
+ * Note that the QueryEngine is thread-safe.
+ * </p>
+ * <h4>Planning</h4>
+ * <p>
+ * In the <i>planning</i> stage, a canonical plan is generated for each query. This plan is a tree of {@link org.jboss.dna.graph.query.plan.PlanNode}
+ * objects that each represent a different aspect of the query, and is a form that is easily manipulated by subsequent stages.
+ * Any implementation of {@link org.jboss.dna.graph.query.plan.Planner} can be used, though a {@link org.jboss.dna.graph.query.plan.CanonicalPlanner}
+ * implementation is provided and will be sufficient for most cases. In fact, the subsequent execution steps often
+ * require the plan to be in its canonical form, so for most situations it may be best to simply reuse the CanonicalPlanner
+ * and in other simply extend it.
+ * </p>
+ * <p>
+ * Note that query plans are mutable and not thread-safe, meaning that such plans are not intended to be shared
+ * or reused.
+ * </p>
+ * <h4>Optimization</h4>
+ * <p>
+ * In the <i>optimization</i> stage, the canonical query plan is evaluated, validated, and manipulated to produce a more
+ * a single optimized query processing plan. The query plan is often changed in situ, although this is not required
+ * of the {@link org.jboss.dna.graph.query.optimize.Optimizer} implementations. A library of existing
+ * {@link org.jboss.dna.graph.query.optimize.OptimizerRule} classes is provided, though it's very easy to
+ * add more optimizer rules.
+ * </p>
+ * <p>
+ * The {@link org.jboss.dna.graph.query.optimize.RuleBasedOptimizer} is an implementation that optimizes a query
+ * using a stack of rules. A new stack is created for each rule, though the rules are required to be immutable and thus
+ * often shared and reused. And, the RuleBasedOptimizer is easily subclassed to define a custom stack of rules.
+ * </p>
+ * <h4>Validation</h4>
+ * <p>
+ * The canonical planner or the optimization rules have access to the table and column definitions that may be
+ * queried. The query framework does not prescribe the semantics of a table or column, but instead provides
+ * a {@link org.jboss.dna.graph.query.validate.Schemata} interface that provides access to the immutable
+ * {@link org.jboss.dna.graph.query.validate.Schemata.Table} definitions (that then contain the
+ * {@link org.jboss.dna.graph.query.validate.Schemata.Column} and {@link org.jboss.dna.graph.query.validate.Schemata.Key} definitions).
+ * </p>
+ * <p>
+ * The canonical planner and a number of the provided optimizer rules use the Schemata to verify that the
+ * query is referencing an existing table and columns, whatever they are defined to be. Although any Schemata
+ * implementaiton can be used, the query framework provides an {@link org.jboss.dna.graph.query.validate.ImmutableSchemata}
+ * class with a {@link org.jboss.dna.graph.query.validate.ImmutableSchemata.Builder builder} with a fluent API
+ * that can create the corresponding immutable table, column and key definitions.
+ * </p>
+ * <h4>Processing</h4>
+ * <p>
+ * In the <i>processing</i> stage, the optimized query plan is used to construct and assemble the
+ * {@link org.jboss.dna.graph.query.process.ProcessingComponent} that correspond to the various parts of the
+ * quer plan. The resulting components form the basic processing engine for that query. At the bottom are
+ * the "access" components that perform the low-level access of the tuples from the graph container.
+ * Above these are the other components that implement various operations, such as limits, joins (using merge and
+ * nested loop algorithms), unions, intersects, distinct, sorts, and even column projections. At the top is
+ * a single component that produces tuples that represent the results of the query.
+ * </p>
+ * <p>
+ * Once the {@link org.jboss.dna.graph.query.process.QueryProcessor} creates the ProcessingComponent assembly,
+ * the top-level component is executed. Execution involves requesting from the child processing component(s) the next batch of results,
+ * processing each of the tuples according to the specific ProcessingComponent algorithm, and finally returning
+ * the processed tuples.
+ * </p>
+ * <h3>QueryResults</h3>
+ * <p>
+ * A query over a graph of content will result in a set of nodes that matched the criteria specified in the query.
+ * Each node contained in the results will be identified by its {@link org.jboss.dna.graph.Location} as well
+ * as any values for the selected properties. Typically, queries will result in a single node per row, although
+ * joins may result in multiple rows per row.
+ * </p>
+ */
+
+package org.jboss.dna.graph.query;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+/**
+ * This package defines the {@link QueryParser} interface, which defines a component that can parse a query represented
+ * in a specific language and produce the corresponding {@link org.jboss.dna.graph.query.model abstract query model} representation.
+ * <p>
+ * Several parsers are provided, including one that parses a subset of SQL and another that parses the full-text search
+ * expressions. However, other query parsers can easily be created and used.
+ * </p>
+ */
+
+package org.jboss.dna.graph.query.parse;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+/**
+ * This package defines the {@link Planner} interface, the {@link CanonicalPlanner} implementation, and the
+ * {@link PlanNode} class that is used to represent a canonical query plan.
+ * <p>
+ * The query plan is a tree of {@link org.jboss.dna.graph.query.plan.PlanNode} objects that each represent a
+ * different aspect of the query, and is a form that is easily manipulated by subsequent stages.
+ * Any implementation of {@link org.jboss.dna.graph.query.plan.Planner} can be used, though a {@link org.jboss.dna.graph.query.plan.CanonicalPlanner}
+ * implementation is provided and will be sufficient for most cases. In fact, the subsequent execution steps often
+ * require the plan to be in its canonical form, so for most situations it may be best to simply reuse the CanonicalPlanner
+ * and in other simply extend it.
+ * </p>
+ * <p>
+ * Note that query plans are mutable and not thread-safe, meaning that such plans are not intended to be shared
+ * or reused.
+ * </p>
+
+ */
+
+package org.jboss.dna.graph.query.plan;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+/**
+ * This package defines the {@link QueryProcessor} interface, which is responsible for constructing for each query
+ * a tree of {@link ProcessingComponent} objects that each are responsible for processing a specific aspect of
+ * the query and returning the tuples to the parent component.
+ * <p>
+ * At the bottom are the "access" components that perform the low-level access of the tuples from the graph container.
+ * Above these are the other components that implement various operations, such as limits, joins (using merge and
+ * nested loop algorithms), unions, intersects, distinct, sorts, and even column projections. At the top is
+ * a single component that produces tuples that represent the results of the query.
+ * </p>
+ * <p>
+ * Once the {@link org.jboss.dna.graph.query.process.QueryProcessor} creates the ProcessingComponent assembly,
+ * the top-level component is executed. Execution involves requesting from the child processing component(s) the next batch of results,
+ * processing each of the tuples according to the specific ProcessingComponent algorithm, and finally returning
+ * the processed tuples.
+ * </p>
+ */
+
+package org.jboss.dna.graph.query.process;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java 2009-10-15 19:53:51 UTC (rev 1290)
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+/**
+ * This package provides the interfaces that define the tables and columns that can be queried. Though the query framework does not prescribe
+ * the semantics of a table or column, it does provide
+ * a {@link org.jboss.dna.graph.query.validate.Schemata} interface that provides access to the immutable
+ * {@link org.jboss.dna.graph.query.validate.Schemata.Table} definitions (that then contain the
+ * {@link org.jboss.dna.graph.query.validate.Schemata.Column} and {@link org.jboss.dna.graph.query.validate.Schemata.Key} definitions).
+ * <p>
+ * Although any Schemata implementaiton can be used, the query framework provides an {@link org.jboss.dna.graph.query.validate.ImmutableSchemata}
+ * class with a {@link org.jboss.dna.graph.query.validate.ImmutableSchemata.Builder builder} with a fluent API
+ * that can create the corresponding immutable table, column and key definitions.
+ */
+
+package org.jboss.dna.graph.query.validate;
+
Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
14 years, 7 months
DNA SVN: r1289 - in trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client: jobs and 1 other directories.
by dna-commits@lists.jboss.org
Author: elvisisking
Date: 2009-10-07 12:18:30 -0400 (Wed, 07 Oct 2009)
New Revision: 1289
Modified:
trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/RestClientI18n.properties
trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/jobs/PublishJob.java
trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/DnaContentProvider.java
trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/ServerView.java
Log:
DNA 533 Decorator Does Not Add/Remove Overaly Image Immediately After The Publishing Operation: DnaContentProvider had some left over overrided, unimplemented interface methods that I just deleted. Everything started working.
DNA 538 Duplicate Error Messages Generated When A Connection To A Server Cannot Be Established: If a connection to a server fails, we won't try connecting again until a certain amount of time has gone by (currently set to 10 seconds).
Modified: trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/RestClientI18n.properties
===================================================================
--- trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/RestClientI18n.properties 2009-10-07 15:43:05 UTC (rev 1288)
+++ trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/RestClientI18n.properties 2009-10-07 16:18:30 UTC (rev 1289)
@@ -139,7 +139,7 @@
serverReconnectActionToolTip = Reconnect to the selected server
serverManagerGetRepositoriesExceptionMsg = There was a problem obtaining repositories for the server "{0}."
-serverManagerGetWorkspacesExceptionMsg = There was a problem obtaining workspaces for repository "{0}."
+serverManagerGetWorkspacesExceptionMsg = There was a problem obtaining workspaces for "{0}."
serverPageAuthenticationGroupTitle = Authentication
serverPageInvalidServerProperties = Cannot construct a server because all server properties are not valid
Modified: trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/jobs/PublishJob.java
===================================================================
--- trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/jobs/PublishJob.java 2009-10-07 15:43:05 UTC (rev 1288)
+++ trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/jobs/PublishJob.java 2009-10-07 16:18:30 UTC (rev 1289)
@@ -226,11 +226,6 @@
// set persistent property on resource indicating it has been published
if (!status.isError()) {
resourceHelper.addPublishedProperty(eclipseFile, workspace);
- DnaContentProvider decorator = DnaContentProvider.getDecorator();
-
- if (decorator != null) {
- // decorator.refresh(eclipseFile);
- }
}
} else {
status = getServerManager().unpublish(this.workspace, path, file);
@@ -238,17 +233,19 @@
// clear persistent property on resource indicating it has been unpublished
if (!status.isError()) {
resourceHelper.removePublishedProperty(eclipseFile, workspace);
- DnaContentProvider decorator = DnaContentProvider.getDecorator();
-
- if (decorator != null) {
- // decorator.refresh(eclipseFile);
- }
}
}
++numProcessed;
monitor.worked(1);
+ // let decorator know publishing state has changed on this file
+ DnaContentProvider decorator = DnaContentProvider.getDecorator();
+
+ if (decorator != null) {
+ decorator.refresh(eclipseFile);
+ }
+
// write outcome message to console
if (isPublishing() && status.isOk()) {
URL url = getServerManager().getUrl(file, path, this.workspace);
Modified: trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/DnaContentProvider.java
===================================================================
--- trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/DnaContentProvider.java 2009-10-07 15:43:05 UTC (rev 1288)
+++ trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/DnaContentProvider.java 2009-10-07 16:18:30 UTC (rev 1289)
@@ -24,11 +24,15 @@
package org.jboss.dna.eclipse.jcr.rest.client.views;
import static org.jboss.dna.eclipse.jcr.rest.client.IUiConstants.PUBLISHED_OVERLAY_IMAGE;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import net.jcip.annotations.GuardedBy;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IDecoration;
-import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
@@ -40,7 +44,9 @@
import org.jboss.dna.eclipse.jcr.rest.client.DnaResourceHelper;
import org.jboss.dna.eclipse.jcr.rest.client.RestClientI18n;
import org.jboss.dna.eclipse.jcr.rest.client.Utils;
+import org.jboss.dna.web.jcr.rest.client.IServerRegistryListener;
import org.jboss.dna.web.jcr.rest.client.ServerManager;
+import org.jboss.dna.web.jcr.rest.client.ServerRegistryEvent;
import org.jboss.dna.web.jcr.rest.client.Status;
import org.jboss.dna.web.jcr.rest.client.Status.Severity;
import org.jboss.dna.web.jcr.rest.client.domain.IDnaObject;
@@ -49,9 +55,11 @@
import org.jboss.dna.web.jcr.rest.client.domain.Workspace;
/**
- * The <code>DnaContentProvider</code> is a content and label provider for DNA repositories.
+ * The <code>DnaContentProvider</code> is a content and label provider for DNA repositories. This class <strong>MUST</strong> be
+ * registered, and then unregistered, to receive server registry events.
*/
-public final class DnaContentProvider extends ColumnLabelProvider implements ILightweightLabelDecorator, ITreeContentProvider {
+public final class DnaContentProvider extends ColumnLabelProvider
+ implements ILightweightLabelDecorator, IServerRegistryListener, ITreeContentProvider {
// ===========================================================================================================================
// Constants
@@ -62,6 +70,11 @@
*/
private static final String ID = "org.jboss.dna.eclipse.jcr.rest.client.dnaDecorator"; //$NON-NLS-1$
+ /**
+ * If a server connection cannot be established, wait this amount of time before trying again.
+ */
+ private static final long RETRY_DURATION = 10000;
+
// ===========================================================================================================================
// Class Methods
// ===========================================================================================================================
@@ -79,11 +92,23 @@
return null;
}
- // ===========================================dnaDecorator================================================================================
+ // ===========================================================================================================================
// Fields
// ===========================================================================================================================
/**
+ * Servers that a connection can't be established. Value is the last time a establishing a connection was tried.
+ */
+ @GuardedBy( "offlineServersLock" )
+ private final Map<Server, Long> offlineServerMap = new HashMap<Server, Long>();
+
+ /**
+ * Lock used for when accessing the offline server map. The map will be accessed in different threads as the decorator runs in
+ * its own thread (not the UI thread).
+ */
+ private final ReadWriteLock offlineServersLock = new ReentrantReadWriteLock();
+
+ /**
* The server manager where the server registry is managed.
*/
private ServerManager serverManager;
@@ -93,13 +118,15 @@
// ===========================================================================================================================
/**
- * {@inheritDoc}
- *
- * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
+ * @param server the server that is offline
*/
- @Override
- public void addListener( ILabelProviderListener listener ) {
- // nothing to do
+ private void addOfflineServer( Server server ) {
+ try {
+ this.offlineServersLock.writeLock().lock();
+ this.offlineServerMap.put(server, System.currentTimeMillis());
+ } finally {
+ this.offlineServersLock.writeLock().unlock();
+ }
}
/**
@@ -117,11 +144,18 @@
return;
}
- // must be an IDnaObject
if (getServerManager() != null) {
if (element instanceof Server) {
- Status status = getServerManager().ping((Server)element);
- overlay = Utils.getOverlayImage(status);
+ Server server = (Server)element;
+
+ if (isOkToConnect(server)) {
+ Status status = getServerManager().ping(server);
+ overlay = Utils.getOverlayImage(status);
+
+ if (status.isError()) {
+ addOfflineServer(server);
+ }
+ }
} else if ((element instanceof IFile) && new DnaResourceHelper(getServerManager()).isPublished((IFile)element)) {
overlay = Activator.getDefault().getImageDescriptor(PUBLISHED_OVERLAY_IMAGE);
}
@@ -152,22 +186,30 @@
assert (parentElement instanceof IDnaObject);
if (getServerManager() != null) {
- try {
- if ((parentElement instanceof Server) && getServerManager().ping((Server)parentElement).isOk()) {
- return getServerManager().getRepositories((Server)parentElement).toArray();
+ if (parentElement instanceof Server) {
+ Server server = (Server)parentElement;
+
+ if (isOkToConnect(server)) {
+ try {
+ return getServerManager().getRepositories(server).toArray();
+ } catch (Exception e) {
+ addOfflineServer(server);
+ String msg = RestClientI18n.serverManagerGetRepositoriesExceptionMsg.text(server.getShortDescription());
+ Activator.getDefault().log(new Status(Severity.ERROR, msg, e));
+ }
}
- } catch (Exception e) {
- String msg = RestClientI18n.serverManagerGetRepositoriesExceptionMsg.text(((Server)parentElement).getShortDescription());
- Activator.getDefault().log(new Status(Severity.ERROR, msg, e));
- }
+ } else if (parentElement instanceof Repository) {
+ Repository repository = (Repository)parentElement;
- try {
- if (parentElement instanceof Repository) {
- return getServerManager().getWorkspaces((Repository)parentElement).toArray();
+ if (isOkToConnect(repository.getServer())) {
+ try {
+ return getServerManager().getWorkspaces(repository).toArray();
+ } catch (Exception e) {
+ addOfflineServer(repository.getServer());
+ String msg = RestClientI18n.serverManagerGetWorkspacesExceptionMsg.text(repository.getShortDescription());
+ Activator.getDefault().log(new Status(Severity.ERROR, msg, e));
+ }
}
- } catch (Exception e) {
- String msg = RestClientI18n.serverManagerGetWorkspacesExceptionMsg.text();
- Activator.getDefault().log(new Status(Severity.ERROR, msg, e));
}
}
@@ -291,20 +333,9 @@
public void inputChanged( Viewer viewer,
Object oldInput,
Object newInput ) {
- // this.viewer = (StructuredViewer)viewer;
+ // nothing to do
}
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, java.lang.String)
- */
- @Override
- public boolean isLabelProperty( Object element,
- String property ) {
- return false;
- }
-
public void refresh( final Object element ) {
final Display display = Display.getDefault();
@@ -327,13 +358,68 @@
}
/**
+ * Determines if a try to connect to a server should be done based on the last time a try was done and failed.
+ *
+ * @param server the server being checked
+ * @return <code>true</code> if it is OK to try and connect
+ */
+ private boolean isOkToConnect( Server server ) {
+ boolean check = false; // check map for time
+
+ try {
+ this.offlineServersLock.readLock().lock();
+ check = this.offlineServerMap.containsKey(server);
+ } finally {
+ this.offlineServersLock.readLock().unlock();
+ }
+
+ if (check) {
+ try {
+ this.offlineServersLock.writeLock().lock();
+
+ if (this.offlineServerMap.containsKey(server)) {
+ long checkTime = this.offlineServerMap.get(server);
+
+ // OK to try and connect if last failed attempt was too long ago
+ if ((System.currentTimeMillis() - checkTime) > RETRY_DURATION) {
+ this.offlineServerMap.remove(server);
+ return true;
+ }
+
+ // don't try and connect because we just tried and failed
+ return false;
+ }
+ } finally {
+ this.offlineServersLock.writeLock().unlock();
+ }
+ }
+
+ // OK to try and connect
+ return true;
+ }
+
+ /**
* {@inheritDoc}
*
- * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
+ * @see org.jboss.dna.web.jcr.rest.client.IServerRegistryListener#serverRegistryChanged(org.jboss.dna.web.jcr.rest.client.ServerRegistryEvent)
*/
@Override
- public void removeListener( ILabelProviderListener listener ) {
- // nothing to do
+ public Exception[] serverRegistryChanged( ServerRegistryEvent event ) {
+ Exception[] errors = null;
+
+ // only care about servers being removed or updated
+ if (event.isRemove() || event.isUpdate()) {
+ try {
+ this.offlineServersLock.writeLock().lock();
+ this.offlineServerMap.remove(event.getServer());
+ } catch (Exception e) {
+ errors = new Exception[] {e};
+ } finally {
+ this.offlineServersLock.writeLock().unlock();
+ }
+ }
+
+ return errors;
}
}
Modified: trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/ServerView.java
===================================================================
--- trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/ServerView.java 2009-10-07 15:43:05 UTC (rev 1288)
+++ trunk/tools/org.jboss.dna.eclipse.jcr.rest.client/src/org/jboss/dna/eclipse/jcr/rest/client/views/ServerView.java 2009-10-07 16:18:30 UTC (rev 1289)
@@ -218,6 +218,7 @@
// register to receive changes to the server registry
getServerManager().addRegistryListener(this);
+ getServerManager().addRegistryListener(this.provider);
// register with the help system
IWorkbenchHelpSystem helpSystem = Activator.getDefault().getWorkbench().getHelpSystem();
@@ -232,6 +233,11 @@
@Override
public void dispose() {
getServerManager().removeRegistryListener(this);
+
+ if (this.provider != null) {
+ getServerManager().removeRegistryListener(this.provider);
+ }
+
super.dispose();
}
@@ -283,14 +289,14 @@
}
}
});
-
+
// don't want cut, copy, or paste actions so hook them up with a disabled action
class NoOpAction extends Action {
NoOpAction() {
setEnabled(false);
}
}
-
+
IAction noop = new NoOpAction();
bars.setGlobalActionHandler(ActionFactory.CUT.getId(), noop);
bars.setGlobalActionHandler(ActionFactory.COPY.getId(), noop);
14 years, 7 months
DNA SVN: r1288 - trunk/tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite.
by dna-commits@lists.jboss.org
Author: elvisisking
Date: 2009-10-07 11:43:05 -0400 (Wed, 07 Oct 2009)
New Revision: 1288
Added:
trunk/tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite/readme.txt
Log:
Added a "readme" with steps to create an update site archive. Also include information on how to install archive into Eclipse and to how to test using JBoss.
Added: trunk/tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite/readme.txt
===================================================================
--- trunk/tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite/readme.txt (rev 0)
+++ trunk/tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite/readme.txt 2009-10-07 15:43:05 UTC (rev 1288)
@@ -0,0 +1,29 @@
+***** Steps to create an update site archive *****
+
+1. Make sure you get a clean Maven build by running "mvn clean install" in the "trunk/" directory.
+
+2. In the "web/dna-web-jcr-rest-client/" directory run "mvn assembly:assembly" (which generates a dependencies jar)
+
+3. Copy these 2 files from "web/dna-web-jcr-rest-client/target/" to "tools/org.jboss.dna.eclipse.jcr.rest.client" project:
+ - dna-web-jcr-rest-client-[release]-jar-with-dependencies.jar
+ - dna-web-jcr-rest-client-[release]-sources.jar
+
+4. Update the feature version number by editing "feature.xml" in the "tools/org.jboss.dna.eclipse.jcr.rest.client.feature" project.
+
+5. Open "site.xml" in the tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite" project. If the feature on the "Site Map" tab
+ is not right, delete the existing one and then click "Add Feature" to add the correct version of the feature plugin.
+
+6. Once the feature is added click "Build All." This will create 2 folders (features, plugins) and 2 files (artifacts.xml, content.xml).
+
+7. At the top of both the artifacts.xml and content.xml, change the name to "JBoss DNA JCR REST Eclipse Client."
+
+8. Create a zip archive containing the following: features/, plugins/, site.xml, artifacts.xml, & contents.xml.
+
+***** Additional Information *****
+
+- To install the archive download and install a new copy of Eclipse. Use Help -> Software Updates to add the local archive site.
+
+- In order to test you need a DNA REST server. Copy the "resources.war" file found in the "targets/" directory of the
+ "web/dna-web-jcr-rest-war" project. For JBoss copy it into the "[JBOSS-INSTALL-DIR]/server/[CONFIG-NAME]/deploy/" directory. Then
+ in Eclipse using the DNA Servers View create a new server with the appropriate URL (for example: http://localhost:8080).
+ The user is "dnauser" and the passowrd is "password".
\ No newline at end of file
Property changes on: trunk/tools/org.jboss.dna.eclipse.jcr.rest.client.updatesite/readme.txt
___________________________________________________________________
Name: svn:mime-type
+ text/plain
14 years, 7 months
DNA SVN: r1287 - in trunk/web/dna-web-jcr-rest-client/src: test/java/org/jboss/dna/web/jcr/rest/client/json and 3 other directories.
by dna-commits@lists.jboss.org
Author: elvisisking
Date: 2009-10-05 15:00:27 -0400 (Mon, 05 Oct 2009)
New Revision: 1287
Added:
trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-().txt
Removed:
trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-*().txt
Modified:
trunk/web/dna-web-jcr-rest-client/src/main/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClient.java
trunk/web/dna-web-jcr-rest-client/src/test/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClientTest.java
Log:
Changed file name of a test file which was causing problems on MS Windows.
Modified: trunk/web/dna-web-jcr-rest-client/src/main/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClient.java
===================================================================
--- trunk/web/dna-web-jcr-rest-client/src/main/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClient.java 2009-10-03 04:09:29 UTC (rev 1286)
+++ trunk/web/dna-web-jcr-rest-client/src/main/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClient.java 2009-10-05 19:00:27 UTC (rev 1287)
@@ -92,13 +92,12 @@
connection.write(fileNode.getContent());
// make sure node was created
- if (connection.getResponseCode() != HttpURLConnection.HTTP_CREATED) {
+ int responseCode = connection.getResponseCode();
+
+ if (responseCode != HttpURLConnection.HTTP_CREATED) {
// node was not created
- this.logger.error(RestClientI18n.connectionErrorMsg, connection.getResponseCode(), "createFileNode"); //$NON-NLS-1$
- String msg = RestClientI18n.createFileFailedMsg.text(file.getName(),
- path,
- workspace.getName(),
- connection.getResponseCode());
+ this.logger.error(RestClientI18n.connectionErrorMsg, responseCode, "createFileNode"); //$NON-NLS-1$
+ String msg = RestClientI18n.createFileFailedMsg.text(file.getName(), path, workspace.getName(), responseCode);
throw new RuntimeException(msg);
}
} finally {
@@ -127,10 +126,12 @@
connection.write(folderNode.getContent());
// make sure node was created
- if (connection.getResponseCode() != HttpURLConnection.HTTP_CREATED) {
+ int responseCode = connection.getResponseCode();
+
+ if (responseCode != HttpURLConnection.HTTP_CREATED) {
// node was not created
- this.logger.error(RestClientI18n.connectionErrorMsg, connection.getResponseCode(), "createFolderNode"); //$NON-NLS-1$
- String msg = RestClientI18n.createFolderFailedMsg.text(path, workspace.getName(), connection.getResponseCode());
+ this.logger.error(RestClientI18n.connectionErrorMsg, responseCode, "createFolderNode"); //$NON-NLS-1$
+ String msg = RestClientI18n.createFolderFailedMsg.text(path, workspace.getName(), responseCode);
throw new RuntimeException(msg);
}
} finally {
@@ -195,13 +196,15 @@
HttpClientConnection connection = connect(server, serverNode.getFindRepositoriesUrl(), RequestMethod.GET);
try {
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ int responseCode = connection.getResponseCode();
+
+ if (responseCode == HttpURLConnection.HTTP_OK) {
return serverNode.getRepositories(connection.read());
}
// not a good response code
- this.logger.error(RestClientI18n.connectionErrorMsg, connection.getResponseCode(), "getRepositories"); //$NON-NLS-1$
- String msg = RestClientI18n.getRepositoriesFailedMsg.text(server.getName(), connection.getResponseCode());
+ this.logger.error(RestClientI18n.connectionErrorMsg, responseCode, "getRepositories"); //$NON-NLS-1$
+ String msg = RestClientI18n.getRepositoriesFailedMsg.text(server.getName(), responseCode);
throw new RuntimeException(msg);
} finally {
if (connection != null) {
@@ -245,15 +248,17 @@
HttpClientConnection connection = connect(repository.getServer(), repositoryNode.getUrl(), RequestMethod.GET);
try {
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ int responseCode = connection.getResponseCode();
+
+ if (responseCode == HttpURLConnection.HTTP_OK) {
return repositoryNode.getWorkspaces(connection.read());
}
// not a good response code
- this.logger.error(RestClientI18n.connectionErrorMsg, connection.getResponseCode(), "getWorkspaces"); //$NON-NLS-1$
+ this.logger.error(RestClientI18n.connectionErrorMsg, responseCode, "getWorkspaces"); //$NON-NLS-1$
String msg = RestClientI18n.getWorkspacesFailedMsg.text(repository.getName(),
repository.getServer().getName(),
- connection.getResponseCode());
+ responseCode);
throw new RuntimeException(msg);
} finally {
if (connection != null) {
Modified: trunk/web/dna-web-jcr-rest-client/src/test/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClientTest.java
===================================================================
--- trunk/web/dna-web-jcr-rest-client/src/test/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClientTest.java 2009-10-03 04:09:29 UTC (rev 1286)
+++ trunk/web/dna-web-jcr-rest-client/src/test/java/org/jboss/dna/web/jcr/rest/client/json/JsonRestClientTest.java 2009-10-05 19:00:27 UTC (rev 1287)
@@ -66,7 +66,7 @@
private static final String BINARY_FILE_PATH = WORKSPACE_PATH + "picture.jpg"; //$NON-NLS-1$
private static final String WORKSPACE_UNUSUALPATH = "/myproject/My.Test - Folder/"; //$NON-NLS-1$
- private static final String FILE_UNUSUALPATH = WORKSPACE_UNUSUALPATH + "Test File_.a-*().txt"; //$NON-NLS-1$
+ private static final String FILE_UNUSUALPATH = WORKSPACE_UNUSUALPATH + "Test File_.a-().txt"; //$NON-NLS-1$
// ===========================================================================================================================
// Fields
Copied: trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-().txt (from rev 1284, trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-*().txt)
===================================================================
--- trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-().txt (rev 0)
+++ trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-().txt 2009-10-05 19:00:27 UTC (rev 1287)
@@ -0,0 +1,174 @@
+Help us improve Wikipedia by supporting it financially.
+Registration for Wikimania 2009 is now open. Learn more.
+
+[Hide] [Help us with translations!]
+Percent-encoding
+From Wikipedia, the free encyclopedia
+Jump to: navigation, search
+ This article includes a list of references, related reading or external links, but its sources remain unclear because it lacks inline citations. Please improve this article by introducing more precise citations where appropriate. (April 2008)
+
+ For the urlencode in mediawiki, see Help:Magic words
+
+Percent-encoding, also known as URL encoding, is a mechanism for encoding information in a Uniform Resource Identifier (URI) under certain circumstances. Although it is known as URL encoding it is, in fact, used more generally within the main Uniform Resource Identifier (URI) set, which includes both Uniform Resource Locator (URL) and Uniform Resource Name (URN). As such it is also used in the preparation of data of the "application/x-www-form-urlencoded" media type, as is often used in email messages and the submission of HTML form data in HTTP requests.
+Contents
+[hide]
+
+ * 1 Percent-encoding in a URI
+ o 1.1 Types of URI characters
+ o 1.2 Percent-encoding reserved characters
+ o 1.3 Percent-encoding unreserved characters
+ o 1.4 Percent-encoding arbitrary data
+ + 1.4.1 Binary data
+ + 1.4.2 Character data
+ o 1.5 Current standard
+ o 1.6 Non-standard implementations
+ * 2 The application/x-www-form-urlencoded type
+ * 3 See also
+ * 4 References
+ * 5 External links
+
+[edit] Percent-encoding in a URI
+
+[edit] Types of URI characters
+
+The characters allowed in a URI are either reserved or unreserved. Reserved characters are those characters that sometimes have special meaning, while unreserved characters have no such meaning. Using percent-encoding, characters which otherwise would not be allowed are represented using allowed characters. The sets of reserved and unreserved characters and the circumstances under which certain reserved characters have special meaning have changed slightly with each revision of specifications that govern URIs and URI schemes.
+RFC 3986 section 2.2 Reserved Characters (January 2005) ! * ' ( ) ; : @ & = + $ , / ? % # [ ]
+RFC 3986 section 2.3 Unreserved Characters (January 2005) A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+a b c d e f g h i j k l m n o p q r s t u v w x y z
+0 1 2 3 4 5 6 7 8 9 - _ . ~
+
+No other characters are allowed in a URI.
+
+[edit] Percent-encoding reserved characters
+
+When a character from the reserved set (a "reserved character") has special meaning (a "reserved purpose") in a certain context, and a URI scheme says that it is necessary to use that character for some other purpose, then the character must be percent-encoded. Percent-encoding a reserved character involves converting the character to its corresponding byte value in ASCII and then representing that value as a pair of hexadecimal digits. The digits, preceded by a percent sign ("%"), are then used in the URI in place of the reserved character. (For a non-ASCII character, it is typically converted to its byte sequence in UTF-8, and then each byte value is represented as above.)
+
+The reserved character "/", for example, if used in the "path" component of a URI, has the special meaning of being a delimiter between path segments. If, according to a given URI scheme, "/" needs to be in a path segment, then the three characters "%2F" or "%2f" must be used in the segment instead of a raw "/".
+Reserved characters after percent-encoding ! * " ' ( ) ; : @ & = + $ , / ? % # [ ]
+%21 %2A %22 %27 %28 %29 %3B %3A %40 %26 %3D %2B %24 %2C %2F %3F %25 %23 %5B %5D
+
+Reserved characters that have no reserved purpose in a particular context may also be percent-encoded but are not semantically different from those that are not.
+
+In the "query" component of a URI, for example, "/" is still considered a reserved character but it normally has no reserved purpose, unless a particular URI scheme says otherwise. The character does not need to be percent-encoded when it has no reserved purpose.
+
+URIs that differ only by whether a reserved character is percent-encoded or appears literally are normally considered not equivalent (denoting the same resource) unless it can be determined that the reserved characters in question have no reserved purpose. This determination is dependent upon the rules established for reserved characters by individual URI schemes.
+
+[edit] Percent-encoding unreserved characters
+
+Characters from the unreserved set can be percent-encoded in the same way as reserved characters. That is, if a scheme calls for an unreserved character to be used in a URI, either the raw character or its percent-encoded equivalent may be used interchangeably.
+
+URIs that differ only by whether an unreserved character is percent-encoded or appears literally are equivalent by definition, but URI processors, in practice, may not always recognize this equivalence. For example, URI consumers shouldn't treat "%41" differently from "A" or "%7E" differently from "~", but some do. For maximum interoperability, URI producers are discouraged from percent-encoding unreserved characters.
+
+[edit] Percent-encoding arbitrary data
+
+Most URI schemes involve the representation of arbitrary data, such as an IP address or file system path, as components of a URI. URI scheme specifications should, but often don't, provide an explicit mapping between URI characters and all possible data values being represented by those characters.
+
+[edit] Binary data
+
+Since the publication of RFC 1738 in 1994 it has been specified that schemes that provide for the representation of binary data in a URI must divide the data into 8-bit bytes and percent-encode each byte in the same manner as above. Byte value 0F (hexadecimal), for example, should be represented by "%0F", but byte value 41 (hexadecimal) can be represented by "A", or "%41". The use of unencoded characters for alphanumeric and unreserved characters is typically preferred as it results in shorter URLs.
+
+[edit] Character data
+
+The procedure for percent-encoding binary data has often been extrapolated, sometimes inappropriately or without being fully specified, to apply to character-based data. In the World Wide Web's formative years, when dealing with data characters in the ASCII repertoire and using their corresponding bytes in ASCII as the basis for determining percent-encoded sequences, this practice was relatively harmless; it was just assumed that characters and bytes mapped one-to-one and were interchangeable. The need to represent characters outside the ASCII range, however, grew quickly and URI schemes and protocols often failed to provide standard rules for preparing character data for inclusion in a URI. Web applications consequently began using different multi-byte, stateful, and other non-ASCII-compatible encodings as the basis for percent-encoding, leading to ambiguities and difficulty interpreting URIs reliably.
+
+For example, many URI schemes and protocols based on RFCs 1738 and 2396 presume that the data characters will be converted to bytes according to some unspecified character encoding before being represented in a URI by unreserved characters or percent-encoded bytes. If the scheme does not allow the URI to provide a hint as to what encoding was used, or if the encoding conflicts with the use of ASCII to percent-encode reserved and unreserved characters, then the URI cannot be reliably interpreted. Some schemes fail to account for encoding at all, and instead just suggest that data characters map directly to URI characters, which leaves it up to implementations to decide whether and how to percent-encode data characters that are in neither the reserved nor unreserved sets.
+
+[edit] Current standard
+
+The generic URI syntax mandates that new URI schemes that provide for the representation of character data in a URI must, in effect, represent characters from the unreserved set without translation, and should convert all other characters to bytes according to UTF-8, and then percent-encode those values. This requirement was introduced in January 2005 with the publication of RFC 3986. URI schemes introduced before this date are not affected.
+
+Not addressed by the current specification is what to do with encoded character data. For example, in computers, character data manifests in encoded form, at some level, and thus could be treated as either binary data or as character data when being mapped to URI characters. Presumably, it is up to the URI scheme specifications to account for this possibility and require one or the other, but in practice, few, if any, actually do.
+
+[edit] Non-standard implementations
+
+There exists a non-standard encoding for Unicode characters: %uxxxx, where xxxx is a Unicode value represented as four hexadecimal digits. This behavior is not specified by any RFC and has been rejected by the W3C. The third edition of ECMA-262 still includes an escape(string) function that uses this syntax, but also an encodeURI(uri) function that converts to UTF-8 and percent-encodes each octet.
+
+[edit] The application/x-www-form-urlencoded type
+
+When data that has been entered into HTML forms is submitted, the form field names and values are encoded and sent to the server in an HTTP request message using method GET or POST, or, historically, via email.[1] The encoding used by default is based on a very early version of the general URI percent-encoding rules, with a number of modifications such as newline normalization and replacing spaces with "+" instead of "%20". The MIME type of data encoded this way is application/x-www-form-urlencoded, and it is currently defined (still in a very outdated manner) in the HTML and XForms specifications. In addition, the CGI specification contains rules for how web servers decode data of this type and make it available to applications.
+
+When sent in an HTTP GET request, application/x-www-form-urlencoded data is included in the query component of the request URI. When sent in an HTTP POST request or via email, the data is placed in the body of the message, and the name of the media type is included in the message's Content-Type header.
+
+[edit] See also
+
+ * Internationalized Resource Identifier
+ * Punycode
+
+[edit] References
+
+ 1. ^ User-agent support for email based HTML form submission, using a 'mailto' URL as the form action, was proposed in RFC 1867 section 5.6, during the HTML 3.2 era. Various web browsers implemented it by invoking a separate email program or using their own rudimentary SMTP capabilities. Although sometimes unreliable, it was briefly popular as a simple way to transmit form data without involving a web server or CGI scripts.
+
+[edit] External links
+
+The following specifications all discuss and define reserved characters, unreserved characters, and percent-encoding, in some form or other:
+
+ * RFC 3986 / STD 66, the current generic URI syntax specification.
+ * RFC 2396 (obsolete) and RFC 2732 together comprised the previous version of the generic URI syntax specification.
+ * RFC 1738 (mostly obsolete) and RFC 1808 (obsolete), which define URLs.
+ * RFC 1630 (obsolete), the first generic URI syntax specification.
+ * W3C Guidelines on Naming and Addressing: URIs, URLs, ...
+ * W3C explanation of UTF-8 in URIs
+ * W3C HTML form content types
+
+Retrieved from "http://en.wikipedia.org/wiki/Percent-encoding"
+Categories: URI scheme | Internet standards | Binary-to-text encoding formats
+Hidden categories: Articles lacking in-text citations from April 2008
+Views
+
+ * Article
+ * Discussion
+ * Edit this page
+ * History
+
+Personal tools
+
+ * Try Beta
+ * Log in / create account
+
+Navigation
+
+ * Main page
+ * Contents
+ * Featured content
+ * Current events
+ * Random article
+
+Search
+
+Interaction
+
+ * About Wikipedia
+ * Community portal
+ * Recent changes
+ * Contact Wikipedia
+ * Donate to Wikipedia
+ * Help
+
+Toolbox
+
+ * What links here
+ * Related changes
+ * Upload file
+ * Special pages
+ * Printable version
+ * Permanent link
+ * Cite this page
+
+Languages
+
+ * Česky
+ * Deutsch
+ * 한국어
+ * Magyar
+ * 日本語
+
+Powered by MediaWiki
+Wikimedia Foundation
+
+ * This page was last modified on 5 August 2009 at 16:59.
+ * Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. See Terms of Use for details.
+ Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.
+ * Privacy policy
+ * About Wikipedia
+ * Disclaimers
+
Property changes on: trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-().txt
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Deleted: trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-*().txt
===================================================================
--- trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-*().txt 2009-10-03 04:09:29 UTC (rev 1286)
+++ trunk/web/dna-web-jcr-rest-client/src/test/resources/myproject/My.Test - Folder/Test File_.a-*().txt 2009-10-05 19:00:27 UTC (rev 1287)
@@ -1,174 +0,0 @@
-Help us improve Wikipedia by supporting it financially.
-Registration for Wikimania 2009 is now open. Learn more.
-
-[Hide] [Help us with translations!]
-Percent-encoding
-From Wikipedia, the free encyclopedia
-Jump to: navigation, search
- This article includes a list of references, related reading or external links, but its sources remain unclear because it lacks inline citations. Please improve this article by introducing more precise citations where appropriate. (April 2008)
-
- For the urlencode in mediawiki, see Help:Magic words
-
-Percent-encoding, also known as URL encoding, is a mechanism for encoding information in a Uniform Resource Identifier (URI) under certain circumstances. Although it is known as URL encoding it is, in fact, used more generally within the main Uniform Resource Identifier (URI) set, which includes both Uniform Resource Locator (URL) and Uniform Resource Name (URN). As such it is also used in the preparation of data of the "application/x-www-form-urlencoded" media type, as is often used in email messages and the submission of HTML form data in HTTP requests.
-Contents
-[hide]
-
- * 1 Percent-encoding in a URI
- o 1.1 Types of URI characters
- o 1.2 Percent-encoding reserved characters
- o 1.3 Percent-encoding unreserved characters
- o 1.4 Percent-encoding arbitrary data
- + 1.4.1 Binary data
- + 1.4.2 Character data
- o 1.5 Current standard
- o 1.6 Non-standard implementations
- * 2 The application/x-www-form-urlencoded type
- * 3 See also
- * 4 References
- * 5 External links
-
-[edit] Percent-encoding in a URI
-
-[edit] Types of URI characters
-
-The characters allowed in a URI are either reserved or unreserved. Reserved characters are those characters that sometimes have special meaning, while unreserved characters have no such meaning. Using percent-encoding, characters which otherwise would not be allowed are represented using allowed characters. The sets of reserved and unreserved characters and the circumstances under which certain reserved characters have special meaning have changed slightly with each revision of specifications that govern URIs and URI schemes.
-RFC 3986 section 2.2 Reserved Characters (January 2005) ! * ' ( ) ; : @ & = + $ , / ? % # [ ]
-RFC 3986 section 2.3 Unreserved Characters (January 2005) A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
-a b c d e f g h i j k l m n o p q r s t u v w x y z
-0 1 2 3 4 5 6 7 8 9 - _ . ~
-
-No other characters are allowed in a URI.
-
-[edit] Percent-encoding reserved characters
-
-When a character from the reserved set (a "reserved character") has special meaning (a "reserved purpose") in a certain context, and a URI scheme says that it is necessary to use that character for some other purpose, then the character must be percent-encoded. Percent-encoding a reserved character involves converting the character to its corresponding byte value in ASCII and then representing that value as a pair of hexadecimal digits. The digits, preceded by a percent sign ("%"), are then used in the URI in place of the reserved character. (For a non-ASCII character, it is typically converted to its byte sequence in UTF-8, and then each byte value is represented as above.)
-
-The reserved character "/", for example, if used in the "path" component of a URI, has the special meaning of being a delimiter between path segments. If, according to a given URI scheme, "/" needs to be in a path segment, then the three characters "%2F" or "%2f" must be used in the segment instead of a raw "/".
-Reserved characters after percent-encoding ! * " ' ( ) ; : @ & = + $ , / ? % # [ ]
-%21 %2A %22 %27 %28 %29 %3B %3A %40 %26 %3D %2B %24 %2C %2F %3F %25 %23 %5B %5D
-
-Reserved characters that have no reserved purpose in a particular context may also be percent-encoded but are not semantically different from those that are not.
-
-In the "query" component of a URI, for example, "/" is still considered a reserved character but it normally has no reserved purpose, unless a particular URI scheme says otherwise. The character does not need to be percent-encoded when it has no reserved purpose.
-
-URIs that differ only by whether a reserved character is percent-encoded or appears literally are normally considered not equivalent (denoting the same resource) unless it can be determined that the reserved characters in question have no reserved purpose. This determination is dependent upon the rules established for reserved characters by individual URI schemes.
-
-[edit] Percent-encoding unreserved characters
-
-Characters from the unreserved set can be percent-encoded in the same way as reserved characters. That is, if a scheme calls for an unreserved character to be used in a URI, either the raw character or its percent-encoded equivalent may be used interchangeably.
-
-URIs that differ only by whether an unreserved character is percent-encoded or appears literally are equivalent by definition, but URI processors, in practice, may not always recognize this equivalence. For example, URI consumers shouldn't treat "%41" differently from "A" or "%7E" differently from "~", but some do. For maximum interoperability, URI producers are discouraged from percent-encoding unreserved characters.
-
-[edit] Percent-encoding arbitrary data
-
-Most URI schemes involve the representation of arbitrary data, such as an IP address or file system path, as components of a URI. URI scheme specifications should, but often don't, provide an explicit mapping between URI characters and all possible data values being represented by those characters.
-
-[edit] Binary data
-
-Since the publication of RFC 1738 in 1994 it has been specified that schemes that provide for the representation of binary data in a URI must divide the data into 8-bit bytes and percent-encode each byte in the same manner as above. Byte value 0F (hexadecimal), for example, should be represented by "%0F", but byte value 41 (hexadecimal) can be represented by "A", or "%41". The use of unencoded characters for alphanumeric and unreserved characters is typically preferred as it results in shorter URLs.
-
-[edit] Character data
-
-The procedure for percent-encoding binary data has often been extrapolated, sometimes inappropriately or without being fully specified, to apply to character-based data. In the World Wide Web's formative years, when dealing with data characters in the ASCII repertoire and using their corresponding bytes in ASCII as the basis for determining percent-encoded sequences, this practice was relatively harmless; it was just assumed that characters and bytes mapped one-to-one and were interchangeable. The need to represent characters outside the ASCII range, however, grew quickly and URI schemes and protocols often failed to provide standard rules for preparing character data for inclusion in a URI. Web applications consequently began using different multi-byte, stateful, and other non-ASCII-compatible encodings as the basis for percent-encoding, leading to ambiguities and difficulty interpreting URIs reliably.
-
-For example, many URI schemes and protocols based on RFCs 1738 and 2396 presume that the data characters will be converted to bytes according to some unspecified character encoding before being represented in a URI by unreserved characters or percent-encoded bytes. If the scheme does not allow the URI to provide a hint as to what encoding was used, or if the encoding conflicts with the use of ASCII to percent-encode reserved and unreserved characters, then the URI cannot be reliably interpreted. Some schemes fail to account for encoding at all, and instead just suggest that data characters map directly to URI characters, which leaves it up to implementations to decide whether and how to percent-encode data characters that are in neither the reserved nor unreserved sets.
-
-[edit] Current standard
-
-The generic URI syntax mandates that new URI schemes that provide for the representation of character data in a URI must, in effect, represent characters from the unreserved set without translation, and should convert all other characters to bytes according to UTF-8, and then percent-encode those values. This requirement was introduced in January 2005 with the publication of RFC 3986. URI schemes introduced before this date are not affected.
-
-Not addressed by the current specification is what to do with encoded character data. For example, in computers, character data manifests in encoded form, at some level, and thus could be treated as either binary data or as character data when being mapped to URI characters. Presumably, it is up to the URI scheme specifications to account for this possibility and require one or the other, but in practice, few, if any, actually do.
-
-[edit] Non-standard implementations
-
-There exists a non-standard encoding for Unicode characters: %uxxxx, where xxxx is a Unicode value represented as four hexadecimal digits. This behavior is not specified by any RFC and has been rejected by the W3C. The third edition of ECMA-262 still includes an escape(string) function that uses this syntax, but also an encodeURI(uri) function that converts to UTF-8 and percent-encodes each octet.
-
-[edit] The application/x-www-form-urlencoded type
-
-When data that has been entered into HTML forms is submitted, the form field names and values are encoded and sent to the server in an HTTP request message using method GET or POST, or, historically, via email.[1] The encoding used by default is based on a very early version of the general URI percent-encoding rules, with a number of modifications such as newline normalization and replacing spaces with "+" instead of "%20". The MIME type of data encoded this way is application/x-www-form-urlencoded, and it is currently defined (still in a very outdated manner) in the HTML and XForms specifications. In addition, the CGI specification contains rules for how web servers decode data of this type and make it available to applications.
-
-When sent in an HTTP GET request, application/x-www-form-urlencoded data is included in the query component of the request URI. When sent in an HTTP POST request or via email, the data is placed in the body of the message, and the name of the media type is included in the message's Content-Type header.
-
-[edit] See also
-
- * Internationalized Resource Identifier
- * Punycode
-
-[edit] References
-
- 1. ^ User-agent support for email based HTML form submission, using a 'mailto' URL as the form action, was proposed in RFC 1867 section 5.6, during the HTML 3.2 era. Various web browsers implemented it by invoking a separate email program or using their own rudimentary SMTP capabilities. Although sometimes unreliable, it was briefly popular as a simple way to transmit form data without involving a web server or CGI scripts.
-
-[edit] External links
-
-The following specifications all discuss and define reserved characters, unreserved characters, and percent-encoding, in some form or other:
-
- * RFC 3986 / STD 66, the current generic URI syntax specification.
- * RFC 2396 (obsolete) and RFC 2732 together comprised the previous version of the generic URI syntax specification.
- * RFC 1738 (mostly obsolete) and RFC 1808 (obsolete), which define URLs.
- * RFC 1630 (obsolete), the first generic URI syntax specification.
- * W3C Guidelines on Naming and Addressing: URIs, URLs, ...
- * W3C explanation of UTF-8 in URIs
- * W3C HTML form content types
-
-Retrieved from "http://en.wikipedia.org/wiki/Percent-encoding"
-Categories: URI scheme | Internet standards | Binary-to-text encoding formats
-Hidden categories: Articles lacking in-text citations from April 2008
-Views
-
- * Article
- * Discussion
- * Edit this page
- * History
-
-Personal tools
-
- * Try Beta
- * Log in / create account
-
-Navigation
-
- * Main page
- * Contents
- * Featured content
- * Current events
- * Random article
-
-Search
-
-Interaction
-
- * About Wikipedia
- * Community portal
- * Recent changes
- * Contact Wikipedia
- * Donate to Wikipedia
- * Help
-
-Toolbox
-
- * What links here
- * Related changes
- * Upload file
- * Special pages
- * Printable version
- * Permanent link
- * Cite this page
-
-Languages
-
- * Česky
- * Deutsch
- * 한국어
- * Magyar
- * 日本語
-
-Powered by MediaWiki
-Wikimedia Foundation
-
- * This page was last modified on 5 August 2009 at 16:59.
- * Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. See Terms of Use for details.
- Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.
- * Privacy policy
- * About Wikipedia
- * Disclaimers
-
14 years, 7 months