Package org.lilyproject.repository.impl

Source Code of org.lilyproject.repository.impl.AbstractTypeManager

/*
* Copyright 2010 Outerthought bvba
*
* 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.lilyproject.repository.impl;

import com.google.common.collect.Maps;
import org.apache.commons.logging.Log;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.FieldTypeBuilder;
import org.lilyproject.repository.api.FieldTypeEntry;
import org.lilyproject.repository.api.FieldTypes;
import org.lilyproject.repository.api.IdGenerator;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.RecordType;
import org.lilyproject.repository.api.RecordTypeBuilder;
import org.lilyproject.repository.api.RecordTypeNotFoundException;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.Scope;
import org.lilyproject.repository.api.TypeException;
import org.lilyproject.repository.api.TypeManager;
import org.lilyproject.repository.api.ValueType;
import org.lilyproject.repository.api.ValueTypeFactory;
import org.lilyproject.repository.impl.valuetype.BlobValueType;
import org.lilyproject.repository.impl.valuetype.BooleanValueType;
import org.lilyproject.repository.impl.valuetype.ByteArrayValueType;
import org.lilyproject.repository.impl.valuetype.DateTimeValueType;
import org.lilyproject.repository.impl.valuetype.DateValueType;
import org.lilyproject.repository.impl.valuetype.DecimalValueType;
import org.lilyproject.repository.impl.valuetype.DoubleValueType;
import org.lilyproject.repository.impl.valuetype.IntegerValueType;
import org.lilyproject.repository.impl.valuetype.LinkValueType;
import org.lilyproject.repository.impl.valuetype.ListValueType;
import org.lilyproject.repository.impl.valuetype.LongValueType;
import org.lilyproject.repository.impl.valuetype.PathValueType;
import org.lilyproject.repository.impl.valuetype.RecordValueType;
import org.lilyproject.repository.impl.valuetype.StringValueType;
import org.lilyproject.repository.impl.valuetype.UriValueType;
import org.lilyproject.util.ArgumentValidator;
import org.lilyproject.util.Pair;
import org.lilyproject.util.zookeeper.ZooKeeperItf;

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

public abstract class AbstractTypeManager implements TypeManager {
    protected Log log;

    protected Map<String, ValueTypeFactory> valueTypeFactories = new HashMap<String, ValueTypeFactory>();
    protected IdGenerator idGenerator;

    protected ZooKeeperItf zooKeeper;

    protected SchemaCache schemaCache;

    public AbstractTypeManager(ZooKeeperItf zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    @Override
    public FieldTypes getFieldTypesSnapshot() throws InterruptedException {
        return schemaCache.getFieldTypesSnapshot();
    }

    @Override
    abstract public List<FieldType> getFieldTypesWithoutCache() throws RepositoryException, InterruptedException;

    @Override
    abstract public List<RecordType> getRecordTypesWithoutCache() throws RepositoryException, InterruptedException;

    protected void updateFieldTypeCache(FieldType fieldType) throws TypeException, InterruptedException {
        schemaCache.updateFieldType(fieldType);
    }

    protected void updateRecordTypeCache(RecordType recordType) throws TypeException, InterruptedException {
        schemaCache.updateRecordType(recordType);
    }

    @Override
    public Collection<RecordType> getRecordTypes() throws InterruptedException {
        return schemaCache.getRecordTypes();
    }

    @Override
    public List<FieldType> getFieldTypes() throws TypeException, InterruptedException {
        return schemaCache.getFieldTypes();
    }

    protected RecordType getRecordTypeFromCache(QName name, Long version) throws InterruptedException {
        return schemaCache.getRecordType(name, version);
    }

    protected RecordType getRecordTypeFromCache(SchemaId id, Long version) {
        return schemaCache.getRecordType(id, version);
    }

    @Override
    public RecordType getRecordTypeById(SchemaId id, Long version)
            throws RecordTypeNotFoundException, TypeException, RepositoryException, InterruptedException {
        ArgumentValidator.notNull(id, "id");
        RecordType recordType = getRecordTypeFromCache(id, version);
        if (recordType == null) {
            throw new RecordTypeNotFoundException(id, version);
        }
        return recordType.clone();
    }

    @Override
    public RecordType getRecordTypeByName(QName name, Long version)
            throws RecordTypeNotFoundException, TypeException, RepositoryException, InterruptedException {
        ArgumentValidator.notNull(name, "name");
        RecordType recordType = getRecordTypeFromCache(name, version);
        if (recordType == null) {
            throw new RecordTypeNotFoundException(name, version);
        }
        return recordType.clone();
    }

    @Override
    public Set<QName> findSubtypes(QName recordTypeName) throws InterruptedException, RepositoryException {
        return findSubTypes(recordTypeName, true);
    }

    @Override
    public Set<QName> findDirectSubtypes(QName recordTypeName) throws InterruptedException, RepositoryException {
        return findSubTypes(recordTypeName, false);
    }

    private Set<QName> findSubTypes(QName recordTypeName, boolean recursive)
            throws InterruptedException, RepositoryException {
        ArgumentValidator.notNull(recordTypeName, "recordTypeName");

        RecordType recordType = getRecordTypeByName(recordTypeName, null);
        Set<SchemaId> result = new HashSet<SchemaId>();
        collectSubTypes(recordType.getId(), result, recursive);

        // Translate schema id's to QName's
        Set<QName> names = new HashSet<QName>();
        for (SchemaId id : result) {
            try {
                names.add(getRecordTypeById(id, null).getName());
            } catch (RecordTypeNotFoundException e) {
                // skip, this should only occur in border cases, i.e. the schema is being modified while
                // this method is called
            }
        }

        return names;
    }

    @Override
    public Collection<FieldTypeEntry> getFieldTypesForRecordType(RecordType recordType, boolean includeSupertypes)
            throws RecordTypeNotFoundException, TypeException, RepositoryException, InterruptedException {
        if (!includeSupertypes) {
            return recordType.getFieldTypeEntries();
        } else {
            // Pairs of record type id and version
            Map<Pair<SchemaId, Long>, RecordType> recordSupertypeMap = Maps.newHashMap();
            collectRecordSupertypes(Pair.create(recordType.getId(), recordType.getVersion()), recordSupertypeMap);

            // We use a map of SchemaId to FieldTypeEntry so that we can let mandatory field type entries
            // for the same field type override non-mandatory versions
            Map<SchemaId, FieldTypeEntry> fieldTypeMap = Maps.newHashMap();

            for (Pair<SchemaId, Long> recordSuperTypePair : recordSupertypeMap.keySet()) {
                RecordType superRecordType = recordSupertypeMap.get(recordSuperTypePair);
                for (FieldTypeEntry fieldTypeEntry : superRecordType.getFieldTypeEntries()) {
                    SchemaId fieldTypeId = fieldTypeEntry.getFieldTypeId();
                    if (fieldTypeMap.containsKey(fieldTypeId)) {
                        // Only overwrite an existing entry if we have one that is mandatory
                        if (fieldTypeEntry.isMandatory()) {
                            fieldTypeMap.put(fieldTypeId, fieldTypeEntry);
                        }
                    } else {
                        fieldTypeMap.put(fieldTypeId, fieldTypeEntry);
                    }
                }
            }
            return fieldTypeMap.values();
        }
    }


    private void collectRecordSupertypes(Pair<SchemaId, Long> recordTypeAndVersion,
            Map<Pair<SchemaId, Long>, RecordType> recordSuperTypes)
            throws RecordTypeNotFoundException, TypeException, RepositoryException, InterruptedException {
        if (recordSuperTypes.containsKey(recordTypeAndVersion)) {
            return;
        }
        RecordType recordType = getRecordTypeById(recordTypeAndVersion.getV1(), recordTypeAndVersion.getV2());
        recordSuperTypes.put(recordTypeAndVersion, recordType);
        for (Entry<SchemaId, Long> entry : recordType.getSupertypes().entrySet()) {
            collectRecordSupertypes(Pair.create(entry.getKey(), entry.getValue()), recordSuperTypes);
        }

    }

    @Override
    public Set<SchemaId> findSubtypes(SchemaId recordTypeId) throws InterruptedException, RepositoryException {
        return findSubTypes(recordTypeId, true);
    }

    @Override
    public Set<SchemaId> findDirectSubtypes(SchemaId recordTypeId) throws InterruptedException, RepositoryException {
        return findSubTypes(recordTypeId, false);
    }

    private Set<SchemaId> findSubTypes(SchemaId recordTypeId, boolean recursive)
            throws InterruptedException, RepositoryException {
        ArgumentValidator.notNull(recordTypeId, "recordTypeId");

        // This is to validate the requested ID exists
        getRecordTypeById(recordTypeId, null);

        Set<SchemaId> result = new HashSet<SchemaId>();
        collectSubTypes(recordTypeId, result, recursive);

        return result;
    }

    private void collectSubTypes(SchemaId recordTypeId, Set<SchemaId> result, boolean recursive)
            throws InterruptedException {
        collectSubTypes(recordTypeId, result, new ArrayDeque<SchemaId>(), recursive);
    }

    private void collectSubTypes(SchemaId recordTypeId, Set<SchemaId> result, Deque<SchemaId> parents,
            boolean recursive) throws InterruptedException {
        // the parent-stack is to protect against endless loops in the type hierarchy. If a type is a subtype
        // of itself, it will not be included in the result. Thus if record type A extends (directly or indirectly)
        // from A, and we search the subtypes of A, then the resulting set will not include A.
        parents.push(recordTypeId);
        Set<SchemaId> subtypes = schemaCache.findDirectSubTypes(recordTypeId);
        for (SchemaId subtype : subtypes) {
            if (!parents.contains(subtype)) {
                result.add(subtype);
                if (recursive) {
                    collectSubTypes(subtype, result, parents, recursive);
                }
            } else {
                // Loop detected in type hierarchy, log a warning about this
                log.warn(formatSupertypeLoopError(subtype, parents));
            }
        }
        parents.pop();
    }

    protected String formatSupertypeLoopError(SchemaId subtype, Deque<SchemaId> parents) {
        // Loop detected in type hierarchy, log a warning about this
        List<SchemaId> parentsList = new ArrayList<SchemaId>(parents);
        Collections.reverse(parentsList);

        // find the place where the current childType occurred (we don't want to log any parents higher
        // up, doesn't add any value for the user)
        int pos = parentsList.indexOf(subtype);

        StringBuilder msg = new StringBuilder();
        for (int i = pos; i < parentsList.size(); i++) {
            if (msg.length() > 0) {
                msg.append(" <- ");
            }
            msg.append(getNameSafe(parentsList.get(i)));
        }
        msg.append(" <- ");
        msg.append(getNameSafe(subtype));

        return "Loop in record type inheritance: " + msg;
    }

    /**
     * Tries to look up the name of the record type, but returns the id if it fails.
     */
    private String getNameSafe(SchemaId schemaId) {
        try {
            return getRecordTypeById(schemaId, null).getName().toString();
        } catch (Exception e) {
            return schemaId.toString();
        }
    }

    @Override
    public FieldType getFieldTypeById(SchemaId id) throws TypeException, InterruptedException {
        return schemaCache.getFieldType(id);
    }

    @Override
    public FieldType getFieldTypeByName(QName name) throws InterruptedException, TypeException {
        return schemaCache.getFieldType(name);
    }

    //
    // Object creation methods
    //
    @Override
    public RecordType newRecordType(QName name) {
        return new RecordTypeImpl(null, name);
    }

    @Override
    public RecordType newRecordType(SchemaId recordTypeId, QName name) {
        return new RecordTypeImpl(recordTypeId, name);
    }

    @Override
    public FieldType newFieldType(ValueType valueType, QName name, Scope scope) {
        return newFieldType(null, valueType, name, scope);
    }

    @Override
    public FieldType newFieldType(String valueType, QName name, Scope scope) throws RepositoryException,
            InterruptedException {
        return newFieldType(null, getValueType(valueType), name, scope);
    }

    @Override
    public FieldTypeEntry newFieldTypeEntry(SchemaId fieldTypeId, boolean mandatory) {
        ArgumentValidator.notNull(fieldTypeId, "fieldTypeId");
        ArgumentValidator.notNull(mandatory, "mandatory");
        return new FieldTypeEntryImpl(fieldTypeId, mandatory);
    }


    @Override
    public FieldType newFieldType(SchemaId id, ValueType valueType, QName name, Scope scope) {
        return new FieldTypeImpl(id, valueType, name, scope);
    }

    @Override
    public RecordTypeBuilder recordTypeBuilder() throws TypeException {
        return new RecordTypeBuilderImpl(this);
    }

    @Override
    public FieldTypeBuilder fieldTypeBuilder() throws TypeException {
        return new FieldTypeBuilderImpl(this);
    }

    //
    // Value types
    //
    @Override
    public void registerValueType(String valueTypeName, ValueTypeFactory valueTypeFactory) {
        valueTypeFactories.put(valueTypeName, valueTypeFactory);
    }

    @Override
    public ValueType getValueType(String valueTypeSpec) throws RepositoryException, InterruptedException {
        ValueType valueType;

        int indexOfParams = valueTypeSpec.indexOf("<");
        if (indexOfParams == -1) {
            ValueTypeFactory valueTypeFactory = valueTypeFactories.get(valueTypeSpec);
            if (valueTypeFactory == null) {
                throw new TypeException("Unkown value type: " + valueTypeSpec);
            }
            valueType = valueTypeFactory.getValueType(null);
        } else {
            if (!valueTypeSpec.endsWith(">")) {
                throw new IllegalArgumentException("Invalid value type string, no closing angle bracket: '" +
                        valueTypeSpec + "'");
            }

            String arg = valueTypeSpec.substring(indexOfParams + 1, valueTypeSpec.length() - 1);

            if (arg.length() == 0) {
                throw new IllegalArgumentException("Invalid value type string, type arg is zero length: '" +
                        valueTypeSpec + "'");
            }

            ValueTypeFactory valueTypeFactory = valueTypeFactories.get(valueTypeSpec.substring(0, indexOfParams));
            if (valueTypeFactory == null) {
                throw new TypeException("Unkown value type: " + valueTypeSpec);
            }
            valueType = valueTypeFactory.getValueType(arg);
        }

        return valueType;
    }

    // TODO get this from some configuration file
    protected void registerDefaultValueTypes() {
        //
        // Important:
        //
        // When adding a type below, please update the list of built-in
        // types in the javadoc of the method TypeManager.getValueType.
        //

        // TODO or rather use factories?
        registerValueType(StringValueType.NAME, StringValueType.factory());
        registerValueType(IntegerValueType.NAME, IntegerValueType.factory());
        registerValueType(LongValueType.NAME, LongValueType.factory());
        registerValueType(DoubleValueType.NAME, DoubleValueType.factory());
        registerValueType(DecimalValueType.NAME, DecimalValueType.factory());
        registerValueType(BooleanValueType.NAME, BooleanValueType.factory());
        registerValueType(DateValueType.NAME, DateValueType.factory());
        registerValueType(DateTimeValueType.NAME, DateTimeValueType.factory());
        registerValueType(LinkValueType.NAME, LinkValueType.factory(idGenerator, this));
        registerValueType(BlobValueType.NAME, BlobValueType.factory());
        registerValueType(UriValueType.NAME, UriValueType.factory());
        registerValueType(ListValueType.NAME, ListValueType.factory(this));
        registerValueType(PathValueType.NAME, PathValueType.factory(this));
        registerValueType(RecordValueType.NAME, RecordValueType.factory(this));
        registerValueType(ByteArrayValueType.NAME, ByteArrayValueType.factory());
    }
}
TOP

Related Classes of org.lilyproject.repository.impl.AbstractTypeManager

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.