org.topazproject.otm.metadata.AnnotationClassMetaFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.topazproject.otm.metadata.AnnotationClassMetaFactory.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.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import java.net.URI;
import java.net.URL;
import java.net.URISyntaxException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.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.CascadeType;
import org.topazproject.otm.ClassMetadata;
import org.topazproject.otm.CollectionType;
import org.topazproject.otm.EntityMode;
import org.topazproject.otm.FetchType;
import org.topazproject.otm.GraphConfig;
import org.topazproject.otm.OtmException;
import org.topazproject.otm.Rdf;
import org.topazproject.otm.SessionFactory;
import org.topazproject.otm.TripleStore;
import org.topazproject.otm.annotations.Alias;
import org.topazproject.otm.annotations.Aliases;
import org.topazproject.otm.annotations.Blob;
import org.topazproject.otm.annotations.Embedded;
import org.topazproject.otm.annotations.Entity;
import org.topazproject.otm.annotations.GeneratedValue;
import org.topazproject.otm.annotations.Graph;
import org.topazproject.otm.annotations.Graphs;
import org.topazproject.otm.annotations.Id;
import org.topazproject.otm.annotations.Predicate;
import org.topazproject.otm.annotations.Predicate.PropType;
import org.topazproject.otm.annotations.PredicateMap;
import org.topazproject.otm.annotations.Projection;
import org.topazproject.otm.annotations.Searchable;
import org.topazproject.otm.annotations.SubClassResolver;
import org.topazproject.otm.annotations.SubView;
import org.topazproject.otm.annotations.UriPrefix;
import org.topazproject.otm.annotations.View;
import org.topazproject.otm.id.IdentifierGenerator;
import org.topazproject.otm.mapping.java.ClassBinder;
import org.topazproject.otm.mapping.java.Property;
import org.topazproject.otm.mapping.java.PropertyBinderFactory;
import org.topazproject.otm.search.PreProcessor;
import org.topazproject.otm.serializer.Serializer;

/**
 * A factory that processes annotations on a class.
 *
 * @author Pradeep Krishnan
 */
public class AnnotationClassMetaFactory {
    private static final Log log = LogFactory.getLog(AnnotationClassMetaFactory.class);
    private SessionFactory sf;

    /** The list of Annotations recognized by this class. */
    private static final List<Class<? extends Annotation>> KNOWN_ANNOTATIONS;

    static {
        List<Class<? extends Annotation>> knowns = new ArrayList<Class<? extends Annotation>>();
        knowns.add(Aliases.class);
        knowns.add(Graphs.class);
        knowns.add(Entity.class);
        knowns.add(SubView.class);
        knowns.add(UriPrefix.class);
        knowns.add(View.class);
        KNOWN_ANNOTATIONS = Collections.unmodifiableList(knowns);
    }

    /**
     * Creates a new AnnotationClassMetaFactory object.
     *
     * @param sf the session factory
     */
    public AnnotationClassMetaFactory(SessionFactory sf) {
        this.sf = sf;
    }

    /**
     * Retrieve a list of all the annotation types that this MetaFactory recognizes on classes.
     *
     * @return A complete unmodifiable list of all the Annotation classes this class recognizes.
     */
    public static final List<Class<? extends Annotation>> getKnownAnnotations() {
        return KNOWN_ANNOTATIONS;
    }

    /**
     * Creates a new ClassMetadata object.
     *
     * @param clazz the class with annotations
     *
     * @throws OtmException on an error
     */
    public void create(Class<?> clazz) throws OtmException {
        // Add alias definitions from both class and package
        addAliases(clazz.getAnnotation(Aliases.class));
        if (clazz.getPackage() != null)
            addAliases(clazz.getPackage().getAnnotation(Aliases.class));

        // Add graph definitions from both class and package
        addGraphs(clazz.getAnnotation(Graphs.class));
        if (clazz.getPackage() != null)
            addGraphs(clazz.getPackage().getAnnotation(Graphs.class));

        if ((clazz.getAnnotation(View.class) != null) || (clazz.getAnnotation(SubView.class) != null))
            createView(clazz);
        else
            createEntity(clazz);
    }

