Package org.jboss.dna.jcr

Source Code of org.jboss.dna.jcr.NodeTypeSchemata$SessionSchemata

/*
* 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.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
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 org.jboss.dna.search.lucene.IndexRules;
import org.jboss.dna.search.lucene.LuceneSearchEngine;
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();
    private final IndexRules indexRules;

    NodeTypeSchemata( ExecutionContext context,
                      Map<Name, JcrNodeType> nodeTypes,
                      Iterable<JcrPropertyDefinition> propertyDefinitions,
                      boolean includeColumnsForInheritedProperties ) {
        this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties;
        this.propertyDefinitions = propertyDefinitions;
        this.nodeTypesByName = nodeTypes;

        // Register all the namespace prefixes by URIs ...
        for (Namespace namespace : context.getNamespaceRegistry().getNamespaces()) {
            this.prefixesByUris.put(namespace.getNamespaceUri(), namespace.getPrefix());
        }

        // 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 ...
        IndexRules.Builder indexRulesBuilder = IndexRules.createBuilder(LuceneSearchEngine.DEFAULT_RULES);
        addAllNodesTable(builder, indexRulesBuilder, context);

        // Define a view for each node type ...
        for (JcrNodeType nodeType : nodeTypesByName.values()) {
            addView(builder, context, nodeType);
        }

        schemata = builder.build();
        indexRules = indexRulesBuilder.build();
    }

    /**
     * Get the index rules ...
     *
     * @return indexRules
     */
    public IndexRules getIndexRules() {
        return indexRules;
    }

    protected JcrNodeType getNodeType( Name nodeTypeName ) {
        return nodeTypesByName.get(nodeTypeName);
    }

    protected final void addAllNodesTable( ImmutableSchemata.Builder builder,
                                           IndexRules.Builder indexRuleBuilder,
                                           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.isPrivate()) continue;
            // if (defn.isMultiple()) continue;
            Name name = defn.getInternalName();

            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);

            // And build an indexing rule for this type ...
            if (indexRuleBuilder != null) addIndexRule(indexRuleBuilder, defn, type, typeSystem);
        }
    }

    /**
     * Add an index rule for the given property definition and the type in the {@link TypeSystem}.
     *
     * @param builder the index rule builder; never null
     * @param defn the property definition; never null
     * @param type the TypeSystem type, which may be a more general type than dictated by the definition, since multiple
     *        definitions with the same name require the index rule to use the common base type; never null
     * @param typeSystem the type system; never null
     */
    protected final void addIndexRule( IndexRules.Builder builder,
                                       JcrPropertyDefinition defn,
                                       String type,
                                       TypeSystem typeSystem ) {
        Store store = Store.YES;
        Index index = defn.isFullTextSearchable() ? Index.ANALYZED : Index.NO;
        if (typeSystem.getStringFactory().getTypeName().equals(type)) {
            builder.stringField(defn.getInternalName(), store, index);
        } else if (typeSystem.getDateTimeFactory().getTypeName().equals(type)) {
            Long minimum = typeSystem.getLongFactory().create(defn.getMinimumValue());
            Long maximum = typeSystem.getLongFactory().create(defn.getMaximumValue());
            builder.dateField(defn.getInternalName(), store, index, minimum, maximum);
        } else if (typeSystem.getLongFactory().getTypeName().equals(type)) {
            Long minimum = typeSystem.getLongFactory().create(defn.getMinimumValue());
            Long maximum = typeSystem.getLongFactory().create(defn.getMaximumValue());
            builder.longField(defn.getInternalName(), store, index, minimum, maximum);
        } else if (typeSystem.getDoubleFactory().getTypeName().equals(type)) {
            Double minimum = typeSystem.getDoubleFactory().create(defn.getMinimumValue());
            Double maximum = typeSystem.getDoubleFactory().create(defn.getMaximumValue());
            builder.doubleField(defn.getInternalName(), store, index, minimum, maximum);
        } else if (typeSystem.getBooleanFactory().getTypeName().equals(type)) {
            builder.booleanField(defn.getInternalName(), store, index);
        } else if (typeSystem.getBinaryFactory().getTypeName().equals(type)) {
            store = Store.NO;
            builder.binaryField(defn.getInternalName(), store, index);
        } else {
            // Everything else gets stored as a string ...
            builder.stringField(defn.getInternalName(), store, index);
        }

    }

    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;
            if (defn.isPrivate()) continue;
            Name name = defn.getInternalName();

            String columnName = name.getString(registry);
            if (first) first = false;
            else viewDefinition.append(',');
            viewDefinition.append('[').append(columnName).append(']');
        }
        if (first) {
            // All the properties were skipped ...
            return;
        }
        viewDefinition.append(" FROM ").append(AllNodes.ALL_NODES_NAME);

        // The 'nt:base' node type will have every single object in it, so we don't need to add the type criteria ...
        if (!JcrNtLexicon.BASE.equals(nodeType.getInternalName())) {
            // The node type is not 'nt:base', which
            viewDefinition.append(" WHERE ");

            Collection<JcrNodeType> typeAndSubtypes = subtypesByName.get(nodeType);
            if (nodeType.isMixin()) {
                // Build the list of mixin types ...
                StringBuilder mixinTypes = null;
                int count = 0;
                for (JcrNodeType thisOrSupertype : typeAndSubtypes) {
                    if (!thisOrSupertype.isMixin()) continue;
                    if (mixinTypes == null) {
                        mixinTypes = new StringBuilder();
                    } else {
                        mixinTypes.append(',');
                    }
                    assert prefixesByUris.containsKey(thisOrSupertype.getInternalName().getNamespaceUri());
                    String name = thisOrSupertype.getInternalName().getString(registry);
                    mixinTypes.append('[').append(name).append(']');
                    ++count;
                }
                assert mixinTypes != null; // should at least include itself
                assert count > 0;
                viewDefinition.append('[').append(JcrLexicon.MIXIN_TYPES.getString(registry)).append(']');
                if (count == 1) {
                    viewDefinition.append('=').append(mixinTypes);
                } else {
                    viewDefinition.append(" IN (").append(mixinTypes).append(')');
                }
            } else {
                // Build the list of node type names ...
                StringBuilder primaryTypes = null;
                int count = 0;
                for (JcrNodeType thisOrSupertype : typeAndSubtypes) {
                    if (thisOrSupertype.isMixin()) continue;
                    if (primaryTypes == null) {
                        primaryTypes = new StringBuilder();
                    } else {
                        primaryTypes.append(',');
                    }
                    assert prefixesByUris.containsKey(thisOrSupertype.getInternalName().getNamespaceUri());
                    String name = thisOrSupertype.getInternalName().getString(registry);
                    primaryTypes.append('[').append(name).append(']');
                    ++count;
                }
                assert primaryTypes != null; // should at least include itself
                assert count > 0;
                viewDefinition.append('[').append(JcrLexicon.PRIMARY_TYPE.getString(registry)).append(']');
                if (count == 1) {
                    viewDefinition.append('=').append(primaryTypes);
                } else {
                    viewDefinition.append(" IN (").append(primaryTypes).append(')');
                }
            }
        }

        // 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, null, 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);
        }
    }

}
TOP

Related Classes of org.jboss.dna.jcr.NodeTypeSchemata$SessionSchemata

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.