com.amalto.core.storage.hibernate.FullTextQueryHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.amalto.core.storage.hibernate.FullTextQueryHandler.java

Source

/*
 * Copyright (C) 2006-2016 Talend Inc. - www.talend.com
 *
 * This source code is available under agreement available at
 * %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
 *
 * You should have received a copy of the agreement
 * along with this program; if not, write to Talend SA
 * 9 rue Pages 92150 Suresnes, France
 */

package com.amalto.core.storage.hibernate;

import com.amalto.core.query.user.*;
import com.amalto.core.query.user.metadata.*;
import com.amalto.core.storage.CloseableIterator;
import com.amalto.core.storage.Storage;
import com.amalto.core.storage.StorageResults;
import com.amalto.core.storage.exception.FullTextQueryCompositeKeyException;
import com.amalto.core.storage.record.DataRecord;
import com.amalto.core.storage.record.metadata.UnsupportedDataRecordMetadata;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.*;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.talend.mdm.commmon.metadata.*;

import javax.xml.XMLConstants;
import java.io.IOException;
import java.util.*;

class FullTextQueryHandler extends AbstractQueryHandler {

    private FullTextQuery query;

    private int pageSize;

    private final MappingRepository mappings;

    public FullTextQueryHandler(Storage storage, MappingRepository mappings, StorageClassLoader storageClassLoader,
            Session session, Select select, List<TypedExpression> selectedFields, Set<ResultsCallback> callbacks) {
        super(storage, storageClassLoader, session, select, selectedFields, callbacks);
        this.mappings = mappings;
    }

    @Override
    public StorageResults visit(Select select) {
        // TMDM-4654: Checks if entity has a composite PK.
        Set<ComplexTypeMetadata> compositeKeyTypes = new HashSet<ComplexTypeMetadata>();
        // TMDM-7496: Search should include references to reused types
        Collection<ComplexTypeMetadata> types = new HashSet<ComplexTypeMetadata>(
                select.accept(new SearchTransitiveClosure(storage)));
        for (ComplexTypeMetadata type : types) {
            if (type.getKeyFields().size() > 1) {
                compositeKeyTypes.add(type);
            }
        }
        if (!compositeKeyTypes.isEmpty()) {
            StringBuilder message = new StringBuilder();
            Iterator it = compositeKeyTypes.iterator();
            while (it.hasNext()) {
                ComplexTypeMetadata compositeKeyType = (ComplexTypeMetadata) it.next();
                message.append(compositeKeyType.getName());
                if (it.hasNext()) {
                    message.append(',');
                }
            }
            throw new FullTextQueryCompositeKeyException(message.toString());
        }
        // Removes Joins and joined fields.
        List<Join> joins = select.getJoins();
        if (!joins.isEmpty()) {
            Set<ComplexTypeMetadata> joinedTypes = new HashSet<ComplexTypeMetadata>();
            for (Join join : joins) {
                joinedTypes.add(join.getRightField().getFieldMetadata().getContainingType());
            }
            for (ComplexTypeMetadata joinedType : joinedTypes) {
                types.remove(joinedType);
            }
            List<TypedExpression> filteredFields = new LinkedList<TypedExpression>();
            for (TypedExpression expression : select.getSelectedFields()) {
                if (expression instanceof Field) {
                    FieldMetadata fieldMetadata = ((Field) expression).getFieldMetadata();
                    if (joinedTypes.contains(fieldMetadata.getContainingType())) {
                        TypeMapping mapping = mappings.getMappingFromDatabase(fieldMetadata.getContainingType());
                        filteredFields.add(new Alias(new StringConstant(StringUtils.EMPTY),
                                mapping.getUser(fieldMetadata).getName()));
                    } else {
                        filteredFields.add(expression);
                    }
                } else {
                    filteredFields.add(expression);
                }
            }
            selectedFields.clear();
            selectedFields.addAll(filteredFields);
        }
        // Handle condition
        Condition condition = select.getCondition();
        if (condition == null) {
            throw new IllegalArgumentException("Expected a condition in select clause but got 0.");
        }
        // Create Lucene query (concatenates all sub queries together).
        FullTextSession fullTextSession = Search.getFullTextSession(session);
        Query parsedQuery = select.getCondition().accept(new LuceneQueryGenerator(types));
        // Create Hibernate Search query
        Set<Class> classes = new HashSet<Class>();
        for (ComplexTypeMetadata type : types) {
            String className = ClassCreator.getClassName(type.getName());
            try {
                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
                classes.add(contextClassLoader.loadClass(className));
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("Could not find class '" + className + "'.", e);
            }
        }
        FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(parsedQuery,
                classes.toArray(new Class<?>[classes.size()]));
        // Very important to leave this null (would disable ability to search across different types)
        fullTextQuery.setCriteriaQuery(null);
        fullTextQuery.setSort(Sort.RELEVANCE); // Default sort (if no order by specified).
        query = EntityFinder.wrap(fullTextQuery, (HibernateStorage) storage, session, select.getTypes()); // ensures only MDM entity objects are returned.
        // Order by
        for (OrderBy current : select.getOrderBy()) {
            current.accept(this);
        }
        // Paging
        Paging paging = select.getPaging();
        paging.accept(this);
        pageSize = paging.getLimit();
        boolean hasPaging = pageSize < Integer.MAX_VALUE;
        if (!hasPaging) {
            return createResults(query.scroll(ScrollMode.FORWARD_ONLY));
        } else {
            return createResults(query.list());
        }
    }