    private void addGraphs(Graphs graphs) throws OtmException {
        if (graphs == null)
            return;
        for (Graph g : graphs.value()) {
            try {
                GraphConfig oldGraph = sf.getGraph(g.id());
                if (oldGraph != null)
                    sf.removeGraph(oldGraph);

                URI gType = null;
                if (!"".equals(g.type()))
                    gType = new URI(g.type());
                sf.addGraph(new GraphConfig(g.id(), new URI(g.uri()), gType));
            } catch (URISyntaxException e) {
                throw new OtmException("Invalid URI syntax in graph definition: " + g.id(), e);
            }
        }
    }

    private void addAliases(Aliases aliases) throws OtmException {
        if (aliases == null)
            return;
        for (Alias a : aliases.value())
            sf.addAlias(a.alias(), a.value());
    }

    private void createEntity(Class<?> clazz) throws OtmException {
        Set<String> types = new HashSet<String>();
        Set<String> sup = new HashSet<String>();
        String graph = null;
        String uriPrefix = null;

        if (log.isDebugEnabled())
            log.debug("Creating class-meta for " + clazz);

        Entity entity = clazz.getAnnotation(Entity.class);

        if (entity != null) {
            if (!"".equals(entity.graph()))
                graph = entity.graph();

            for (String t : entity.types())
                types.add(sf.expandAlias(t));
        }

        String name = getEntityName(clazz);
        Class<?> s = clazz.getSuperclass();

        if ((s != null) && !s.equals(Object.class))
            sup.add(getEntityName(s));

        for (Class<?> c : clazz.getInterfaces())
            sup.add(getEntityName(c));

        if (s != null)
            for (Class<?> c : s.getInterfaces())
                sup.remove(getEntityName(c));

        EntityDefinition ed = new EntityDefinition(name, types, graph, sup);
        UriPrefix uriPrefixAnn = clazz.getAnnotation(UriPrefix.class);

        if (uriPrefixAnn != null)
            uriPrefix = sf.expandAlias(uriPrefixAnn.value());

        createMeta(ed, clazz, uriPrefix);
    }

    private void createView(Class<?> clazz) throws OtmException {
        String name;
        String query = null;

        View view = clazz.getAnnotation(View.class);

        if (view != null) {
            name = (!"".equals(view.name())) ? view.name() : getName(clazz);
            query = view.query();
        } else {
            SubView sv = clazz.getAnnotation(SubView.class);
            name = (!"".equals(sv.name())) ? sv.name() : getName(clazz);
        }

        ViewDefinition vd = new ViewDefinition(name, query);

        createMeta(vd, clazz, null);
    }

    private void createMeta(ClassDefinition def, Class<?> clazz, String uriPrefix) throws OtmException {
        sf.addDefinition(def);

        ClassBinding bin = sf.getClassBinding(def.getName());
        bin.bind(EntityMode.POJO, new ClassBinder(clazz));

        Map<String, PropertyDefFactory> factories = new HashMap<String, PropertyDefFactory>();

        for (Method method : clazz.getDeclaredMethods()) {
            if (!isAnnotated(method))
                continue;

            if (method.getAnnotation(SubClassResolver.class) != null) {
                registerSubClassResolver(def, method);
                continue;
            }

            Property property = Property.toProperty(method);

            if (property == null)
                throw new OtmException("'" + method.toGenericString() + "' is not a valid getter or setter");

            PropertyDefFactory pi = factories.get(property.getName());

            if (pi != null) {
                if (method.equals(pi.property.getReadMethod()) || method.equals(pi.property.getWriteMethod()))
                    continue;

                throw new OtmException("Duplicate property " + property);
            }

            validate(property, def);
            factories.put(property.getName(), new PropertyDefFactory(def, property));
        }

        if (def instanceof EntityDefinition) {
            if (clazz.getGenericSuperclass() instanceof ParameterizedType)
                addGenericsSyntheticProps(def, clazz, (ParameterizedType) clazz.getGenericSuperclass(), factories);

            for (Type t : clazz.getGenericInterfaces())
                if (t instanceof ParameterizedType)
                    addGenericsSyntheticProps(def, clazz, (ParameterizedType) t, factories);

            Map<String, Map<String, String>> supersedes = new HashMap<String, Map<String, String>>();
            buildSupersedes((EntityDefinition) def, supersedes);

            for (String name : supersedes.keySet()) {
                PropertyDefFactory pi = factories.get(name);
                if (pi != null)
                    pi.setSupersedes(supersedes.get(name));
            }
        }

        for (PropertyDefFactory fi : factories.values()) {
            PropertyDefinition d = fi.getDefinition(sf, uriPrefix);
            if (d != null) {
                sf.addDefinition(d);
                bin.addBinderFactory(new PropertyBinderFactory(fi.name, fi.property));
            }

            SearchableDefinition sd = fi.getSearchableDefinition(sf, d instanceof BlobDefinition);
            if (sd != null)
                sf.addDefinition(sd);
        }
    }

