org.topazproject.otm.ClassMetadata.java Source code

Java tutorial

Introduction

Here is the source code for org.topazproject.otm.ClassMetadata.java

Source

/* $HeadURL::                                                                            $
 * $Id$
 *
 * Copyright (c) 2007-2008 by Topaz, Inc.
 * http://topazproject.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.topazproject.otm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.topazproject.otm.mapping.BlobMapper;
import org.topazproject.otm.mapping.EmbeddedMapper;
import org.topazproject.otm.mapping.IdMapper;
import org.topazproject.otm.mapping.Mapper;
import org.topazproject.otm.mapping.RdfMapper;
import org.topazproject.otm.mapping.VarMapper;
import org.topazproject.otm.mapping.EntityBinder;

/**
 * Meta information for mapping a class to a set of triples.
 *
 * @author Pradeep Krishnan
 */
public class ClassMetadata {
    private static final Log log = LogFactory.getLog(ClassMetadata.class);

    private final Set<String> allTypes;
    private final Set<String> types;
    private final String name;
    private final Set<String> superEntities;
    private final String graph;
    private final IdMapper idField;
    private final BlobMapper blobField;
    private final Map<String, List<RdfMapper>> uriMap;
    private final Map<String, Mapper> nameMap;
    private final Map<EntityMode, EntityBinder> binders;
    private final Collection<RdfMapper> rdfFields;
    private final Collection<VarMapper> varFields;
    private final Collection<EmbeddedMapper> embeds;
    private final String query;
    private final Map<String, List<VarMapper>> varMap;

    /**
     * Creates a new ClassMetadata object for an Entity.
     *
     * @param binders the entity binders
     * @param name the entity name for use in queries
     * @param types the most specific rdf:type that identify this class
     * @param allTypes set of rdf:type values that identify this class
     * @param graph the graph where this class is persisted
     * @param idField the mapper for the id field
     * @param fields mappers for all persistable fields (includes embedded class fields)
     * @param blobField loader for the blob
     * @param superEntities super class entities for which this is a sub-class of
     * @param embeds fields that are embeds
     */
    public ClassMetadata(Map<EntityMode, EntityBinder> binders, String name, Set<String> types,
            Set<String> allTypes, String graph, IdMapper idField, Collection<RdfMapper> fields,
            BlobMapper blobField, Set<String> superEntities, Collection<EmbeddedMapper> embeds)
            throws OtmException {
        this.binders = binders;
        this.name = name;
        this.query = null;
        this.graph = graph;
        this.idField = idField;
        this.blobField = blobField;

        this.superEntities = Collections.unmodifiableSet(new HashSet<String>(superEntities));
        this.types = Collections.unmodifiableSet(new HashSet<String>(types));
        this.allTypes = Collections.unmodifiableSet(new HashSet<String>(allTypes));
        this.embeds = Collections.unmodifiableCollection(new ArrayList<EmbeddedMapper>(embeds));
        this.rdfFields = Collections.unmodifiableCollection(new ArrayList<RdfMapper>(fields));
        this.varFields = null;

        Map<String, List<RdfMapper>> uriMap = new HashMap<String, List<RdfMapper>>();
        Map<String, Mapper> nameMap = new HashMap<String, Mapper>();

        for (RdfMapper m : fields) {
            List<RdfMapper> rdfMappers = uriMap.get(m.getUri());

            // See ticket #840. The following enforces the current restrictions.
            if (rdfMappers == null)
                uriMap.put(m.getUri(), rdfMappers = new ArrayList<RdfMapper>());
            else if (isDuplicateMapping(rdfMappers, m))
                throw new OtmException("Duplicate predicate uri for " + m.getName() + " in " + name);
            else if (rdfMappers.get(0).getColType() != m.getColType())
                throw new OtmException("The colType for " + m.getName() + " in " + name + ", must be "
                        + rdfMappers.get(0).getColType() + " as defined by " + rdfMappers.get(0).getName()
                        + " since they both share the same predicate uri");
            else if (!sameGraph(rdfMappers.get(0), m, graph))
                throw new OtmException("The graph for " + m.getName() + " in " + name + ", must be "
                        + rdfMappers.get(0).getGraph() + " as defined by " + rdfMappers.get(0).getName()
                        + " since they both share the same predicate uri");

            rdfMappers.add(m);

            if (nameMap.put(m.getName(), m) != null)
                throw new OtmException("Duplicate field name " + m.getName() + " in " + name);
        }

        for (EmbeddedMapper m : embeds) {
            if (nameMap.put(m.getName(), m) != null)
                throw new OtmException("Duplicate field name " + m.getName() + " in " + name);
        }

        this.uriMap = Collections.unmodifiableMap(uriMap);
        this.nameMap = Collections.unmodifiableMap(nameMap);
        this.varMap = null;

        // Notify binders
        for (EntityBinder b : binders.values())
            b.bindComplete(this);
    }