    @Override
    public StorageResults visit(Paging paging) {
        query.setFirstResult(paging.getStart());
        query.setFetchSize(AbstractQueryHandler.JDBC_FETCH_SIZE);
        query.setMaxResults(paging.getLimit());
        return null;
    }

    private StorageResults createResults(final List list) {
        CloseableIterator<DataRecord> iterator;
        if (selectedFields.isEmpty()) {
            iterator = new ListIterator(mappings, storageClassLoader, list.iterator(), callbacks);
        } else {
            iterator = new ListIterator(mappings, storageClassLoader, list.iterator(), callbacks) {
                @Override
                public DataRecord next() {
                    final DataRecord next = super.next();
                    final ComplexTypeMetadata explicitProjectionType = new ComplexTypeMetadataImpl(
                            StringUtils.EMPTY, Storage.PROJECTION_TYPE, false);
                    final DataRecord nextRecord = new DataRecord(explicitProjectionType,
                            UnsupportedDataRecordMetadata.INSTANCE);
                    VisitorAdapter<Void> visitor = new VisitorAdapter<Void>() {
                        private String aliasName;

                        @Override
                        public Void visit(Field field) {
                            FieldMetadata fieldMetadata = field.getFieldMetadata();
                            TypeMapping mapping = mappings
                                    .getMappingFromDatabase(fieldMetadata.getContainingType());
                            if (mapping != null && mapping.getUser(fieldMetadata) != null) {
                                fieldMetadata = mapping.getUser(fieldMetadata);
                            }
                            Object value;
                            if (fieldMetadata instanceof ReferenceFieldMetadata) {
                                value = getReferencedId(next, (ReferenceFieldMetadata) fieldMetadata);
                            } else {
                                value = next.get(fieldMetadata);
                            }
                            if (aliasName != null) {
                                SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                        XMLConstants.W3C_XML_SCHEMA_NS_URI, fieldMetadata.getType().getName());
                                fieldMetadata = new SimpleTypeFieldMetadata(explicitProjectionType, false, false,
                                        false, aliasName, fieldType, Collections.<String>emptyList(),
                                        Collections.<String>emptyList(), Collections.<String>emptyList(),
                                        StringUtils.EMPTY);
                                explicitProjectionType.addField(fieldMetadata);
                            } else {
                                explicitProjectionType.addField(fieldMetadata);
                            }
                            nextRecord.set(fieldMetadata, value);
                            return null;
                        }

                        @Override
                        public Void visit(StringConstant constant) {
                            if (aliasName != null) {
                                SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                        XMLConstants.W3C_XML_SCHEMA_NS_URI, Types.STRING);
                                FieldMetadata fieldMetadata = new SimpleTypeFieldMetadata(explicitProjectionType,
                                        false, false, false, aliasName, fieldType, Collections.<String>emptyList(),
                                        Collections.<String>emptyList(), Collections.<String>emptyList(),
                                        StringUtils.EMPTY);
                                explicitProjectionType.addField(fieldMetadata);
                                nextRecord.set(fieldMetadata, constant.getValue());
                            } else {
                                throw new IllegalStateException("Expected an alias for a constant expression.");
                            }
                            return null;
                        }

                        @Override
                        public Void visit(Count count) {
                            if (aliasName != null) {
                                SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                        XMLConstants.W3C_XML_SCHEMA_NS_URI, count.getTypeName());
                                FieldMetadata fieldMetadata = new SimpleTypeFieldMetadata(explicitProjectionType,
                                        false, false, false, aliasName, fieldType, Collections.<String>emptyList(),
                                        Collections.<String>emptyList(), Collections.<String>emptyList(),
                                        StringUtils.EMPTY);
                                explicitProjectionType.addField(fieldMetadata);
                                nextRecord.set(fieldMetadata, list.size());
                            }
                            return null;
                        }

                        @Override
                        public Void visit(Alias alias) {
                            aliasName = alias.getAliasName();
                            {
                                alias.getTypedExpression().accept(this);
                            }
                            aliasName = null;
                            return null;
                        }

                        private Void handleMetadataField(MetadataField field) {
                            SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                    XMLConstants.W3C_XML_SCHEMA_NS_URI, field.getTypeName());
                            String fieldName = aliasName == null ? field.getFieldName() : aliasName;
                            SimpleTypeFieldMetadata aliasField = new SimpleTypeFieldMetadata(explicitProjectionType,
                                    false, false, false, fieldName, fieldType, Collections.<String>emptyList(),
                                    Collections.<String>emptyList(), Collections.<String>emptyList(),
                                    StringUtils.EMPTY);
                            explicitProjectionType.addField(aliasField);
                            nextRecord.set(aliasField, field.getReader().readValue(next));
                            return null;
                        }

                        @Override
                        public Void visit(Timestamp timestamp) {
                            return handleMetadataField(timestamp);
                        }

                        @Override
                        public Void visit(TaskId taskId) {
                            return handleMetadataField(taskId);
                        }

                        @Override
                        public Void visit(StagingStatus stagingStatus) {
                            return handleMetadataField(stagingStatus);
                        }

                        @Override
                        public Void visit(StagingError stagingError) {
                            return handleMetadataField(stagingError);
                        }

                        @Override
                        public Void visit(StagingSource stagingSource) {
                            return handleMetadataField(stagingSource);
                        }

                        @Override
                        public Void visit(StagingBlockKey stagingBlockKey) {
                            return handleMetadataField(stagingBlockKey);
                        }

                        @Override
                        public Void visit(Type type) {
                            FieldMetadata fieldMetadata = type.getField().getFieldMetadata();
                            SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                    XMLConstants.W3C_XML_SCHEMA_NS_URI, Types.STRING);
                            SimpleTypeFieldMetadata aliasField = new SimpleTypeFieldMetadata(explicitProjectionType,
                                    false, false, false, aliasName, fieldType, Collections.<String>emptyList(),
                                    Collections.<String>emptyList(), Collections.<String>emptyList(),
                                    StringUtils.EMPTY);
                            explicitProjectionType.addField(aliasField);
                            DataRecord dataRecord = (DataRecord) next.get(fieldMetadata.getName());
                            if (dataRecord != null) {
                                nextRecord.set(aliasField, dataRecord.getType().getName());
                            } else {
                                nextRecord.set(aliasField, StringUtils.EMPTY);
                            }
                            return null;
                        }
                    };
                    for (TypedExpression selectedField : selectedFields) {
                        selectedField.accept(visitor);
                    }
                    return nextRecord;
                }
            };
        }
        return new FullTextStorageResults(pageSize, list.size(), iterator);
    }

    private StorageResults createResults(ScrollableResults scrollableResults) {
        CloseableIterator<DataRecord> iterator;
        if (selectedFields.isEmpty()) {
            iterator = new ScrollableIterator(mappings, storageClassLoader, scrollableResults, callbacks);
        } else {
            iterator = new ScrollableIterator(mappings, storageClassLoader, scrollableResults, callbacks) {
                @Override
                public DataRecord next() {
                    DataRecord next = super.next();
                    ComplexTypeMetadata explicitProjectionType = new ComplexTypeMetadataImpl(StringUtils.EMPTY,
                            Storage.PROJECTION_TYPE, false);
                    DataRecord nextRecord = new DataRecord(explicitProjectionType,
                            UnsupportedDataRecordMetadata.INSTANCE);
                    for (TypedExpression selectedField : selectedFields) {
                        if (selectedField instanceof Field) {
                            FieldMetadata field = ((Field) selectedField).getFieldMetadata();
                            TypeMapping mapping = mappings.getMappingFromDatabase(field.getContainingType());
                            if (mapping != null && mapping.getUser(field) != null) {
                                field = mapping.getUser(field);
                            }
                            explicitProjectionType.addField(field);
                            if (field instanceof ReferenceFieldMetadata) {
                                nextRecord.set(field, getReferencedId(next, (ReferenceFieldMetadata) field));
                            } else {
                                nextRecord.set(field, next.get(field));
                            }
                        } else if (selectedField instanceof Alias) {
                            SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                    XMLConstants.W3C_XML_SCHEMA_NS_URI, selectedField.getTypeName());
                            Alias alias = (Alias) selectedField;
                            SimpleTypeFieldMetadata newField = new SimpleTypeFieldMetadata(explicitProjectionType,
                                    false, false, false, alias.getAliasName(), fieldType,
                                    Collections.<String>emptyList(), Collections.<String>emptyList(),
                                    Collections.<String>emptyList(), StringUtils.EMPTY);
                            explicitProjectionType.addField(newField);
                            TypedExpression typedExpression = alias.getTypedExpression();
                            if (typedExpression instanceof StringConstant) {
                                nextRecord.set(newField, ((StringConstant) typedExpression).getValue());
                            } else if (typedExpression instanceof Field) {
                                nextRecord.set(newField, next.get(((Field) typedExpression).getFieldMetadata()));
                            } else if (typedExpression instanceof Type) {
                                FieldMetadata fieldMetadata = ((Type) typedExpression).getField()
                                        .getFieldMetadata();
                                nextRecord.set(newField,
                                        ((DataRecord) next.get(fieldMetadata.getName())).getType().getName());
                            } else if (typedExpression instanceof MetadataField) {
                                nextRecord.set(newField,
                                        ((MetadataField) typedExpression).getReader().readValue(next));
                            } else {
                                throw new IllegalArgumentException(
                                        "Aliased expression '" + typedExpression + "' is not supported.");
                            }
                        } else if (selectedField instanceof MetadataField) {
                            SimpleTypeMetadata fieldType = new SimpleTypeMetadata(
                                    XMLConstants.W3C_XML_SCHEMA_NS_URI, selectedField.getTypeName());
                            SimpleTypeFieldMetadata newField = new SimpleTypeFieldMetadata(explicitProjectionType,
                                    false, false, false, ((MetadataField) selectedField).getFieldName(), fieldType,
                                    Collections.<String>emptyList(), Collections.<String>emptyList(),
                                    Collections.<String>emptyList(), StringUtils.EMPTY);
                            explicitProjectionType.addField(newField);
                            nextRecord.set(newField, ((MetadataField) selectedField).getReader().readValue(next));
                        }
                    }
                    explicitProjectionType.freeze();
                    return nextRecord;
                }
            };
        }

        return new FullTextStorageResults(pageSize, query.getResultSize(), iterator);
    }

    private static Object getReferencedId(DataRecord next, ReferenceFieldMetadata field) {
        DataRecord record = (DataRecord) next.get(field);
        if (record != null) {
            Collection<FieldMetadata> keyFields = record.getType().getKeyFields();
            if (keyFields.size() == 1) {
                return record.get(keyFields.iterator().next());
            } else {
                List<Object> compositeKeyValues = new ArrayList<Object>(keyFields.size());
                for (FieldMetadata keyField : keyFields) {
                    compositeKeyValues.add(record.get(keyField));
                }
                return compositeKeyValues.toArray(new Object[keyFields.size()]);
            }
        } else {
            return StringUtils.EMPTY;
        }
    }

    @Override
    public StorageResults visit(OrderBy orderBy) {
        TypedExpression field = orderBy.getExpression();
        if (field instanceof Field) {
            FieldMetadata fieldMetadata = ((Field) field).getFieldMetadata();
            SortField sortField = new SortField(fieldMetadata.getName(), getSortType(fieldMetadata),
                    orderBy.getDirection() == OrderBy.Direction.DESC);
            query.setSort(new Sort(sortField));
            return null;
        } else {
            throw new NotImplementedException("No support for order by for full text search on non-field.");
        }
    }

    private static SortField.Type getSortType(FieldMetadata fieldMetadata) {
        TypeMetadata fieldType = fieldMetadata.getType();
        String type = MetadataUtils.getSuperConcreteType(fieldType).getName();
        if (Types.STRING.equals(type) || Types.ANY_URI.equals(type) || Types.BOOLEAN.equals(type)
                || Types.BASE64_BINARY.equals(type) || Types.QNAME.equals(type) || Types.HEX_BINARY.equals(type)
                || Types.DURATION.equals(type)) {
            return SortField.Type.STRING_VAL; // STRING does not work well for 'long' strings.
        } else if (Types.INT.equals(type) || Types.INTEGER.equals(type) || Types.POSITIVE_INTEGER.equals(type)
                || Types.NON_POSITIVE_INTEGER.equals(type) || Types.NON_NEGATIVE_INTEGER.equals(type)
                || Types.NEGATIVE_INTEGER.equals(type) || Types.UNSIGNED_INT.equals(type)) {
            return SortField.Type.INT;
        } else if (Types.DECIMAL.equals(type) || Types.DOUBLE.equals(type)) {
            return SortField.Type.DOUBLE;
        } else if (Types.DATE.equals(type) || Types.DATETIME.equals(type) || Types.TIME.equals(type)) {
            return SortField.Type.STRING;
        } else if (Types.UNSIGNED_SHORT.equals(type) || Types.SHORT.equals(type)) {
            return SortField.Type.SHORT;
        } else if (Types.UNSIGNED_LONG.equals(type) || Types.LONG.equals(type)) {
            return SortField.Type.LONG;
        } else if (Types.FLOAT.equals(type)) {
            return SortField.Type.FLOAT;
        } else if (Types.BYTE.equals(type) || Types.UNSIGNED_BYTE.equals(type)) {
            return SortField.Type.BYTE;
        } else {
            throw new UnsupportedOperationException("No support for field typed as '" + type + "'");
        }
    }

    private static class FullTextStorageResults implements StorageResults {

        private final int size;

        private final int count;

        private final CloseableIterator<DataRecord> iterator;

        public FullTextStorageResults(int size, int count, CloseableIterator<DataRecord> iterator) {
            this.size = size;
            this.count = count;
            this.iterator = iterator;
        }

        public int getSize() {
            if (size == Integer.MAX_VALUE) {
                return getCount();
            }
            return size;
        }

        public int getCount() {
            return count;
        }

        public void close() {
            try {
                iterator.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Iterator<DataRecord> iterator() {
            return iterator;
        }
    }

    private static class SearchTransitiveClosure extends VisitorAdapter<Collection<? extends ComplexTypeMetadata>> {

        final Storage storage;

        private final Set<ComplexTypeMetadata> closure = new HashSet<ComplexTypeMetadata>();

        public SearchTransitiveClosure(Storage storage) {
            this.storage = storage;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(Range range) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(Select select) {
            closure.addAll(select.getTypes());
            if (select.getCondition() != null) {
                select.getCondition().accept(this);
            }
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(Alias alias) {
            alias.getTypedExpression().accept(this);
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(Field field) {
            closure.add(field.getFieldMetadata().getContainingType());
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(Compare condition) {
            condition.getLeft().accept(this);
            condition.getRight().accept(this);
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(BinaryLogicOperator condition) {
            condition.getLeft().accept(this);
            condition.getRight().accept(this);
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(UnaryLogicOperator condition) {
            condition.getCondition().accept(this);
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(FullText fullText) {
            if (closure != null && closure.size() > 0) {
                for (ComplexTypeMetadata ctm : closure) {
                    closure.addAll(ctm.accept(new ComplexTypeMetadataOptimization(this.storage)));
                }
            }
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(FieldFullText fieldFullText) {
            closure.add(fieldFullText.getField().getFieldMetadata().getContainingType());
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(ConstantCollection collection) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(StringConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(IntegerConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(DateConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(DateTimeConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(BooleanConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(BigDecimalConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(TimeConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(ShortConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(ByteConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(LongConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(DoubleConstant constant) {
            return closure;
        }

        @Override
        public Collection<? extends ComplexTypeMetadata> visit(FloatConstant constant) {
            return closure;
        }
    }

    private static class ComplexTypeMetadataOptimization extends DefaultMetadataVisitor<Set<ComplexTypeMetadata>> {

        private final Storage storage;
        private final Set<ComplexTypeMetadata> complexTypeMetadataSet = new HashSet<ComplexTypeMetadata>();

        public ComplexTypeMetadataOptimization(Storage storage) {
            this.storage = storage;
        }

        @Override
        public Set<ComplexTypeMetadata> visit(ComplexTypeMetadata complexTypeMetadata) {
            if (complexTypeMetadata != null && !complexTypeMetadataSet.contains(complexTypeMetadata)) {
                complexTypeMetadataSet.add(complexTypeMetadata);
                for (FieldMetadata fm : complexTypeMetadata.getFields()) {
                    fm.accept(this);
                }
            }
            return complexTypeMetadataSet;
        }

        @Override
        public Set<ComplexTypeMetadata> visit(ContainedComplexTypeMetadata containedType) {
            if (containedType != null && !complexTypeMetadataSet.contains(containedType)) {
                if (!this.storage.getMetadataRepository().getInstantiableTypes().contains(containedType)) {
                    complexTypeMetadataSet.add(containedType);
                    for (FieldMetadata fm : containedType.getFields()) {
                        fm.accept(this);
                    }
                }
            }
            return complexTypeMetadataSet;
        }

        @Override
        public Set<ComplexTypeMetadata> visit(ReferenceFieldMetadata referenceField) {
            if (referenceField != null && !complexTypeMetadataSet.contains(referenceField.getReferencedType())) {
                if (!this.storage.getMetadataRepository().getInstantiableTypes()
                        .contains(referenceField.getReferencedType())) {
                    complexTypeMetadataSet.add(referenceField.getReferencedType());
                    for (FieldMetadata fm : referenceField.getReferencedType().getFields()) {
                        fm.accept(this);
                    }
                }
            }
            return complexTypeMetadataSet;
        }

        @Override
        public Set<ComplexTypeMetadata> visit(ContainedTypeFieldMetadata containedField) {
            if (containedField != null && !complexTypeMetadataSet.contains(containedField.getContainedType())) {
                if (!this.storage.getMetadataRepository().getInstantiableTypes()
                        .contains(containedField.getContainedType())) {
                    complexTypeMetadataSet.add(containedField.getContainedType());
                    for (FieldMetadata fm : containedField.getContainedType().getFields()) {
                        fm.accept(this);
                    }
                }
            }
            return complexTypeMetadataSet;
        }
    }
}