    private void buildSupersedes(EntityDefinition def, Map<String, Map<String, String>> supersedes) {
        for (String sup : def.getSuperEntities()) {
            EntityDefinition sdef = (EntityDefinition) sf.getDefinition(sup);

            if (sdef != null)
                buildSupersedes(sdef, supersedes);
        }

        ClassBinding b = sf.getClassBinding(def.getName());

        if (b != null) {
            for (String prop : b.getProperties()) {
                PropertyDefinition pd = (PropertyDefinition) sf.getDefinition(prop);
                if (pd != null)
                    put(supersedes, pd.getLocalName(), null, prop); // overwrite super-class

                SearchableDefinition sd = (SearchableDefinition) sf.getDefinition(SearchableDefinition.NS + prop);
                if (sd != null)
                    put(supersedes, sd.getLocalName(), SearchableDefinition.NS, prop);
            }
        }
    }

    private static void put(Map<String, Map<String, String>> map, String name, String type, String value) {
        Map<String, String> inner = map.get(name);
        if (inner == null)
            map.put(name, inner = new HashMap<String, String>());

        inner.put(type, value);
    }

    private void addGenericsSyntheticProps(ClassDefinition def, Class clazz, ParameterizedType psup,
            Map<String, PropertyDefFactory> factories) throws OtmException {
        for (Method m : ((Class) psup.getRawType()).getMethods()) {
            if (!isAnnotated(m))
                continue;

            Property property = Property.toProperty(m);

            if ((property == null) || factories.containsKey(property.getName()))
                continue;

            property = property.resolveGenericsType(clazz, psup);

            if (property != null)
                factories.put(property.getName(), new PropertyDefFactory(def, property));
        }
    }

    private static boolean isAnnotated(Method method) {
        String ours = Id.class.getPackage().getName();

        for (Annotation a : method.getAnnotations())
            if (a.annotationType().getPackage().getName().equals(ours))
                return true;

        return false;
    }

    private static String getName(Class<?> clazz) {
        String name = clazz.getName();
        Package p = clazz.getPackage();

        if (p != null)
            name = name.substring(p.getName().length() + 1);

        return name;
    }

    private static String getGraph(Class<?> clazz) {
        if (clazz == null)
            return null;

        Entity entity = clazz.getAnnotation(Entity.class);

        if ((entity != null) && !"".equals(entity.graph()))
            return entity.graph();

        return getGraph(clazz.getSuperclass());
    }

    /**
     * Gets the entity name for a class.
     *
     * @param clazz the class to look-up
     *
     * @return name from {@link org.topazproject.otm.annotations.Entity @Entity} annotation or the
     *         default short-name of the clazz.
     */
    public static String getEntityName(Class<?> clazz) {
        if (clazz == null)
            return null;

        Entity entity = clazz.getAnnotation(Entity.class);

        if ((entity != null) && !"".equals(entity.name()))
            return entity.name();

        return getName(clazz);
    }