    /**
     * Creates a new ClassMetadata object for a View.
     *
     * @param binders the entity binders
     * @param name    the view name (used to look up class-metadata)
     * @param query   the query, or null for SubView's
     * @param idField the mapper for the id field
     * @param fields  mappers for all persistable fields (includes embedded class fields)
     */
    public ClassMetadata(Map<EntityMode, EntityBinder> binders, String name, String query, IdMapper idField,
            Collection<VarMapper> fields) throws OtmException {
        this.binders = binders;
        this.name = name;
        this.query = query;
        this.graph = null;
        this.idField = idField;
        this.blobField = null;

        this.superEntities = Collections.emptySet();
        this.types = Collections.emptySet();
        this.allTypes = Collections.emptySet();
        this.varFields = Collections.unmodifiableCollection(new ArrayList<VarMapper>(fields));
        this.rdfFields = null;
        this.embeds = null;

        Map<String, List<VarMapper>> varMap = new HashMap<String, List<VarMapper>>();
        Map<String, Mapper> nameMap = new HashMap<String, Mapper>();
        for (VarMapper m : fields) {
            if (nameMap.put(m.getName(), m) != null)
                throw new OtmException("Duplicate field name " + m.getName() + " in view " + name);

            List<VarMapper> varMappers = varMap.get(m.getProjectionVar());
            if (varMappers == null)
                varMap.put(m.getProjectionVar(), varMappers = new ArrayList<VarMapper>());
            varMappers.add(m);
        }

        this.uriMap = null;
        this.nameMap = Collections.unmodifiableMap(nameMap);
        this.varMap = Collections.unmodifiableMap(varMap);

        // Notify binders
        for (EntityBinder b : binders.values())
            b.bindComplete(this);
    }

    private static Set<String> buildNames(String name, Collection<EntityBinder> binders) {
        Set<String> names = new HashSet<String>();

        names.add(name);

        for (EntityBinder binder : binders)
            Collections.addAll(names, binder.getNames());

        return names;
    }

    /**
     * Gets the binder corresponding to the entity mode.
     *
     * @param mode the entity mode
     *
     * @return the entity binder
     */
    public EntityBinder getEntityBinder(EntityMode mode) {
        return binders.get(mode);
    }

    /**
     * Gets the binder corresponding to the entity mode.
     *
     * @param session the otm session implementation
     *
     * @return the entity binder
     */
    public EntityBinder getEntityBinder(Session session) {
        return binders.get(session.getEntityMode());
    }

    /**
     * Gets the graph where this class is persisted.
     *
     * @return the graph identifier
     */
    public String getGraph() {
        return graph;
    }

    /**
     * Gets the entity name for this ClassMetadata
     *
     * @return the entity name
     */
    public String getName() {
        return name;
    }

    /**
     * Gets the set of all unique names by which this entity is known.
     *
     * @return set of unique names for this entity
     */
    public Set<String> getNames() {
        return buildNames(name, binders.values());
    }

    /**
     * Gets the most specific rdf:type that describes this class.
     *
     * @return the set of rdf:type values. Could be an empty set.
     *
     * @see #getAllTypes
     */
    public Set<String> getTypes() {
        return types;
    }

    /**
     * Gets the set of all rdf:type values that describe this class including all types
     * that are inherited from the super-classes.
     *
     * @return set of rdf:type values. Could be an empty set.
     *
     * @see #getTypes
     * @see #getSuperEntities
     */
    public Set<String> getAllTypes() {
        return allTypes;
    }

    /**
     * Gets the set of entities that are declared as super-classes for this entity.
     *
     * @return the super entity or null
     */
    public Set<String> getSuperEntities() {
        return superEntities;
    }

    /**
     * Gets the mappers for all predicates that this ClassMetadata maps. All
     * embedded class fields are flattened and made available as part of this
     * list for convenience.
     *
     * @return collection of field mappers.
     */
    public Collection<RdfMapper> getRdfMappers() {
        return rdfFields;
    }

    /**
     * Gets the mappers for all projection variables in a view.
     *
     * @return collection of field mappers.
     */
    public Collection<VarMapper> getVarMappers() {
        return varFields;
    }

    /**
     * Gets the mappers for all embedded variables in a view.
     *
     * @return collection of embedded mappers.
     */
    public Collection<EmbeddedMapper> getEmbeddedMappers() {
        return embeds;
    }

    /**
     * Gets the mapper for the id/subject-uri field.
     *
     * @return the id field or null for embeddable classes and views
     */
    public IdMapper getIdField() {
        return idField;
    }

    /**
     * Gets the PropertyBinder for the blob field.
     *
     * @return the blob field or null
     */
    public BlobMapper getBlobField() {
        return blobField;
    }

