Author: rhauch
Date: 2009-12-03 15:47:19 -0500 (Thu, 03 Dec 2009)
New Revision: 1388
Added:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/FullTextSearchParser.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeTypeSchemata.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/ValueTypeSystem.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/LocalNamespaceRegistry.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/TypeSystem.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.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/JcrWorkspace.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/CndNodeTypeRegistrationTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNamespaceRegistryTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
Log:
DNA-468 Add XPath query language support
Completed the integration and implementation of the JCR interfaces, meaning the
implementation of the query functionality in the JCR layers should be complete.
HOWEVER, THE UNDERLYING CONNECTORS DO NOT YET SUPPORT QUERIES OR SEARCHES, SO THE JCR
QUERY FUNCTIONALITY DOES NOT YET WORK.
Updated the JcrQueryManager implementation of javax.jcr.query.QueryManager to parse the
queries using one of three languages: SQL, XPath, and free-text search. The SQL language
is really that spelled out in JCR 2.0, with enhancements to support UNION, INTERSECT,
EXCEPT, IN clauses, additional join types, BETWEEN criteria, and the PATH(...) and
DEPTH(...) functions for use on the left-hand side of criteria (e.g., dynamic operands).
The XPath language is that specified by JCR 1.0. And the free-text search language just
allows the client to submit a search expression that complies with the Section 6.7.19 of
the JCR 2.0 specification (the same search grammar used in the JCR-JQOM
'FullTextSearch' criteria and the 'CONTAINS(...)' function in JCR-SQL2).
If the submitted query is valid and well-formed, a javax.jcr.query.Query object is created
(using either JcrQuery or JcrSearch, depending upon the language). When executed, the
query/search is pushed down to the graph layer via the new Graph API methods. The Graph
then does its thing (like planning, validating, optimizing, and processing) by pushing
down to the connector (as a single batch) the one or more AccessQueryRequest objects. The
connector then does its thing by computing the results for each the access query and
setting the results on the request. The graph's query engine uses these results and
performs any additional operations (like joins, unions, ordering, or application of
additional criteria that couldn't be pushed down) to produce the final results (in the
form of the org.jboss.dna.graph.query.QueryResults). Back in the JCR layer, the graph
results are wrapped by a javax.jcr.query.QueryResult implementation (JcrQueryResult),
where the client can access th!
e javax.jcr.Node object for each row or the Value objects in each column of each Row.
There are a couple of things to note about the QueryResult implementation. First, because
of the signature of the NodeIterator (specifically that the methods don't throw
RepositoryException), accessing the nodes in the results is done when the NodeIterator is
obtained. In other words, the Nodes are fetched and loaded into the Session immediately
when the 'QueryResult.getNodes()' method is called. However, the RowIterator
method signatures do throw RepositoryException, so the values are NOT loaded when
'QueryResult.getRows()' is called but are instead loaded lazily as the iterator is
used.
Second, the QueryResult always returns the values cached in the Session, meaning that
while transient changes within the Session are not used to evaluate criteria and determine
the rows, the actual values of the rows DO come from the Session's transient state.
This behavior is spelled out in the specification.
Submitting the queries to the Graph API require supplying an implementation of the
org.jboss.dna.graph.validate.Schemata interface. This is used by the query engine to a)
identify valid tables and columns by their names so that the submitted query can be
resolved; b) to identify all selectable columns in a table when SELECT * is used; c)
determine the appropriate datatype for each column appearing in the SELECT or WHERE
clauses; and d) to obtain the definition of views so that use of views in a query plan can
be replaced with the view's definition.
This commit includes an implementation of Schemata that is based upon the node types in
the repository. Each node should appear in all tables represented by its primary type,
all supertypes of the primary type, all mixin types, and all supertypes of all mixin
types.
Each non-mixin node type is represented as a VIEW defined with a query of the form:
SELECT <propertyList> FROM __ALLNODES__
WHERE [jcr:primaryType] IN (<nodeTypeName>,<subtypeNameList>)
Similarly, each mixin node type is represented as a VIEW defined with a query of the
form:
SELECT <propertyList> FROM __ALLNODES__
WHERE [jcr:mixinTypes] IN (<nodeTypeName>,<subtypeNameList>)
In these queries, the '<propertyList>' is the comma-separated names of the
single-valued, non-residual properties explicitly defined on the node type (and optionally
all its supertypes). Also, '<subtypeNameList>' is the comma-separated names
of all node types that have the node type in question as a supertype.
It was pretty easy to create a Schemata implementation of the node types in the
RepositoryNodeTypeManager (the master definition of node types for all workspaces in the
repository). As NodeTypeSchemata is immutable, it can always be used to provide an
immutable, consistent schemata for a query. Additionally, it can continue to be reused
until a node type changes in the RNTM, and the RNTM ensures this is so. A
NodeTypeSchemata is only created when needed by the QueryManager, and will be discarded
any time node types are changed in the repository.
One twist, however, is that Schemata uses stringified names, not DNA Name objects. And
that means the Schemata is dependent upon the namespace mappings, and the NodeTypeSchemata
is dependent upon the JcrRepository's context. Each Session might have redefined some
namespace mappings, and the queries are to use the session's namespace mappings. So,
NodeTypeSchemata provides a method to obtain a Schemata instance given a JcrSession
instance, and this is what is used by QueryManager. Obtaining a session-specific schemata
could be expensive, so this method does a couple of cool tricks to minimize the time
required to build the session-specific schemata. First, if the JcrSession doesn't
actually redefine _any_ namespace mappings, or it doesn't redefine any of the
namespace mappings for namespaces used in the node types, or if the namespace mappings for
those namespaces used in the node types are unchanged, the NodeTypeSchemata can be used as
is. In all other cases, a session!
-specific Schemata must be created (and must be thown out if any namespace mappings are
changed in the session).
But, unlike the NodeTypeSchemata (which creates views for all node types preemptively),
the session-specific Schemata implementations are lazy. The theory is that a single query
may not involve that many tables, so it's not worth defining all views.
It should also be noted that the Lucene integration stores properties and paths in a
manner that is dependent upon the namespace URI, and not the prefix. The search engine
uses the ExecutionContext in which each query is being performed to transform the Schemata
names back into the prefix-independent form, prior to working with the indexed content.
This whole system appears to work very well so far. Again, work still needs to be done at
the connector-level to support FullTextSearchRequest and AccessQueryRequest types.
That's next.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/ValueTypeSystem.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/ValueTypeSystem.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/ValueTypeSystem.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -44,6 +44,7 @@
protected final ValueFactory<String> stringValueFactory;
private final Map<PropertyType, TypeFactory<?>>
typeFactoriesByPropertyType;
private final Map<String, TypeFactory<?>> typeFactoriesByName;
+ private final Map<String, PropertyType> propertyTypeByName;
private final TypeFactory<String> stringFactory;
private final TypeFactory<Boolean> booleanFactory;
private final TypeFactory<Long> longFactory;
@@ -138,6 +139,11 @@
}
});
this.typeFactoriesByPropertyType = Collections.unmodifiableMap(factories);
+ Map<String, PropertyType> propertyTypeByName = new HashMap<String,
PropertyType>();
+ for (Map.Entry<PropertyType, TypeFactory<?>> entry :
this.typeFactoriesByPropertyType.entrySet()) {
+ propertyTypeByName.put(entry.getValue().getTypeName(), entry.getKey());
+ }
+ this.propertyTypeByName = Collections.unmodifiableMap(propertyTypeByName);
Map<String, TypeFactory<?>> byName = new HashMap<String,
TypeFactory<?>>();
for (TypeFactory<?> factory : factories.values()) {
byName.put(factory.getTypeName(), factory);
@@ -259,6 +265,43 @@
return typeFactoriesByName.keySet();
}
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.TypeSystem#getCompatibleType(java.lang.String,
java.lang.String)
+ */
+ public String getCompatibleType( String type1,
+ String type2 ) {
+ if (type1 == null) {
+ return type2 != null ? type2 : getDefaultType();
+ }
+ if (type2 == null) return type1;
+ if (type1.equals(type2)) return type1;
+
+ // neither is null ...
+ PropertyType ptype1 = propertyTypeByName.get(type1);
+ PropertyType ptype2 = propertyTypeByName.get(type2);
+ assert ptype1 != null;
+ assert ptype2 != null;
+ if (ptype1 == PropertyType.STRING) return type1;
+ if (ptype2 == PropertyType.STRING) return type2;
+ // Dates are compatible with longs ...
+ if (ptype1 == PropertyType.LONG && ptype2 == PropertyType.DATE) return
type1;
+ if (ptype1 == PropertyType.DATE && ptype2 == PropertyType.LONG) return
type2;
+ // Booleans and longs are compatible ...
+ if (ptype1 == PropertyType.LONG && ptype2 == PropertyType.BOOLEAN) return
type1;
+ if (ptype1 == PropertyType.BOOLEAN && ptype2 == PropertyType.LONG) return
type2;
+ // Doubles and longs ...
+ if (ptype1 == PropertyType.DOUBLE && ptype2 == PropertyType.LONG) return
type1;
+ if (ptype1 == PropertyType.LONG && ptype2 == PropertyType.DOUBLE) return
type2;
+ // Paths and names ...
+ if (ptype1 == PropertyType.PATH && ptype2 == PropertyType.NAME) return
type1;
+ if (ptype1 == PropertyType.NAME && ptype2 == PropertyType.PATH) return
type2;
+
+ // Otherwise, it's just the default type (string) ...
+ return getDefaultType();
+ }
+
protected class Factory<T> implements TypeFactory<T> {
protected final PropertyType type;
protected final ValueFactory<T> valueFactory;
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/LocalNamespaceRegistry.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/LocalNamespaceRegistry.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/LocalNamespaceRegistry.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -96,6 +96,7 @@
public Set<Namespace> getNamespaces() {
Set<Namespace> delegateNamespaces = this.delegate.getNamespaces();
Set<Namespace> localNamespaces = super.getNamespaces();
+ if (localNamespaces.isEmpty()) return delegateNamespaces;
// Load the local namespaces first ...
Set<Namespace> namespaces = new HashSet<Namespace>(localNamespaces);
@@ -115,6 +116,15 @@
}
/**
+ * Obtain the set of namespaces that are overridden within this {@link
LocalNamespaceRegistry} instance.
+ *
+ * @return the set of overridden namespace mappings; never null but possibly empty
+ */
+ public Set<Namespace> getLocalNamespaces() {
+ return super.getNamespaces();
+ }
+
+ /**
* {@inheritDoc}
*
* @see
org.jboss.dna.graph.property.basic.SimpleNamespaceRegistry#getPrefixForNamespaceUri(java.lang.String,
boolean)
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/TypeSystem.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/TypeSystem.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/TypeSystem.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -122,6 +122,16 @@
String asString( Object value );
/**
+ * Get the type that is compatible with both of the supplied types.
+ *
+ * @param type1 the first type; may be null
+ * @param type2 the second type; may be null
+ * @return the compatible type; never null
+ */
+ String getCompatibleType( String type1,
+ String type2 );
+
+ /**
* Factory interface for creating values from strings.
*
* @param <T> the type of value object
Modified: 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 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParsers.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -74,7 +74,7 @@
*/
public void addLanguage( QueryParser languageParser ) {
CheckArg.isNotNull(languageParser, "languageParser");
- this.parsers.put(languageParser.getLanguage().toLowerCase(), languageParser);
+ this.parsers.put(languageParser.getLanguage().trim().toLowerCase(),
languageParser);
}
/**
@@ -153,11 +153,24 @@
}
/**
+ * Get the parser for the supplied language.
+ *
+ * @param language the language in which the query is expressed; must
case-insensitively match one of the supported
+ * {@link #getLanguages() languages}
+ * @return the query parser, or null if the supplied language is not supported
+ * @throws IllegalArgumentException if the language is null
+ */
+ public QueryParser getParserFor( String language ) {
+ CheckArg.isNotNull(language, "language");
+ return parsers.get(language.trim().toLowerCase());
+ }
+
+ /**
* Execute the supplied query by planning, optimizing, and then processing it.
*
* @param typeSystem the type system that should be used
- * @param language the language in which the query is expressed; must be one of the
supported {@link #getLanguages()
- * languages}
+ * @param language the language in which the query is expressed; must
case-insensitively match 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
@@ -170,7 +183,7 @@
CheckArg.isNotNull(language, "language");
CheckArg.isNotNull(typeSystem, "typeSystem");
CheckArg.isNotNull(query, "query");
- QueryParser parser = parsers.get(language.toLowerCase());
+ QueryParser parser = parsers.get(language.trim().toLowerCase());
if (parser == null) {
throw new
IllegalArgumentException(GraphI18n.unknownQueryLanguage.text(language));
}
Copied: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/FullTextSearchParser.java (from rev
1387, trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java)
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/FullTextSearchParser.java
(rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/FullTextSearchParser.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -0,0 +1,61 @@
+/*
+ * 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.jcr;
+
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.TypeSystem;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.graph.query.parse.QueryParser;
+
+/**
+ * A {@link QueryParser} implementation that is stored in the {@link JcrRepository}'s
list of {@link JcrRepository#queryParsers()
+ * query parsers} so that the name is there, but it should never be used.
+ *
+ * @see JcrRepository#queryParsers()
+ * @see JcrQueryManager#createQuery(String, String)
+ */
+class FullTextSearchParser implements QueryParser {
+
+ public static final String LANGUAGE = "Search";
+
+ /**
+ * {@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.query.model.TypeSystem)
+ */
+ public QueryCommand parseQuery( String query,
+ TypeSystem typeSystem ) throws InvalidQueryException
{
+ assert false; // This method should never be called;
+ throw new UnsupportedOperationException();
+ }
+}
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-12-03 20:45:23 UTC
(rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-12-03 20:47:19 UTC
(rev 1388)
@@ -130,6 +130,8 @@
// Query-related messages
public static I18n notStoredQuery;
public static I18n invalidQueryLanguage;
+ public static I18n queryCannotBeParsedUsingLanguage;
+ public static I18n queryInLanguageIsNotValid;
// Type registration messages
public static I18n invalidNodeTypeName;
@@ -166,7 +168,7 @@
public static I18n cannotAddMixin;
public static I18n invalidMixinTypeForNode;
public static I18n notOrderable;
-
+
// Lock messages
public static I18n cannotRemoveLockToken;
public static I18n alreadyLocked;
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -55,7 +55,7 @@
super();
this.context = context;
this.declaringNodeType = declaringNodeType;
- this.name = name != null ? name :
context.getValueFactories().getNameFactory().create(JcrNodeType.RESIDUAL_ITEM_NAME) ;
+ this.name = name != null ? name :
context.getValueFactories().getNameFactory().create(JcrNodeType.RESIDUAL_ITEM_NAME);
this.onParentVersion = onParentVersion;
this.autoCreated = autoCreated;
this.mandatory = mandatory;
@@ -67,6 +67,16 @@
}
/**
+ * Determine whether this is a residual item. Section 6.7.15 in the JSR 1.0
specification defines a residual item as one
+ * having a name equal to "*".
+ *
+ * @return true if this item is residual, or false otherwise
+ */
+ public boolean isResidual() {
+ return name.getLocalName().equals("*");
+ }
+
+ /**
* {@inheritDoc}
*
* @see javax.jcr.nodetype.ItemDefinition#getDeclaringNodeType()
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -210,6 +210,7 @@
CheckArg.isNotNull(prefix, "prefix");
CheckArg.isNotNull(uri, "uri");
+ boolean global = false;
switch (behavior) {
case JSR170_SESSION:
// ----------------------------------------------------------
@@ -275,6 +276,7 @@
// --------------------------------------------------
// JSR-170 & JSR-283 Workspace namespace registry ...
// --------------------------------------------------
+ global = true;
try {
session.checkPermission((Path)null,
JcrSession.DNA_REGISTER_NAMESPACE_PERMISSION);
@@ -313,6 +315,9 @@
throw new
NamespaceException(JcrI18n.unableToRegisterNamespaceWithInvalidPrefix.text(prefix, uri));
}
+ // Signal the local node type manager ...
+ session.signalNamespaceChanges(global);
+
// Now we're sure the prefix and URI are valid and okay for a custom mapping
...
try {
registry.register(prefix, uri);
@@ -353,6 +358,9 @@
throw new
NamespaceException(JcrI18n.unableToUnregisterReservedNamespaceUri.text(prefix, uri));
}
+ // Signal the local node type manager ...
+ session.workspace().nodeTypeManager().signalNamespaceChanges();
+
// Now we're sure the prefix is valid and is actually used in a mapping ...
try {
registry.unregister(uri);
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java 2009-12-03 20:45:23 UTC
(rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java 2009-12-03 20:47:19 UTC
(rev 1388)
@@ -493,11 +493,25 @@
/**
* {@inheritDoc}
*
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return this.name.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
+ if (obj instanceof JcrNodeType) {
+ JcrNodeType that = (JcrNodeType)obj;
+ return this.name.equals(that.name);
+ }
if (obj instanceof NodeType) {
NodeType that = (NodeType)obj;
return this.getName().equals(that.getName());
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -44,6 +44,7 @@
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.validate.Schemata;
import org.jboss.dna.jcr.nodetype.InvalidNodeTypeDefinitionException;
import org.jboss.dna.jcr.nodetype.NodeDefinitionTemplate;
import org.jboss.dna.jcr.nodetype.NodeTypeDefinition;
@@ -64,6 +65,7 @@
private final JcrSession session;
private final RepositoryNodeTypeManager repositoryTypeManager;
+ private Schemata schemata;
JcrNodeTypeManager( JcrSession session,
RepositoryNodeTypeManager repositoryTypeManager ) {
@@ -75,6 +77,18 @@
return session.getExecutionContext();
}
+ Schemata schemata() {
+ if (schemata == null) {
+ schemata =
repositoryTypeManager.getRepositorySchemata().getSchemataForSession(session);
+ assert schemata != null;
+ }
+ return schemata;
+ }
+
+ void signalNamespaceChanges() {
+ this.schemata = null;
+ }
+
/**
* {@inheritDoc}
*
@@ -405,7 +419,11 @@
} catch (AccessControlException ace) {
throw new AccessDeniedException(ace);
}
- return this.repositoryTypeManager.registerNodeType(template, allowUpdate);
+ try {
+ return this.repositoryTypeManager.registerNodeType(template, allowUpdate);
+ } finally {
+ schemata = null;
+ }
}
/**
@@ -437,8 +455,11 @@
} catch (AccessControlException ace) {
throw new AccessDeniedException(ace);
}
-
- return new
JcrNodeTypeIterator(this.repositoryTypeManager.registerNodeTypes(templates,
allowUpdates));
+ try {
+ return new
JcrNodeTypeIterator(repositoryTypeManager.registerNodeTypes(templates, allowUpdates));
+ } finally {
+ schemata = null;
+ }
}
/**
@@ -469,7 +490,11 @@
throw new AccessDeniedException(ace);
}
- return new
JcrNodeTypeIterator(this.repositoryTypeManager.registerNodeTypes(source));
+ try {
+ return new
JcrNodeTypeIterator(this.repositoryTypeManager.registerNodeTypes(source));
+ } finally {
+ schemata = null;
+ }
}
/**
@@ -502,6 +527,7 @@
names.add(nameFactory.create(name));
}
repositoryTypeManager.unregisterNodeType(names);
+ schemata = null;
}
/**
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -55,6 +55,7 @@
private final int requiredType;
private final String[] valueConstraints;
private final boolean multiple;
+ private final boolean fullTextSearchable;
private PropertyDefinitionId id;
private ConstraintChecker checker = null;
@@ -68,12 +69,14 @@
Value[] defaultValues,
int requiredType,
String[] valueConstraints,
- boolean multiple ) {
+ boolean multiple,
+ boolean fullTextSearchable ) {
super(context, declaringNodeType, name, onParentVersion, autoCreated, mandatory,
protectedItem);
this.defaultValues = defaultValues;
this.requiredType = requiredType;
this.valueConstraints = valueConstraints;
this.multiple = multiple;
+ this.fullTextSearchable = fullTextSearchable;
}
/**
@@ -125,6 +128,10 @@
return multiple;
}
+ public boolean isFullTextSearchable() {
+ return fullTextSearchable;
+ }
+
/**
* Creates a new <code>JcrPropertyDefinition</code> that is identical to
the current object, but with the given
* <code>declaringNodeType</code>. Provided to support immutable pattern
for this class.
@@ -136,7 +143,8 @@
JcrPropertyDefinition with( JcrNodeType declaringNodeType ) {
return new JcrPropertyDefinition(this.context, declaringNodeType, this.name,
this.getOnParentVersion(),
this.isAutoCreated(), this.isMandatory(),
this.isProtected(), this.getDefaultValues(),
- this.getRequiredType(),
this.getValueConstraints(), this.isMultiple());
+ this.getRequiredType(),
this.getValueConstraints(), this.isMultiple(),
+ this.isFullTextSearchable());
}
/**
@@ -150,7 +158,8 @@
JcrPropertyDefinition with( ExecutionContext context ) {
return new JcrPropertyDefinition(context, this.declaringNodeType, this.name,
this.getOnParentVersion(),
this.isAutoCreated(), this.isMandatory(),
this.isProtected(), this.getDefaultValues(),
- this.getRequiredType(),
this.getValueConstraints(), this.isMultiple());
+ this.getRequiredType(),
this.getValueConstraints(), this.isMultiple(),
+ this.isFullTextSearchable());
}
/**
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.java 2009-12-03 20:45:23
UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.java 2009-12-03 20:47:19
UTC (rev 1388)
@@ -23,20 +23,36 @@
*/
package org.jboss.dna.jcr;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
+import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
+import javax.jcr.Value;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
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.Location;
import org.jboss.dna.graph.property.NamespaceRegistry;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.TypeSystem;
+import org.jboss.dna.graph.query.parse.QueryParser;
+import org.jboss.dna.graph.query.validate.Schemata;
/**
* Place-holder implementation of {@link QueryManager} interface.
@@ -57,30 +73,49 @@
*/
public Query createQuery( String statement,
String language ) throws InvalidQueryException {
- return this.createQuery(statement, language, null);
+ CheckArg.isNotNull(statement, "statement");
+ CheckArg.isNotNull(language, "language");
+ return createQuery(statement, language, null);
}
/**
- * Creates a new query by specifying the query statement itself, the language in
which the query is stated, and, optionally,
- * the node from which the query was loaded. If the query statement is syntactically
invalid, given the language specified, an
- * {@code InvalidQueryException} is thrown. The language must be a string from among
those returned by {@code
- * QueryManager#getSupportedQueryLanguages()}; if it is not, then an {@code
InvalidQueryException} is thrown.
+ * Creates a new JCR {@link Query} by specifying the query expression itself, the
language in which the query is stated, the
+ * {@link QueryCommand} representation and, optionally, the node from which the query
was loaded. The language must be a
+ * string from among those returned by {@code
QueryManager#getSupportedQueryLanguages()}.
*
- * @param statement
- * @param language
- * @param storedNode
- * @return A {@code Query} object
- * @throws InvalidQueryException if statement is invalid or language is unsupported.
- * @see javax.jcr.query.QueryManager#createQuery(java.lang.String, java.lang.String)
+ * @param expression the original query expression as supplied by the client; may not
be null
+ * @param language the language obtained from the {@link QueryParser}; may not be
null
+ * @param storedAtPath the path at which this query was stored, or null if this is
not a stored query
+ * @return query the JCR query object; never null
+ * @throws InvalidQueryException if expression is invalid or language is unsupported
*/
- private Query createQuery( String statement,
- String language,
- AbstractJcrNode storedNode ) throws InvalidQueryException
{
- if (Query.XPATH.equals(language)) {
- return new XPathQuery(this.session, statement, storedNode);
+ public Query createQuery( String expression,
+ String language,
+ Path storedAtPath ) throws InvalidQueryException {
+ // Look for a parser for the specified language ...
+ QueryParser parser = session.repository().queryParsers().getParserFor(language);
+ if (parser == null) {
+ Set<String> languages =
session.repository().queryParsers().getLanguages();
+ throw new InvalidQueryException(JcrI18n.invalidQueryLanguage.text(language,
languages));
}
- throw new InvalidQueryException(JcrI18n.invalidQueryLanguage.text(language,
Arrays.asList(getSupportedQueryLanguages())));
-
+ if (parser.getLanguage().equals(FullTextSearchParser.LANGUAGE)) {
+ // This is a full-text search ...
+ return new JcrSearch(this.session, expression, parser.getLanguage(),
storedAtPath);
+ }
+ TypeSystem typeSystem =
session.executionContext.getValueFactories().getTypeSystem();
+ try {
+ // Parsing must be done now ...
+ QueryCommand command = parser.parseQuery(expression, typeSystem);
+ return new JcrQuery(this.session, expression, parser.getLanguage(), command,
storedAtPath);
+ } catch (ParsingException e) {
+ // The query is not well-formed and cannot be parsed ...
+ String reason = e.getMessage();
+ throw new
InvalidQueryException(JcrI18n.queryCannotBeParsedUsingLanguage.text(language, expression,
reason));
+ } catch (org.jboss.dna.graph.query.parse.InvalidQueryException e) {
+ // The query was parsed, but there is an error in the query
+ String reason = e.getMessage();
+ throw new
InvalidQueryException(JcrI18n.queryInLanguageIsNotValid.text(language, expression,
reason));
+ }
}
/**
@@ -89,19 +124,20 @@
* @see javax.jcr.query.QueryManager#getQuery(javax.jcr.Node)
*/
public Query getQuery( Node node ) throws InvalidQueryException, RepositoryException
{
- assert node instanceof AbstractJcrNode;
+ AbstractJcrNode jcrNode = CheckArg.getInstanceOf(node, AbstractJcrNode.class,
"node");
- JcrNodeType nodeType = (JcrNodeType)node.getPrimaryNodeType();
+ // Check the type of the node ...
+ JcrNodeType nodeType = jcrNode.getPrimaryNodeType();
if (!nodeType.getInternalName().equals(JcrNtLexicon.QUERY)) {
- throw new InvalidQueryException(JcrI18n.notStoredQuery.text());
+ NamespaceRegistry registry =
session.getExecutionContext().getNamespaceRegistry();
+ throw new
InvalidQueryException(JcrI18n.notStoredQuery.text(jcrNode.path().getString(registry)));
}
// These are both mandatory properties for nodes of nt:query
- NamespaceRegistry registry =
session.getExecutionContext().getNamespaceRegistry();
- String statement =
node.getProperty(JcrLexicon.STATEMENT.getString(registry)).getString();
- String language =
node.getProperty(JcrLexicon.LANGUAGE.getString(registry)).getString();
+ String statement = jcrNode.getProperty(JcrLexicon.STATEMENT).getString();
+ String language = jcrNode.getProperty(JcrLexicon.LANGUAGE).getString();
- return createQuery(statement, language, (AbstractJcrNode)node);
+ return createQuery(statement, language, jcrNode.path());
}
/**
@@ -114,41 +150,43 @@
}
@NotThreadSafe
- protected abstract class AbstractJcrQuery implements Query {
- private final JcrSession session;
- private final String language;
- private final String statement;
- private Path storedPath;
+ protected static abstract class AbstractQuery implements Query {
+ protected final JcrSession session;
+ protected final String language;
+ protected final String statement;
+ private Path storedAtPath;
- protected AbstractJcrQuery( JcrSession session,
- String statement,
- String language,
- AbstractJcrNode storedNode ) {
+ /**
+ * Creates a new JCR {@link Query} by specifying the query statement itself, the
language in which the query is stated,
+ * the {@link QueryCommand} representation and, optionally, the node from which
the query was loaded. The language must be
+ * a string from among those returned by {@code
QueryManager#getSupportedQueryLanguages()}.
+ *
+ * @param session the session that was used to create this query and that will be
used to execute this query; may not be
+ * null
+ * @param statement the original statement as supplied by the client; may not be
null
+ * @param language the language obtained from the {@link QueryParser}; may not be
null
+ * @param storedAtPath the path at which this query was stored, or null if this
is not a stored query
+ */
+ protected AbstractQuery( JcrSession session,
+ String statement,
+ String language,
+ Path storedAtPath ) {
assert session != null;
assert statement != null;
assert language != null;
-
this.session = session;
this.language = language;
this.statement = statement;
+ this.storedAtPath = storedAtPath;
+ }
- try {
- this.storedPath = storedNode != null ? storedNode.path() : null;
- } catch (RepositoryException re) {
- throw new IllegalStateException(re);
- }
+ protected final JcrSession session() {
+ return this.session;
}
/**
* {@inheritDoc}
*
- * @see javax.jcr.query.Query#execute()
- */
- public abstract QueryResult execute();
-
- /**
- * {@inheritDoc}
- *
* @see javax.jcr.query.Query#getLanguage()
*/
public String getLanguage() {
@@ -170,10 +208,10 @@
* @see javax.jcr.query.Query#getStoredQueryPath()
*/
public String getStoredQueryPath() throws ItemNotFoundException {
- if (storedPath == null) {
+ if (storedAtPath == null) {
throw new ItemNotFoundException(JcrI18n.notStoredQuery.text());
}
- return
storedPath.getString(session.getExecutionContext().getNamespaceRegistry());
+ return
storedAtPath.getString(session.getExecutionContext().getNamespaceRegistry());
}
/**
@@ -181,51 +219,391 @@
*
* @see javax.jcr.query.Query#storeAsNode(java.lang.String)
*/
- public Node storeAsNode( java.lang.String absPath )
- throws PathNotFoundException, ConstraintViolationException,
RepositoryException {
+ public Node storeAsNode( String absPath ) throws PathNotFoundException,
ConstraintViolationException, RepositoryException {
NamespaceRegistry namespaces = this.session.namespaces();
-
+
Path path;
try {
path =
session.getExecutionContext().getValueFactories().getPathFactory().create(absPath);
- }
- catch (IllegalArgumentException iae) {
+ } catch (IllegalArgumentException iae) {
throw new
RepositoryException(JcrI18n.invalidPathParameter.text("absPath", absPath));
}
Path parentPath = path.getParent();
Node parentNode = session.getNode(parentPath);
Node queryNode =
parentNode.addNode(path.relativeTo(parentPath).getString(namespaces),
- JcrNtLexicon.QUERY.getString(namespaces));
-
+
JcrNtLexicon.QUERY.getString(namespaces));
+
queryNode.setProperty(JcrLexicon.LANGUAGE.getString(namespaces),
this.language);
queryNode.setProperty(JcrLexicon.STATEMENT.getString(namespaces),
this.statement);
-
- this.storedPath = path;
-
+
+ this.storedAtPath = path;
+
return queryNode;
}
+ }
+ /**
+ * Implementation of {@link Query} that represents a {@link QueryCommand} query.
+ */
+ @NotThreadSafe
+ protected static class JcrQuery extends AbstractQuery {
+ private final QueryCommand query;
+
+ /**
+ * Creates a new JCR {@link Query} by specifying the query statement itself, the
language in which the query is stated,
+ * the {@link QueryCommand} representation and, optionally, the node from which
the query was loaded. The language must be
+ * a string from among those returned by {@code
QueryManager#getSupportedQueryLanguages()}.
+ *
+ * @param session the session that was used to create this query and that will be
used to execute this query; may not be
+ * null
+ * @param statement the original statement as supplied by the client; may not be
null
+ * @param language the language obtained from the {@link QueryParser}; may not be
null
+ * @param query the parsed query representation; may not be null
+ * @param storedAtPath the path at which this query was stored, or null if this
is not a stored query
+ */
+ protected JcrQuery( JcrSession session,
+ String statement,
+ String language,
+ QueryCommand query,
+ Path storedAtPath ) {
+ super(session, statement, language, storedAtPath);
+ assert query != null;
+ this.query = query;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.Query#execute()
+ */
+ public QueryResult execute() {
+ // Submit immediately to the workspace graph ...
+ Schemata schemata = session.workspace().nodeTypeManager().schemata();
+ QueryResults result = session.workspace().graph().query(query, schemata)
+ // .using(variables)
+ // .using(hints)
+ .execute();
+ return new JcrQueryResult(session, result);
+ }
+
}
@NotThreadSafe
- protected class XPathQuery extends AbstractJcrQuery {
+ protected static class JcrSearch extends AbstractQuery {
- XPathQuery( JcrSession session,
- String statement,
- AbstractJcrNode storedNode ) {
- super(session, statement, Query.XPATH, storedNode);
+ /**
+ * Creates a new JCR {@link Query} by specifying the query statement itself, the
language in which the query is stated,
+ * the {@link QueryCommand} representation and, optionally, the node from which
the query was loaded. The language must be
+ * a string from among those returned by {@code
QueryManager#getSupportedQueryLanguages()}.
+ *
+ * @param session the session that was used to create this query and that will be
used to execute this query; may not be
+ * null
+ * @param statement the original statement as supplied by the client; may not be
null
+ * @param language the language obtained from the {@link QueryParser}; may not be
null
+ * @param storedAtPath the path at which this query was stored, or null if this
is not a stored query
+ */
+ protected JcrSearch( JcrSession session,
+ String statement,
+ String language,
+ Path storedAtPath ) {
+ super(session, statement, language, storedAtPath);
}
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.jcr.JcrQueryManager.AbstractJcrQuery#execute()
+ * @see javax.jcr.query.Query#execute()
*/
- @Override
public QueryResult execute() {
+ // Submit immediately to the workspace graph ...
+ QueryResults result = session.workspace().graph().search(statement);
+ return new JcrQueryResult(session, result);
+ }
+ }
+
+ /**
+ * The results of a query. This is not thread-safe because it relies upon JcrSession,
which is not thread-safe. Also, although
+ * the results of a query never change, the objects returned by the iterators may
vary if the session information changes.
+ */
+ @NotThreadSafe
+ protected static class JcrQueryResult implements QueryResult {
+ private final JcrSession session;
+ private final QueryResults results;
+
+ protected JcrQueryResult( JcrSession session,
+ QueryResults graphResults ) {
+ this.session = session;
+ this.results = graphResults;
+ assert this.session != null;
+ assert this.results != null;
+ }
+
+ protected QueryResults results() {
+ return results;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.QueryResult#getColumnNames()
+ */
+ public String[] getColumnNames() /*throws RepositoryException*/{
+ List<String> names = results.getColumns().getColumnNames();
+ return names.toArray(new String[names.size()]);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.QueryResult#getNodes()
+ */
+ public NodeIterator getNodes() throws RepositoryException {
+ // Find all of the nodes in the results. We have to do this pre-emptively,
since this
+ // is the only method to throw RepositoryException ...
+ final int numRows = results.getRowCount();
+ if (numRows == 0) return new JcrEmptyNodeIterator();
+
+ final List<AbstractJcrNode> nodes = new
ArrayList<AbstractJcrNode>(numRows);
+ final String selectorName = results.getColumns().getSelectorNames().get(0);
+ final int locationIndex =
results.getColumns().getLocationIndex(selectorName);
+ for (Object[] tuple : results.getTuples()) {
+ Location location = (Location)tuple[locationIndex];
+ nodes.add(session.getNode(location.getPath()));
+ }
+ return new QueryResultNodeIterator(nodes);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.QueryResult#getRows()
+ */
+ public RowIterator getRows() /*throws RepositoryException*/{
+ // We can actually delay the loading of the nodes until the rows are accessed
...
+ final int numRows = results.getRowCount();
+ final List<Object[]> tuples = results.getTuples();
+ return new QueryResultRowIterator(session, results.getColumns(),
tuples.iterator(), numRows);
+ }
+ }
+
+ /**
+ * The {@link NodeIterator} implementation returned by the {@link JcrQueryResult}.
+ *
+ * @see JcrQueryResult#getNodes()
+ */
+ @NotThreadSafe
+ protected static class QueryResultNodeIterator implements NodeIterator {
+ private final Iterator<? extends Node> nodes;
+ private final int size;
+ private long position = 0L;
+
+ protected QueryResultNodeIterator( List<? extends Node> nodes ) {
+ this.nodes = nodes.iterator();
+ this.size = nodes.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.NodeIterator#nextNode()
+ */
+ public Node nextNode() {
+ Node node = nodes.next();
+ ++position;
+ return node;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.RangeIterator#getPosition()
+ */
+ public long getPosition() {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.RangeIterator#getSize()
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.RangeIterator#skip(long)
+ */
+ public void skip( long skipNum ) {
+ for (long i = 0L; i != skipNum; ++i)
+ nextNode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Iterator#hasNext()
+ */
+ public boolean hasNext() {
+ return nodes.hasNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Iterator#next()
+ */
+ public Object next() {
+ return nextNode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Iterator#remove()
+ */
+ public void remove() {
throw new UnsupportedOperationException();
}
}
+ /**
+ * The {@link RowIterator} implementation returned by the {@link JcrQueryResult}.
+ *
+ * @see JcrQueryResult#getRows()
+ */
+ @NotThreadSafe
+ protected static class QueryResultRowIterator implements RowIterator {
+ protected final List<String> propertyNames;
+ private final Iterator<Object[]> tuples;
+ protected final int locationIndex;
+ protected final JcrSession session;
+ private long position = 0L;
+ private final long numRows;
+
+ protected QueryResultRowIterator( JcrSession session,
+ Columns columns,
+ Iterator<Object[]> tuples,
+ long numRows ) {
+ this.tuples = tuples;
+ this.propertyNames = columns.getColumnNames();
+ String selectorName = columns.getSelectorNames().get(0);
+ this.locationIndex = columns.getLocationIndex(selectorName);
+ this.session = session;
+ this.numRows = numRows;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.RowIterator#nextRow()
+ */
+ public Row nextRow() {
+ final Object[] tuple = tuples.next();
+ ++position;
+ return new Row() {
+ private Node node = null;
+ private Value[] values = null;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.Row#getValue(java.lang.String)
+ */
+ public Value getValue( String propertyName ) throws
ItemNotFoundException, RepositoryException {
+ return node().getProperty(propertyName).getValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.query.Row#getValues()
+ */
+ public Value[] getValues() throws RepositoryException {
+ if (values == null) {
+ int i = 0;
+ for (String propertyName : propertyNames) {
+ values[i++] = node().getProperty(propertyName).getValue();
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Load the node. The properties are <i>always</i> fetched
from the session to ensure that any modifications to
+ * the nodes within session are always used.
+ *
+ * @return the node
+ * @throws RepositoryException if the node could not be found
+ */
+ protected final Node node() throws RepositoryException {
+ if (node == null) {
+ Location location = (Location)tuple[locationIndex];
+ node = session.getNode(location.getPath());
+ }
+ return node;
+ }
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.RangeIterator#getPosition()
+ */
+ public long getPosition() {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.RangeIterator#getSize()
+ */
+ public long getSize() {
+ return numRows;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.RangeIterator#skip(long)
+ */
+ public void skip( long skipNum ) {
+ for (long i = 0L; i != skipNum; ++i) {
+ tuples.next();
+ }
+ position += skipNum;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Iterator#hasNext()
+ */
+ public boolean hasNext() {
+ return tuples.hasNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Iterator#next()
+ */
+ public Object next() {
+ return nextRow();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Iterator#remove()
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
}
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-12-03 20:45:23
UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java 2009-12-03 20:47:19
UTC (rev 1388)
@@ -82,7 +82,10 @@
import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.PropertyFactory;
import org.jboss.dna.graph.property.basic.GraphNamespaceRegistry;
+import org.jboss.dna.graph.query.parse.QueryParsers;
+import org.jboss.dna.graph.query.parse.SqlQueryParser;
import org.jboss.dna.graph.request.InvalidWorkspaceException;
+import org.jboss.dna.jcr.xpath.XPathQueryParser;
/**
* Creates JCR {@link Session sessions} to an underlying repository (which may be a
federated repository).
@@ -159,9 +162,20 @@
* A comma-delimited list of default roles provided for anonymous access. A null
or empty value for this option means that
* anonymous access is disabled.
*/
- ANONYMOUS_USER_ROLES;
+ ANONYMOUS_USER_ROLES,
/**
+ * The query system represents node types as tables that can be queried, but
there are two ways to define the columns for
+ * each of those tables. One approach is that each table only has columns
representing the (single-valued) property
+ * definitions explicitly defined by the node type. The other approach also adds
columns for each of the (single-valued)
+ * property definitions inherited by the node type from all of the {@link
javax.jcr.nodetype.NodeType#getSupertypes()}.
+ * <p>
+ * The default value is 'true'.
+ * </p>
+ */
+ TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES;
+
+ /**
* Determine the option given the option name. This does more than {@link
Option#valueOf(String)}, since this method first
* tries to match the supplied string to the option's {@link Option#name()
name}, then the uppercase version of the
* supplied string to the option's name, and finally if the supplied string
is a camel-case version of the name (e.g.,
@@ -213,6 +227,11 @@
* The default value for the {@link Option#READ_DEPTH} option is {@value} .
*/
public static final String ANONYMOUS_USER_ROLES = null;
+
+ /**
+ * The default value for the {@link Option#PROJECT_NODE_TYPES} option is {@value}
.
+ */
+ public static final String TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES =
Boolean.TRUE.toString();
}
/**
@@ -227,6 +246,8 @@
defaults.put(Option.JAAS_LOGIN_CONFIG_NAME,
DefaultOption.JAAS_LOGIN_CONFIG_NAME);
defaults.put(Option.READ_DEPTH, DefaultOption.READ_DEPTH);
defaults.put(Option.ANONYMOUS_USER_ROLES, DefaultOption.ANONYMOUS_USER_ROLES);
+ defaults.put(Option.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES,
+ DefaultOption.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES);
DEFAULT_OPTIONS = Collections.<Option, String>unmodifiableMap(defaults);
}
@@ -246,6 +267,8 @@
private final NamespaceRegistry persistentRegistry;
private final RepositoryObservationManager repositoryObservationManager;
private final SecurityContext anonymousUserContext;
+ private final QueryParsers queryParsers = new QueryParsers(new SqlQueryParser(), new
XPathQueryParser(),
+ new
FullTextSearchParser());
/**
* Creates a JCR repository that uses the supplied {@link RepositoryConnectionFactory
repository connection factory} to
@@ -399,7 +422,8 @@
// Set up the repository type manager ...
try {
- this.repositoryTypeManager = new
RepositoryNodeTypeManager(this.executionContext);
+ boolean includeInheritedProperties =
Boolean.valueOf(this.options.get(Option.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES));
+ this.repositoryTypeManager = new
RepositoryNodeTypeManager(this.executionContext, includeInheritedProperties);
this.repositoryTypeManager.registerNodeTypes(new CndNodeTypeSource(new
String[] {
"/org/jboss/dna/jcr/jsr_170_builtins.cnd",
"/org/jboss/dna/jcr/dna_builtins.cnd"}));
} catch (RepositoryException re) {
@@ -502,6 +526,10 @@
return result;
}
+ QueryParsers queryParsers() {
+ return queryParsers;
+ }
+
/**
* Returns the repository-level node type manager
*
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-12-03 20:45:23 UTC
(rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-12-03 20:47:19 UTC
(rev 1388)
@@ -196,6 +196,11 @@
return this.executionContext.getNamespaceRegistry();
}
+ void signalNamespaceChanges( boolean global ) {
+ nodeTypeManager().signalNamespaceChanges();
+ if (global) repository.getRepositoryTypeManager().signalNamespaceChanges();
+ }
+
JcrWorkspace workspace() {
return this.workspace;
}
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-12-03 20:45:23
UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java 2009-12-03 20:47:19
UTC (rev 1388)
@@ -85,7 +85,7 @@
* DNA implementation of a {@link Workspace JCR Workspace}.
*/
@NotThreadSafe
-final class JcrWorkspace implements Workspace {
+class JcrWorkspace implements Workspace {
/**
* The name of this workspace. This name is used as the name of the source when
@@ -158,8 +158,8 @@
// Create an execution context for this session, which should use the local
namespace registry ...
NamespaceRegistry globalRegistry = context.getNamespaceRegistry();
- NamespaceRegistry local = new LocalNamespaceRegistry(globalRegistry);
- this.context = context.with(local);
+ LocalNamespaceRegistry localRegistry = new
LocalNamespaceRegistry(globalRegistry);
+ this.context = context.with(localRegistry);
// Now create a graph for the session ...
this.graph = this.repository.createWorkspaceGraph(this.name, this.context);
Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeTypeSchemata.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeTypeSchemata.java
(rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeTypeSchemata.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -0,0 +1,324 @@
+/*
+ * 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.jcr;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.jcr.PropertyType;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.NamespaceRegistry.Namespace;
+import org.jboss.dna.graph.property.basic.LocalNamespaceRegistry;
+import org.jboss.dna.graph.query.model.AllNodes;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.TypeSystem;
+import org.jboss.dna.graph.query.validate.ImmutableSchemata;
+import org.jboss.dna.graph.query.validate.Schemata;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * A {@link Schemata} implementation that is constructed from the {@link NodeType}s and
{@link PropertyDefinition}s contained
+ * within a {@link RepositoryNodeTypeManager}. The resulting {@link Schemata.Table}s will
never change, so the
+ * {@link RepositoryNodeTypeManager} must replace it's cached instance whenever the
node types change.
+ */
+@Immutable
+class NodeTypeSchemata implements Schemata {
+
+ private final Schemata schemata;
+ private final Map<Integer, String> types;
+ private final Map<String, String> prefixesByUris = new HashMap<String,
String>();
+ private final boolean includeColumnsForInheritedProperties;
+ private final Iterable<JcrPropertyDefinition> propertyDefinitions;
+ private final Map<Name, JcrNodeType> nodeTypesByName;
+ private final Multimap<JcrNodeType, JcrNodeType> subtypesByName =
LinkedHashMultimap.create();
+
+ NodeTypeSchemata( ExecutionContext context,
+ Map<Name, JcrNodeType> nodeTypes,
+ Iterable<JcrPropertyDefinition> propertyDefinitions,
+ boolean includeColumnsForInheritedProperties ) {
+ this.includeColumnsForInheritedProperties =
includeColumnsForInheritedProperties;
+ this.propertyDefinitions = propertyDefinitions;
+ this.nodeTypesByName = nodeTypes;
+
+ // Identify the subtypes for each node type, and do this before we build any
views ...
+ for (JcrNodeType nodeType : nodeTypesByName.values()) {
+ // For each of the supertypes ...
+ for (JcrNodeType supertype : nodeType.getTypeAndSupertypes()) {
+ subtypesByName.put(supertype, nodeType);
+ }
+ }
+
+ // Build the schemata for the current node types ...
+ TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
+ ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(typeSystem);
+
+ // Build the fast-search for type names based upon PropertyType values ...
+ types = new HashMap<Integer, String>();
+ for (String typeName : typeSystem.getTypeNames()) {
+ org.jboss.dna.graph.property.PropertyType dnaType =
org.jboss.dna.graph.property.PropertyType.valueOf(typeName);
+ int jcrType = PropertyTypeUtil.jcrPropertyTypeFor(dnaType);
+ types.put(jcrType, typeName);
+ }
+
+ // Create the "ALLNODES" table, which will contain all possible
properties ...
+ addAllNodesTable(builder, context);
+
+ // Define a view for each node type ...
+ for (JcrNodeType nodeType : nodeTypesByName.values()) {
+ addView(builder, context, nodeType);
+ }
+
+ schemata = builder.build();
+ }
+
+ protected JcrNodeType getNodeType( Name nodeTypeName ) {
+ return nodeTypesByName.get(nodeTypeName);
+ }
+
+ private void recordName( NamespaceRegistry registry,
+ Name name ) {
+ String uri = name.getNamespaceUri();
+ prefixesByUris.put(uri, registry.getPrefixForNamespaceUri(uri, false));
+ }
+
+ protected final void addAllNodesTable( ImmutableSchemata.Builder builder,
+ ExecutionContext context ) {
+ NamespaceRegistry registry = context.getNamespaceRegistry();
+ TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
+
+ String tableName = AllNodes.ALL_NODES_NAME.getName();
+ boolean first = true;
+ Map<String, String> typesForNames = new HashMap<String, String>();
+ Set<String> fullTextSearchableNames = new HashSet<String>();
+ for (JcrPropertyDefinition defn : propertyDefinitions) {
+ if (defn.isResidual()) continue;
+ if (defn.isMultiple()) continue;
+ Name name = defn.getInternalName();
+ recordName(registry, name);
+ String columnName = name.getString(registry);
+ if (first) {
+ builder.addTable(tableName, columnName);
+ first = false;
+ }
+ String type = typeSystem.getDefaultType();
+ if (defn.getRequiredType() != PropertyType.UNDEFINED) {
+ type = types.get(defn.getRequiredType());
+ }
+ assert type != null;
+ String previousType = typesForNames.put(columnName, type);
+ if (previousType != null && !previousType.equals(type)) {
+ // There are two property definitions with the same name but different
types, so we need to find a common type ...
+ type = typeSystem.getCompatibleType(previousType, type);
+ }
+ boolean fullTextSearchable = fullTextSearchableNames.contains(columnName) ||
defn.isFullTextSearchable();
+ if (fullTextSearchable) fullTextSearchableNames.add(columnName);
+ // Add (or overwrite) the column ...
+ builder.addColumn(tableName, columnName, type, fullTextSearchable);
+ }
+ }
+
+ protected final void addView( ImmutableSchemata.Builder builder,
+ ExecutionContext context,
+ JcrNodeType nodeType ) {
+ NamespaceRegistry registry = context.getNamespaceRegistry();
+
+ String tableName = nodeType.getName();
+ JcrPropertyDefinition[] defns = null;
+ if (includeColumnsForInheritedProperties) {
+ defns = nodeType.getPropertyDefinitions();
+ } else {
+ defns = nodeType.getDeclaredPropertyDefinitions();
+ }
+ if (defns.length == 0) {
+ // There are no properties, so there's no reason to have the view ...
+ return;
+ }
+ // Create the SQL statement ...
+ StringBuilder viewDefinition = new StringBuilder("SELECT ");
+ boolean first = true;
+ for (JcrPropertyDefinition defn : defns) {
+ if (defn.isResidual()) continue;
+ if (defn.isMultiple()) continue;
+ Name name = defn.getInternalName();
+ recordName(registry, name);
+ String columnName = name.getString(registry);
+ if (first) first = false;
+ else viewDefinition.append(',');
+ viewDefinition.append('[').append(columnName).append(']');
+ }
+ viewDefinition.append(" FROM
").append(AllNodes.ALL_NODES_NAME).append(" WHERE ");
+
+ Collection<JcrNodeType> typeAndSubtypes = subtypesByName.get(nodeType);
+ if (nodeType.isMixin()) {
+ // Build the list of mixin types ...
+ StringBuilder mixinTypes = null;
+ for (JcrNodeType thisOrSupertype : typeAndSubtypes) {
+ if (!thisOrSupertype.isMixin()) continue;
+ if (mixinTypes == null) {
+ mixinTypes = new StringBuilder();
+
mixinTypes.append('[').append(JcrLexicon.MIXIN_TYPES.getString(registry)).append("]
IN (");
+ } else {
+ mixinTypes.append(',');
+ }
+ assert
prefixesByUris.containsKey(thisOrSupertype.getInternalName().getNamespaceUri());
+ String name = thisOrSupertype.getInternalName().getString(registry);
+ mixinTypes.append(name);
+ }
+ assert mixinTypes != null; // should at least include itself
+ viewDefinition.append(mixinTypes);
+ } else {
+ // Build the list of node type names ...
+ StringBuilder primaryTypes = null;
+ for (JcrNodeType thisOrSupertype : typeAndSubtypes) {
+ if (thisOrSupertype.isMixin()) continue;
+ if (primaryTypes == null) {
+ primaryTypes = new StringBuilder();
+
primaryTypes.append('[').append(JcrLexicon.PRIMARY_TYPE.getString(registry)).append("]
IN (");
+ } else {
+ primaryTypes.append(',');
+ }
+ assert
prefixesByUris.containsKey(thisOrSupertype.getInternalName().getNamespaceUri());
+ String name = thisOrSupertype.getInternalName().getString(registry);
+ primaryTypes.append(name);
+ }
+ assert primaryTypes != null; // should at least include itself
+ viewDefinition.append(primaryTypes);
+ }
+
+ // Define the view ...
+ builder.addView(tableName, viewDefinition.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata#getTable(org.jboss.dna.graph.query.model.SelectorName)
+ */
+ public Table getTable( SelectorName name ) {
+ return schemata.getTable(name);
+ }
+
+ /**
+ * Get a schemata instance that works with the suppplied session and that uses the
session-specific namespace mappings. Note
+ * that the resulting instance does not change as the session's namespace
mappings are changed, so when that happens the
+ * JcrSession must call this method again to obtain a new schemata.
+ *
+ * @param session the session; may not be null
+ * @return the schemata that can be used for the session; never null
+ */
+ public Schemata getSchemataForSession( JcrSession session ) {
+ assert session != null;
+ // If the session does not override any namespace mappings used in this schemata
...
+ if (!overridesNamespaceMappings(session)) {
+ // Then we can just use this schemata instance ...
+ return this;
+ }
+
+ // Otherwise, the session has some custom namespace mappings, so we need to
return a session-specific instance...
+ return new SessionSchemata(session);
+ }
+
+ /**
+ * Determine if the session overrides any namespace mappings used by this schemata.
+ *
+ * @param session the session; may not be null
+ * @return true if the session overrides one or more namespace mappings used in this
schemata, or false otherwise
+ */
+ private boolean overridesNamespaceMappings( JcrSession session ) {
+ NamespaceRegistry registry =
session.getExecutionContext().getNamespaceRegistry();
+ if (registry instanceof LocalNamespaceRegistry) {
+ Set<Namespace> localNamespaces =
((LocalNamespaceRegistry)registry).getLocalNamespaces();
+ if (localNamespaces.isEmpty()) {
+ // There are no local mappings ...
+ return false;
+ }
+ for (Namespace namespace : localNamespaces) {
+ if (prefixesByUris.containsKey(namespace.getNamespaceUri())) return
true;
+ }
+ // None of the local namespace mappings overrode any namespaces used by this
schemata ...
+ return false;
+ }
+ // We can't find the local mappings, so brute-force it ...
+ for (Namespace namespace : registry.getNamespaces()) {
+ String expectedPrefix = prefixesByUris.get(namespace.getNamespaceUri());
+ if (expectedPrefix == null) {
+ // This namespace is not used by this schemata ...
+ continue;
+ }
+ if (!namespace.getPrefix().equals(expectedPrefix)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Implementation class that builds the tables lazily.
+ */
+ @NotThreadSafe
+ protected class SessionSchemata implements Schemata {
+ private final JcrSession session;
+ private final ExecutionContext context;
+ private final ImmutableSchemata.Builder builder;
+ private final NameFactory nameFactory;
+ private Schemata schemata;
+
+ protected SessionSchemata( JcrSession session ) {
+ this.session = session;
+ this.context = this.session.getExecutionContext();
+ this.nameFactory = context.getValueFactories().getNameFactory();
+ this.builder =
ImmutableSchemata.createBuilder(context.getValueFactories().getTypeSystem());
+ // Add the "AllNodes" table ...
+ addAllNodesTable(builder, context);
+ this.schemata = builder.build();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata#getTable(org.jboss.dna.graph.query.model.SelectorName)
+ */
+ public Table getTable( SelectorName name ) {
+ Table table = schemata.getTable(name);
+ if (table == null) {
+ // Try getting it ...
+ Name nodeTypeName = nameFactory.create(name.getName());
+ JcrNodeType nodeType = getNodeType(nodeTypeName);
+ if (nodeType == null) return null;
+ addView(builder, context, nodeType);
+ schemata = builder.build();
+ }
+ return schemata.getTable(name);
+ }
+ }
+
+}
Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeTypeSchemata.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -49,36 +49,7 @@
if (value == null) return PropertyType.UNDEFINED;
// Get the DNA property type for this ...
- switch (org.jboss.dna.graph.property.PropertyType.discoverType(value)) {
- case STRING:
- return PropertyType.STRING;
- case NAME:
- return PropertyType.NAME;
- case LONG:
- return PropertyType.LONG;
- case UUID:
- return PropertyType.STRING; // JCR treats UUID properties as strings
- case URI:
- return PropertyType.STRING;
- case PATH:
- return PropertyType.PATH;
- case BOOLEAN:
- return PropertyType.BOOLEAN;
- case DATE:
- return PropertyType.DATE;
- case DECIMAL:
- return PropertyType.STRING; // better than losing information
- case DOUBLE:
- return PropertyType.DOUBLE;
- case BINARY:
- return PropertyType.BINARY;
- case OBJECT:
- return PropertyType.UNDEFINED;
- case REFERENCE:
- return PropertyType.REFERENCE;
- }
- assert false;
- return PropertyType.UNDEFINED;
+ return
jcrPropertyTypeFor(org.jboss.dna.graph.property.PropertyType.discoverType(value));
}
/**
@@ -113,4 +84,44 @@
return org.jboss.dna.graph.property.PropertyType.STRING;
}
+ /**
+ * Compute the DNA {@link org.jboss.dna.graph.property.PropertyType} for the given
JCR {@link PropertyType} value.
+ *
+ * @param dnaPropertyType the DNA property type; never null
+ * @return the JCR property type; always a valid value and never {@link
PropertyType#UNDEFINED}.
+ */
+ static final int jcrPropertyTypeFor( org.jboss.dna.graph.property.PropertyType
dnaPropertyType ) {
+ // Make sure the value is the correct type ...
+ switch (dnaPropertyType) {
+ case STRING:
+ return PropertyType.STRING;
+ case NAME:
+ return PropertyType.NAME;
+ case LONG:
+ return PropertyType.LONG;
+ case UUID:
+ return PropertyType.STRING; // JCR treats UUID properties as strings
+ case URI:
+ return PropertyType.STRING;
+ case PATH:
+ return PropertyType.PATH;
+ case BOOLEAN:
+ return PropertyType.BOOLEAN;
+ case DATE:
+ return PropertyType.DATE;
+ case DECIMAL:
+ return PropertyType.STRING; // better than losing information
+ case DOUBLE:
+ return PropertyType.DOUBLE;
+ case BINARY:
+ return PropertyType.BINARY;
+ case OBJECT:
+ return PropertyType.UNDEFINED;
+ case REFERENCE:
+ return PropertyType.REFERENCE;
+ }
+ assert false;
+ return PropertyType.UNDEFINED;
+ }
+
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java
===================================================================
---
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -112,9 +112,12 @@
private final Map<PropertyDefinitionId, JcrPropertyDefinition>
propertyDefinitions;
@GuardedBy( "nodeTypeManagerLock" )
private final Map<NodeDefinitionId, JcrNodeDefinition> childNodeDefinitions;
+ @GuardedBy( "nodeTypeManagerLock" )
+ private NodeTypeSchemata schemata;
private final PropertyFactory propertyFactory;
private final PathFactory pathFactory;
private final ReadWriteLock nodeTypeManagerLock = new ReentrantReadWriteLock();
+ private final boolean includeColumnsForInheritedProperties;
/**
* List of ways to filter the returned property definitions
@@ -138,8 +141,10 @@
ANY
}
- RepositoryNodeTypeManager( ExecutionContext context ) {
+ RepositoryNodeTypeManager( ExecutionContext context,
+ boolean includeColumnsForInheritedProperties ) {
this.context = context;
+ this.includeColumnsForInheritedProperties =
includeColumnsForInheritedProperties;
this.propertyFactory = context.getPropertyFactory();
this.pathFactory = context.getValueFactories().getPathFactory();
@@ -223,6 +228,29 @@
}
}
+ NodeTypeSchemata getRepositorySchemata() {
+ try {
+ nodeTypeManagerLock.writeLock().lock();
+ if (schemata == null) {
+ schemata = new NodeTypeSchemata(context, nodeTypes,
propertyDefinitions.values(),
+ includeColumnsForInheritedProperties);
+ }
+ return schemata;
+ } finally {
+ nodeTypeManagerLock.writeLock().unlock();
+ }
+ }
+
+ void signalNamespaceChanges() {
+ try {
+ nodeTypeManagerLock.writeLock().lock();
+ schemata = null;
+ } finally {
+ nodeTypeManagerLock.writeLock().unlock();
+ }
+ this.schemata = null;
+ }
+
JcrNodeType getNodeType( Name nodeTypeName ) {
try {
nodeTypeManagerLock.readLock().lock();
@@ -1178,6 +1206,7 @@
*/
// TODO: replace this with a query after queries work
this.nodeTypes.keySet().removeAll(nodeTypeNames);
+ this.schemata = null;
} finally {
nodeTypeManagerLock.writeLock().unlock();
@@ -1537,6 +1566,9 @@
// projectNodeTypeOnto(nodeType, parentOfTypeNodes, batch);
}
+
+ // Throw away the schemata, since the node types have changed ...
+ this.schemata = null;
} finally {
nodeTypeManagerLock.writeLock().unlock();
}
@@ -1596,6 +1628,8 @@
boolean multiple =
booleanFactory.create(getFirstPropertyValue(properties.get(JcrLexicon.MULTIPLE)));
boolean autoCreated =
booleanFactory.create(getFirstPropertyValue(properties.get(JcrLexicon.AUTO_CREATED)));
boolean isProtected =
booleanFactory.create(getFirstPropertyValue(properties.get(JcrLexicon.PROTECTED)));
+ Boolean ftsObj =
booleanFactory.create(getFirstPropertyValue(properties.get(JcrLexicon.IS_FULL_TEXT_SEARCHABLE)));
+ boolean fullTextSearchable = ftsObj != null ? ftsObj.booleanValue() : false;
Value[] defaultValues;
Property defaultValuesProperty = properties.get(JcrLexicon.DEFAULT_VALUES);
@@ -1624,7 +1658,7 @@
}
return new JcrPropertyDefinition(this.context, null, propertyName,
onParentVersionBehavior, autoCreated, mandatory,
- isProtected, defaultValues, requiredType,
valueConstraints, multiple);
+ isProtected, defaultValues, requiredType,
valueConstraints, multiple, fullTextSearchable);
}
private JcrNodeDefinition childNodeDefinitionFrom( Subgraph nodeTypeGraph,
@@ -1888,13 +1922,15 @@
for (JcrNodeDefinition ancestor : ancestors) {
if (ancestor.isProtected()) {
throw new InvalidNodeTypeDefinitionException(
-
JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType().getName(),
+
JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType()
+
.getName(),
"child node"));
}
if (ancestor.isMandatory() && !node.isMandatory()) {
throw new InvalidNodeTypeDefinitionException(
-
JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType().getName(),
+
JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType()
+
.getName(),
"child node"));
}
@@ -1959,15 +1995,16 @@
Value[] defaultValues = prop.getDefaultValues();
if (prop.isAutoCreated() && !prop.isProtected() && (defaultValues
== null || defaultValues.length == 0)) {
- throw new InvalidNodeTypeDefinitionException(
-
JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(),
-
prop.getDeclaringNodeType().getName()));
+ throw new
InvalidNodeTypeDefinitionException(JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(),
+
prop.getDeclaringNodeType()
+
.getName()));
}
if (!prop.isMultiple() && (defaultValues != null &&
defaultValues.length > 1)) {
throw new InvalidNodeTypeDefinitionException(
JcrI18n.singleValuedPropertyNeedsSingleValuedDefault.text(prop.getName(),
-
prop.getDeclaringNodeType().getName()));
+
prop.getDeclaringNodeType()
+
.getName()));
}
Name propName =
context.getValueFactories().getNameFactory().create(prop.getName());
@@ -1981,13 +2018,15 @@
for (JcrPropertyDefinition ancestor : ancestors) {
if (ancestor.isProtected()) {
throw new InvalidNodeTypeDefinitionException(
-
JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType().getName(),
+
JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType()
+
.getName(),
"property"));
}
if (ancestor.isMandatory() && !prop.isMandatory()) {
throw new InvalidNodeTypeDefinitionException(
-
JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType().getName(),
+
JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType()
+
.getName(),
"property"));
}
@@ -1998,14 +2037,16 @@
&& !Arrays.equals(ancestor.getValueConstraints(),
prop.getValueConstraints())) {
throw new InvalidNodeTypeDefinitionException(
JcrI18n.constraintsChangedInSubtype.text(propName,
-
ancestor.getDeclaringNodeType().getName()));
+
ancestor.getDeclaringNodeType()
+
.getName()));
}
if (!isAlwaysSafeConversion(prop.getRequiredType(),
ancestor.getRequiredType())) {
throw new InvalidNodeTypeDefinitionException(
JcrI18n.cannotRedefineProperty.text(propName,
PropertyType.nameFromValue(prop.getRequiredType()),
-
ancestor.getDeclaringNodeType().getName(),
+
ancestor.getDeclaringNodeType()
+
.getName(),
PropertyType.nameFromValue(ancestor.getRequiredType())));
}
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-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -23,6 +23,7 @@
*/
package org.jboss.dna.jcr.xpath;
+import javax.jcr.query.Query;
import org.jboss.dna.common.text.ParsingException;
import org.jboss.dna.graph.query.model.QueryCommand;
import org.jboss.dna.graph.query.model.TypeSystem;
@@ -37,7 +38,7 @@
public class XPathQueryParser implements QueryParser {
static final boolean COLLAPSE_INNER_COMPONENTS = true;
- private static final String LANGUAGE = "XPath";
+ private static final String LANGUAGE = Query.XPATH;
/**
* {@inheritDoc}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
===================================================================
---
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -131,6 +131,9 @@
// Result will be NameTest("jcr","root") or
DescendantOrSelf ...
if (first instanceof DescendantOrSelf) {
// do nothing ...
+ } else if (first instanceof NameTest && steps.size() == 1 &&
((NameTest)first).matches("jcr", "root")) {
+ // We can actually remove this first step, since relative paths are
relative to the root ...
+ steps = steps.subList(1, steps.size());
} else if (first instanceof NameTest && steps.size() > 1
&& ((NameTest)first).matches("jcr", "root")) {
// We can actually remove this first step, since relative paths are
relative to the root ...
steps = steps.subList(1, steps.size());
@@ -214,7 +217,7 @@
path.add(step);
}
}
- if (!path.isEmpty()) {
+ if (steps.isEmpty() || !path.isEmpty()) {
translateSource(tableName, path, where);
}
where.end();
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-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-12-03
20:47:19 UTC (rev 1388)
@@ -115,6 +115,8 @@
notStoredQuery=This query has not been stored or loaded
invalidQueryLanguage="{0}" is not a valid query langauge. Supported languages
are\: {1}
+queryCannotBeParsedUsingLanguage=The {0} query "{1}" is not well-formed: {2}
+queryInLanguageIsNotValid=The {0} query "{1}" has one or more errors: {2}
invalidNodeTypeName=Node types cannot have a null or empty name
noSuchNodeType=Type named '{0}' does not exist
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrTest.java 2009-12-03 20:45:23
UTC (rev 1387)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrTest.java 2009-12-03 20:47:19
UTC (rev 1388)
@@ -70,7 +70,7 @@
// Create the node type manager ...
context.getNamespaceRegistry().register(Vehicles.Lexicon.Namespace.PREFIX,
Vehicles.Lexicon.Namespace.URI);
- rntm = new RepositoryNodeTypeManager(context);
+ rntm = new RepositoryNodeTypeManager(context, true);
try {
rntm.registerNodeTypes(new CndNodeTypeSource(new String[]
{"/org/jboss/dna/jcr/jsr_170_builtins.cnd",
"/org/jboss/dna/jcr/dna_builtins.cnd"}));
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-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -45,7 +45,9 @@
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.graph.query.parse.QueryParsers;
import org.jboss.dna.jcr.nodetype.NodeTypeTemplate;
+import org.jboss.dna.jcr.xpath.XPathQueryParser;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations.Mock;
import org.mockito.invocation.InvocationOnMock;
@@ -70,6 +72,7 @@
protected Map<JcrRepository.Option, String> options;
protected NamespaceRegistry registry;
protected WorkspaceLockManager workspaceLockManager;
+ protected QueryParsers parsers;
@Mock
protected JcrRepository repository;
@@ -120,7 +123,7 @@
};
// Stub out the repository, since we only need a few methods ...
- repoTypeManager = new RepositoryNodeTypeManager(context);
+ repoTypeManager = new RepositoryNodeTypeManager(context, true);
try {
this.repoTypeManager.registerNodeTypes(new CndNodeTypeSource(new String[]
{"/org/jboss/dna/jcr/jsr_170_builtins.cnd",
@@ -163,6 +166,10 @@
initializeOptions();
stub(repository.getOptions()).toReturn(options);
+ // Set up the parsers for the repository (we only need the XPath parsers at the
moment) ...
+ parsers = new QueryParsers(new XPathQueryParser());
+ stub(repository.queryParsers()).toReturn(parsers);
+
// Set up the session attributes ...
// Set up the session attributes ...
sessionAttributes = new HashMap<String, Object>();
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/CndNodeTypeRegistrationTest.java
===================================================================
---
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/CndNodeTypeRegistrationTest.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/CndNodeTypeRegistrationTest.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -56,7 +56,7 @@
context = new ExecutionContext();
context.getNamespaceRegistry().register(TestLexicon.Namespace.PREFIX,
TestLexicon.Namespace.URI);
- repoTypeManager = new RepositoryNodeTypeManager(context);
+ repoTypeManager = new RepositoryNodeTypeManager(context, true);
try {
this.repoTypeManager.registerNodeTypes(new CndNodeTypeSource(new String[]
{"/org/jboss/dna/jcr/jsr_170_builtins.cnd",
"/org/jboss/dna/jcr/dna_builtins.cnd"}));
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -250,6 +250,8 @@
options.put(Option.PROJECT_NODE_TYPES, DefaultOption.PROJECT_NODE_TYPES);
options.put(Option.READ_DEPTH, DefaultOption.READ_DEPTH);
options.put(Option.ANONYMOUS_USER_ROLES, DefaultOption.ANONYMOUS_USER_ROLES);
+ options.put(Option.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES,
+ DefaultOption.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES);
assertThat(repository.getOptions(), is(options));
}
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNamespaceRegistryTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNamespaceRegistryTest.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNamespaceRegistryTest.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -49,7 +49,6 @@
MockitoAnnotations.initMocks(this);
executionContext = new ExecutionContext();
registry = new JcrNamespaceRegistry(executionContext.getNamespaceRegistry(),
session);
-
}
protected void assertThatNamespaceIsRegistered( String prefix,
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java 2009-12-03
20:45:23 UTC (rev 1387)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -66,7 +66,7 @@
validLocation = Location.create(validUuid);
// Stub out the repository, since we only need a few methods ...
- repoTypeManager = new RepositoryNodeTypeManager(context);
+ repoTypeManager = new RepositoryNodeTypeManager(context, true);
PathFactory pathFactory = context.getValueFactories().getPathFactory();
stub(repository.getRepositoryTypeManager()).toReturn(repoTypeManager);
Modified:
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
===================================================================
---
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java 2009-12-03
20:45:23 UTC (rev 1387)
+++
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java 2009-12-03
20:47:19 UTC (rev 1388)
@@ -65,6 +65,7 @@
@Test
public void shouldTranslateFromXPathOfAnyNode() {
+ assertThat(xpath("/jcr:root"), isSql("SELECT * FROM __ALLNODES__
AS nodeSet1"));
assertThat(xpath("//element(*)"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1"));
assertThat(xpath("/jcr:root//element(*)"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1"));
assertThat(xpath("//*"), isSql("SELECT * FROM __ALLNODES__ AS
nodeSet1"));