    private void registerSubClassResolver(ClassDefinition def, final Method method) throws OtmException {
        if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers()))
            throw new OtmException("@SubClassResolver method '" + method + "' is not public and static");

        Method resolve = org.topazproject.otm.SubClassResolver.class.getDeclaredMethods()[0];
        assert resolve.getName().equals("resolve") : "unexpected method found in SubClassResolver: " + resolve;

        if (method.getGenericReturnType() != resolve.getGenericReturnType())
            throw new OtmException(
                    "@SubClassResolver method '" + method + "' does not return " + resolve.getGenericReturnType());

        if (!Arrays.equals(method.getGenericParameterTypes(), resolve.getGenericParameterTypes()))
            throw new OtmException("@SubClassResolver method '" + method + "' has wrong signature - " + "required: "
                    + Arrays.toString(resolve.getGenericParameterTypes()));

        sf.addSubClassResolver(def.getName(), new org.topazproject.otm.SubClassResolver() {
            public ClassMetadata resolve(ClassMetadata superEntity, EntityMode instantiatableIn, SessionFactory sf,
                    Collection<String> typeUris, TripleStore.Result statements) {
                try {
                    return (ClassMetadata) method.invoke(null, superEntity, instantiatableIn, sf, typeUris,
                            statements);
                } catch (Exception e) {
                    if (e instanceof InvocationTargetException) {
                        Throwable t = ((InvocationTargetException) e).getCause();
                        if (t instanceof RuntimeException)
                            throw (RuntimeException) t;
                        if (t instanceof Error)
                            throw (Error) t;
                    }

                    throw new OtmException("Error invoking '" + method + "'", e);
                }
            }
        });
    }

    private static void validate(Property property, ClassDefinition def) throws OtmException {
        Method setter = property.getWriteMethod();
        Method getter = property.getReadMethod();

        if (setter == null)
            throw new OtmException("Missing setter for property " + property);

        if ((getter == null) && !(def instanceof ViewDefinition))
            throw new OtmException("Missing getter for property " + property);

        if (Modifier.isFinal(setter.getModifiers()))
            throw new OtmException("Setter can't be 'final' for " + property);

        if ((getter != null) && Modifier.isFinal(getter.getModifiers()))
            throw new OtmException("Getter can't be 'final' for " + property);
    }

    private static class PropertyDefFactory {
        final ClassDefinition cd;
        final Property property;
        final String name;
        final Map<String, String> supersedes = new HashMap<String, String>();
        Searchable searchable;

        public PropertyDefFactory(ClassDefinition cd, Property property) throws OtmException {
            this.cd = cd;
            this.property = property;
            this.name = cd.getName() + ":" + property.getName();
        }

        public void setSupersedes(Map<String, String> supersedes) {
            this.supersedes.putAll(supersedes);
        }

        public String getName() {
            return name;
        }

        public String toString() {
            return property.toString();
        }

        public PropertyDefinition getDefinition(SessionFactory sf, String uriPrefix) throws OtmException {
            GeneratedValue gv = null;
            Annotation ann = null;
            Class<?> decl = null;
            String ours = Id.class.getPackage().getName();

            for (Method m : new Method[] { property.getWriteMethod(), property.getReadMethod() }) {
                if (m == null)
                    continue;

                for (Annotation a : m.getAnnotations()) {
                    if (a instanceof GeneratedValue) {
                        gv = (GeneratedValue) a;
                    } else if (a instanceof Searchable) {
                        if (searchable != null)
                            throw new OtmException("Duplicate @Searchable found on " + getName());
                        searchable = (Searchable) a;
                    } else if (a.annotationType().getPackage().getName().equals(ours)) {
                        if (ann != null) {
                            if (!ann.getClass().equals(a.getClass())
                                    || !decl.isAssignableFrom(m.getDeclaringClass())
                                    || (decl == m.getDeclaringClass()))
                                throw new OtmException(
                                        "Only one of @Id, @Predicate, @Blob, @Projection, @PredicateMap"
                                                + " or @Embedded can be applied to " + this);
                        }

                        ann = a;
                        decl = m.getDeclaringClass();
                    }
                }
            }

            if (((ann != null) && !(ann instanceof Id))
                    && ((cd instanceof ViewDefinition) ^ (ann instanceof Projection)))
                throw new OtmException("@Projection can-only/must be applied to Views or Sub-Views : " + this);

            if (ann == null)
                return null;

            IdentifierGenerator generator;

            if (gv == null)
                generator = null;
            else if ((ann instanceof Id) || (ann instanceof Predicate) || (ann == null))
                generator = getGenerator(sf, uriPrefix, gv);
            else
                throw new OtmException("@GeneratedValue can only be specified for @Id and @Predicate : " + this);

            if ((ann instanceof Predicate) || (ann == null))
                return getRdfDefinition(sf, uriPrefix, (Predicate) ann, generator);

            if (ann instanceof Projection)
                return getVarDefinition(sf, (Projection) ann);

            if (ann instanceof Id)
                return getIdDefinition(sf, (Id) ann, generator);

            if (ann instanceof Blob)
                return getBlobDefinition(sf, (Blob) ann);

            if (ann instanceof Embedded)
                return getEmbeddedDefinition(sf);

            if (ann instanceof PredicateMap)
                return getPredicateMapDefinition(sf, (PredicateMap) ann);

            throw new OtmException("Unexpected annotation " + ann.annotationType() + " on " + this);
        }

        public IdentifierGenerator getGenerator(SessionFactory sf, String uriPrefix, GeneratedValue gv)
                throws OtmException {
            IdentifierGenerator generator;

            try {
                generator = (IdentifierGenerator) Thread.currentThread().getContextClassLoader()
                        .loadClass(gv.generatorClass()).newInstance();
            } catch (Throwable t) {
                // Between Class.forName() and newInstance() there are a half-dozen possible excps
                throw new OtmException(
                        "Unable to find implementation of '" + gv.generatorClass() + "' generator for " + this, t);
            }

            String pre = sf.expandAlias(gv.uriPrefix());

            if (pre.equals("")) {
                pre = ((uriPrefix == null) || uriPrefix.equals("")) ? Rdf.topaz : uriPrefix;
                // Compute default uriPrefix: Rdf.topaz/clazz/generatorClass#
                pre = Rdf.topaz + property.getContainingClass().getName() + '/' + property.getName() + '#';
            }

            generator.setUriPrefix(pre);

            return generator;
        }

        public RdfDefinition getRdfDefinition(SessionFactory sf, String ns, Predicate rdf,
                IdentifierGenerator generator) throws OtmException {
            Class<?> type = property.getComponentType();
            String ref = ((rdf != null) && !"".equals(rdf.ref())) ? rdf.ref() : null;

            if (ref == null)
                ref = supersedes.get(null);

            String uri = ((rdf != null) && !"".equals(rdf.uri())) ? sf.expandAlias(rdf.uri())
                    : (((ref == null) && (ns != null)) ? (ns + property.getName()) : null);

            if ((uri == null) && (ref == null))
                throw new OtmException("Missing attribute 'uri' in @Predicate for " + this);

            Boolean inverse = getBooleanProperty(((rdf != null) ? rdf.inverse() : null), ref, Boolean.FALSE);
            Boolean notOwned = getBooleanProperty(((rdf != null) ? rdf.notOwned() : null), ref, Boolean.FALSE);
            Boolean owned = (notOwned == null) ? null : (!notOwned);

            String dt = ((rdf != null) && !"".equals(rdf.dataType())) ? sf.expandAlias(rdf.dataType())
                    : ((ref != null) ? null : sf.getSerializerFactory().getDefaultDataType(type));

            String assoc = sf.getSerializerFactory().mustSerialize(type) ? null : getEntityName(type);
            Boolean objectProperty = null;

            if ((rdf == null) || PropType.UNDEFINED.equals(rdf.type())) {
                if (URI.class.isAssignableFrom(type) || URL.class.isAssignableFrom(type)) {
                    if (ref == null)
                        objectProperty = Boolean.TRUE;
                } else
                    objectProperty = assoc != null;
            } else if (PropType.OBJECT.equals(rdf.type())) {
                if (!"".equals(rdf.dataType()))
                    throw new OtmException("Datatype cannot be specified for an object-Property " + this);

                objectProperty = Boolean.TRUE;
            } else if (PropType.DATA.equals(rdf.type())) {
                assoc = null;

                if ((inverse != null) && (inverse == Boolean.TRUE))
                    throw new OtmException("Inverse mapping cannot be specified for a data-property " + this);

                objectProperty = Boolean.FALSE;
                inverse = Boolean.FALSE;
            }

            String graph = ((rdf != null) && !"".equals(rdf.graph())) ? rdf.graph() : null;

            if ((inverse != null) && inverse && (graph == null) && (assoc != null))
                graph = getGraph(type);

            CollectionType mt = (rdf == null) ? CollectionType.UNDEFINED : rdf.collectionType();

            if (mt == CollectionType.UNDEFINED)
                mt = (ref == null) ? CollectionType.PREDICATE : null;

            CascadeType[] ct = (rdf != null) ? rdf.cascade() : new CascadeType[] { CascadeType.undefined };

            if ((ct.length == 1) && (ct[0] == CascadeType.undefined))
                ct = (ref == null) ? new CascadeType[] { CascadeType.peer } : null;

            FetchType ft = (rdf != null) ? rdf.fetch() : FetchType.undefined;

            if (ft == FetchType.undefined)
                ft = (ref == null) ? FetchType.lazy : null;

            return new RdfDefinition(getName(), ref, supersedes.get(null), uri, dt, inverse, graph, mt, owned,
                    generator, ct, ft, assoc, objectProperty);
        }

        private Boolean getBooleanProperty(Predicate.BT raw, String ref, Boolean dflt) {
            if (raw == Predicate.BT.TRUE)
                return Boolean.TRUE;

            if (raw == Predicate.BT.FALSE)
                return Boolean.FALSE;

            if (ref == null)
                return dflt;

            return null;
        }

        public VarDefinition getVarDefinition(SessionFactory sf, Projection proj) throws OtmException {
            Class<?> type = property.getComponentType();
            String var = "".equals(proj.value()) ? property.getName() : proj.value();
            Serializer serializer = sf.getSerializerFactory().getSerializer(type, null);

            if ((serializer == null) && sf.getSerializerFactory().mustSerialize(type))
                throw new OtmException("No serializer found for '" + type + "' for " + this);

            // XXX: shouldn't we parse the query to figure this out?
            String assoc = (serializer == null) ? getEntityName(type) : null;

            return new VarDefinition(getName(), var, proj.fetch(), assoc);
        }

        public IdDefinition getIdDefinition(SessionFactory sf, Id id, IdentifierGenerator generator)
                throws OtmException {
            Class<?> type = property.getComponentType();

            if (!type.equals(String.class) && !type.equals(URI.class) && !type.equals(URL.class))
                throw new OtmException("@Id property '" + this + "' must be a String, URI or URL.");

            return new IdDefinition(getName(), generator);
        }

        public BlobDefinition getBlobDefinition(SessionFactory sf, Blob blob) throws OtmException {
            return new BlobDefinition(getName(), supersedes.get(null), supersedes.get(null));
        }

        public RdfDefinition getPredicateMapDefinition(SessionFactory sf, PredicateMap pmap) throws OtmException {
            String graph = null; // TODO: allow predicate maps from other graphs
            Type type = property.getGenericType();

            if (Map.class.isAssignableFrom(property.getPropertyType()) && (type instanceof ParameterizedType)) {
                ParameterizedType ptype = (ParameterizedType) type;
                Type[] targs = ptype.getActualTypeArguments();

                if ((targs.length == 2) && (targs[0] instanceof Class)
                        && String.class.isAssignableFrom((Class) targs[0])
                        && (targs[1] instanceof ParameterizedType)) {
                    ptype = (ParameterizedType) targs[1];
                    type = ptype.getRawType();

                    if ((type instanceof Class) && (List.class.isAssignableFrom((Class) type))) {
                        targs = ptype.getActualTypeArguments();

                        if ((targs.length == 1) && (targs[0] instanceof Class)
                                && String.class.isAssignableFrom((Class) targs[0]))
                            return new RdfDefinition(getName(), graph);
                    }
                }
            }

            throw new OtmException("@PredicateMap can be applied to a Map<String, List<String>> "
                    + " property only. It cannot be applied to " + this);
        }

        public EmbeddedDefinition getEmbeddedDefinition(SessionFactory sf) throws OtmException {
            boolean simpleType = sf.getSerializerFactory().mustSerialize(property.getComponentType());

            if (property.isArray() || property.isCollection() || simpleType)
                throw new OtmException("@Embedded class property " + this
                        + " can't be an array, collection or a simple data type");

            sf.preload(property.getComponentType());

            return new EmbeddedDefinition(getName(), getEntityName(property.getComponentType()));
        }

        public SearchableDefinition getSearchableDefinition(SessionFactory sf, boolean blob) throws OtmException {
            if (searchable == null && supersedes.get(SearchableDefinition.NS) == null)
                return null;

            if (!blob && !sf.getSerializerFactory().mustSerialize(property.getComponentType()))
                throw new OtmException(
                        "@SearchableDefinition property " + this + " can't be applied to a complex type");

            String ref = null, uri = null, index = null, analyzer = null;
            Boolean tokenize = null;
            Integer boost = null;
            PreProcessor preProcessor = null;

            if (searchable != null) {
                ref = !searchable.ref().equals("") ? searchable.ref() : null;

                uri = !searchable.uri().equals("") ? sf.expandAlias(searchable.uri()) : null;
                index = !searchable.index().equals("") ? searchable.index() : null;
                tokenize = searchable.tokenize(); // FIXME
                boost = (searchable.boost() == Integer.MAX_VALUE) ? null : searchable.boost();

                try {
                    if (searchable.preProcessor() != PreProcessor.class)
                        preProcessor = searchable.preProcessor().newInstance();
                } catch (Throwable t) {
                    throw new OtmException("Unable to instantiate '" + searchable.preProcessor() + "' for " + this,
                            t);
                }
            }

            return new SearchableDefinition(getName(), ref, supersedes.get(SearchableDefinition.NS), uri, index,
                    tokenize, analyzer, boost, preProcessor);
        }
    }
}