    /**
     * Gets a field mapper by its predicate uri.
     *
     * @param sf the session factory for looking up associations
     * @param uri the predicate uri
     * @param inverse if the mapping is reverse (ie. o,p,s instead of s,p,o))
     * @param typeUris Collection of rdf:Type values
     *
     * @return the mapper or null
     */
    public RdfMapper getMapperByUri(SessionFactory sf, String uri, boolean inverse, Collection<String> typeUris) {
        List<RdfMapper> rdfMappers = uriMap.get(uri);

        if (rdfMappers == null)
            return null;

        Collection<String> uris = typeUris;
        RdfMapper candidate = null;

        for (RdfMapper m : rdfMappers) {
            if (m.hasInverseUri() != inverse)
                continue;

            if (candidate == null)
                candidate = m;

            if ((uris == null) || uris.isEmpty())
                break;

            ClassMetadata assoc = !m.isAssociation() ? null : sf.getClassMetadata(m.getAssociatedEntity());

            if ((assoc == null) || assoc.getTypes().isEmpty())
                continue;

            if (uris.containsAll(assoc.getTypes())) {
                if (uris == typeUris)
                    uris = new HashSet<String>(typeUris); // lazy copy

                uris.removeAll(assoc.getAllTypes());
                candidate = m;
            }
        }

        return candidate;
    }

    /**
     * Gets the field mappers for a predicate-uri.
     *
     * @param uri the uri
     * @param inverse the inverse flag
     * @return the mappers or an empty list
     */
    public List<RdfMapper> getMappersByUri(String uri, boolean inverse) {
        List<RdfMapper> l = new ArrayList<RdfMapper>();
        List<RdfMapper> mappers = uriMap.get(uri);

        if (mappers != null)
            for (RdfMapper m : mappers)
                if (m.hasInverseUri() == inverse)
                    l.add(m);

        return l;
    }

    /**
     * Gets a field mapper by its field name.
     *
     * @param name the field name.
     *
     * @return the mapper or null
     */
    public Mapper getMapperByName(String name) {
        return nameMap.get(name);
    }

    /**
     * Gets the field mappers for a projection variable (for Views).
     *
     * @param var the projection variable name.
     * @return the mappers or null
     */
    public List<VarMapper> getMappersByVar(String var) {
        List<VarMapper> mappers = varMap.get(var);
        return (mappers != null) ? new ArrayList<VarMapper>(mappers) : null;
    }

    /**
     * Get the OQL query string if this is for a View.
     *
     * @return the OQL query string, or null if this is not a View
     */
    public String getQuery() {
        return query;
    }

    /*
     * inherited javadoc
     */
    public String toString() {
        return "ClassMetadata[name=" + getName() + ", type=" + getTypes() + "]";
    }

    /**
     * Tests if this metadata is for a persistable class. To be persistable, the class must
     * have an id field. It must also have a graph or if a graph is missing then there should not
     * be any persistable fields or rdf types and must have a blob field.
     *
     * @return true only if this class can be persisted
     */
    public boolean isPersistable() {
        if ((idField == null) || (rdfFields == null))
            return false;
        if (graph != null)
            return true;
        return ((allTypes.size() + rdfFields.size()) == 0) && (blobField != null);
    }

    /**
     * Tests if this meta-data is for a view class.
     *
     * @return true for view type
     */
    public boolean isView() {
        return (query != null);
    }

    private static boolean isDuplicateMapping(List<RdfMapper> rdfMappers, RdfMapper o) {
        for (RdfMapper m : rdfMappers) {
            if (m.hasInverseUri() != o.hasInverseUri())
                continue;

            if (!m.isAssociation() || !o.isAssociation())
                return true;

            if (m.getAssociatedEntity().equals(o.getAssociatedEntity()))
                return true;
        }

        return false;
    }

    private static boolean sameGraph(RdfMapper m1, RdfMapper m2, String graph) {
        String g1 = m1.getGraph();
        String g2 = m2.getGraph();

        if ((g1 == null) || "".equals(g1))
            g1 = graph;

        if ((g2 == null) || "".equals(g2))
            g2 = graph;

        return (g1 != null) ? g1.equals(g2) : (g2 == null);
    }

    /**
     * Tests to see if instances of this ClassMetadata is assignment compatible from
     * instances of another. If an EntityMode is passed in, assignment compatibility
     * in that mode is also checked.
     *
     * @param other the other ClassMetadata
     * @param mode the entity mode or null
     *
     * @return true if instances of this is assignable from instances of the other
     */
    public boolean isAssignableFrom(ClassMetadata other, EntityMode mode) {
        if (!other.allTypes.containsAll(this.allTypes))
            return false;

        if (mode != null) {
            EntityBinder thisBinder = getEntityBinder(mode);
            EntityBinder otherBinder = other.getEntityBinder(mode);
            if (!thisBinder.isAssignableFrom(otherBinder))
                return false;
        }
        return true;
    }

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof ClassMetadata))
            return false;

        ClassMetadata o = (ClassMetadata) other;

        return name.equals(o.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }
}