Package org.lilyproject.repository.fake

Source Code of org.lilyproject.repository.fake.FakeTypeManager

/*
* Copyright 2013 NGDATA nv
*
* 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.fake;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.lilyproject.repository.api.*;
import org.lilyproject.repository.impl.FieldTypeBuilderImpl;
import org.lilyproject.repository.impl.FieldTypeEntryImpl;
import org.lilyproject.repository.impl.FieldTypeImpl;
import org.lilyproject.repository.impl.FieldTypesImpl;
import org.lilyproject.repository.impl.RecordTypeBuilderImpl;
import org.lilyproject.repository.impl.RecordTypeImpl;
import org.lilyproject.repository.impl.id.SchemaIdImpl;
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.repo.VersionTag;

/**
* Dummy type manager that keeps field & record types in memory. No support for caching or snapshots since we don't
* really
* need this during tests.
*/
public class FakeTypeManager implements TypeManager {

    Map<SchemaId, FieldType> fieldTypes = new HashMap<SchemaId, FieldType>();
    Map<SchemaId, RecordType> recordTypes = new HashMap<SchemaId, RecordType>();
    Map<QName, FieldType> fieldTypesByName = new HashMap<QName, FieldType>();
    Map<QName, RecordType> recordTypeByName = new HashMap<QName, RecordType>();
    Map<String, ValueTypeFactory> valueTypeFactories = new HashMap<String, ValueTypeFactory>();
    private IdGenerator idGenerator;

    public FakeTypeManager(IdGenerator idGenerator) {
        this.registerDefaultValueTypes();
        this.idGenerator = idGenerator;

        try {
            FieldType fieldType = newFieldType(getValueType("LONG"), VersionTag.LAST, Scope.NON_VERSIONED);
            createFieldType(fieldType);
        } catch (FieldTypeExistsException e) {
            // ok
        } catch (ConcurrentUpdateTypeException e) {
            // ok, another lily-server is starting up and doing the same thing
        } catch (RepositoryException e) {
            // not going to happen here
            new RuntimeException(e);
        } catch (InterruptedException e) {
            // not going to happen here
            new RuntimeException(e);
        }

    }

    @Override
    public RecordType newRecordType(QName name) throws TypeException {
        return new RecordTypeImpl(null, name);
    }

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

    @Override
    public RecordType getRecordTypeById(SchemaId id, Long version) throws RepositoryException, InterruptedException {
        RecordType type = recordTypes.get(id);
        if (type == null) {
            throw new RecordTypeNotFoundException(id, version);
        }
        return type;
    }

    @Override
    public RecordType getRecordTypeByName(QName name, Long version) throws RepositoryException, InterruptedException {
        if (name == null) {
            return null;
        }
        RecordType type = recordTypeByName.get(name);
        if (type == null) {
            throw new RecordTypeNotFoundException(name, version);
        }
        return type;
    }

    @Override
    public Collection<RecordType> getRecordTypes() throws RepositoryException, InterruptedException {
        return Lists.newArrayList(recordTypes.values());
    }

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

    @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 FieldType newFieldType(SchemaId id, ValueType valueType, QName name, Scope scope) {
        return new FieldTypeImpl(id, valueType, name, scope);
    }

    @Override
    public RecordType createRecordType(RecordType recordType) throws RepositoryException, InterruptedException {
        return createOrUpdateRecordType(recordType);
    }

    @Override
    public RecordType updateRecordType(RecordType recordType) throws RepositoryException, InterruptedException {
        return createOrUpdateRecordType(recordType);
    }

    @Override
    public RecordType createOrUpdateRecordType(RecordType recordType) throws RepositoryException, InterruptedException {
        if (recordType.getId() == null) {
            recordType.setId(new SchemaIdImpl(UUID.randomUUID()));
        }
        Long version = recordType.getVersion();
        if (version == null) {
            version = new Long(0l);
        }
        recordType.setVersion(version + 1l);
        recordTypeByName.put(recordType.getName(), recordType);
        recordTypes.put(recordType.getId(), recordType);
        return recordType;
    }

    @Override
    public FieldType createFieldType(FieldType fieldType) throws RepositoryException, InterruptedException {
        return createOrUpdateFieldType(fieldType);
    }

    @Override
    public FieldType createFieldType(ValueType valueType, QName name, Scope scope)
            throws RepositoryException, InterruptedException {
        FieldType fieldType = newFieldType(valueType, name, scope);
        return createOrUpdateFieldType(fieldType);
    }

    @Override
    public FieldType createFieldType(String valueType, QName name, Scope scope)
            throws RepositoryException, InterruptedException {
        FieldType fieldType = newFieldType(valueType, name, scope);
        return createOrUpdateFieldType(fieldType);
    }

    @Override
    public FieldType updateFieldType(FieldType fieldType) throws RepositoryException, InterruptedException {
        return createOrUpdateFieldType(fieldType);
    }

    @Override
    public FieldType createOrUpdateFieldType(FieldType fieldType) throws RepositoryException, InterruptedException {
        if (fieldType.getId() == null) {
            fieldType.setId(new SchemaIdImpl(UUID.randomUUID()));
        }
        fieldTypesByName.put(fieldType.getName(), fieldType);
        fieldTypes.put(fieldType.getId(), fieldType);
        return fieldType;
    }

    @Override
    public Pair<List<FieldType>, List<RecordType>> getTypesWithoutCache()
            throws RepositoryException, InterruptedException {
        return new Pair<List<FieldType>, List<RecordType>>(getFieldTypesWithoutCache(), getRecordTypesWithoutCache());
    }

    @Override
    public FieldType getFieldTypeById(SchemaId id) throws RepositoryException, InterruptedException {
        FieldType type = fieldTypes.get(id);
        if (type == null) {
            throw new FieldTypeNotFoundException(id);
        }
        return type;
    }

    @Override
    public FieldType getFieldTypeByName(QName name) throws RepositoryException, InterruptedException {
        FieldType type = fieldTypesByName.get(name);
        if (type == null) {
            throw new FieldTypeNotFoundException(name);
        }
        return type;
    }

    @Override
    public Collection<FieldTypeEntry> getFieldTypesForRecordType(RecordType recordType, boolean includeSupertypes)
            throws 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 (Map.Entry<SchemaId, Long> entry : recordType.getSupertypes().entrySet()) {
            collectRecordSupertypes(Pair.create(entry.getKey(), entry.getValue()), recordSuperTypes);
        }

    }

    @Override
    public Collection<FieldType> getFieldTypes() throws RepositoryException, InterruptedException {
        return Lists.newArrayList(fieldTypes.values());
    }

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

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

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

    @Override
    public org.lilyproject.repository.api.FieldTypes getFieldTypesSnapshot() throws InterruptedException {
        return new FieldTypesImpl();
    }

    @Override
    public List<FieldType> getFieldTypesWithoutCache() throws RepositoryException, InterruptedException {
        return Lists.newArrayList(getFieldTypes());
    }

    @Override
    public List<RecordType> getRecordTypesWithoutCache() throws RepositoryException, InterruptedException {
        return Lists.newArrayList(getRecordTypes());
    }

    @Override
    public TypeBucket getTypeBucketWithoutCache(String bucketId) throws RepositoryException, InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void enableSchemaCacheRefresh() throws RepositoryException, InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void disableSchemaCacheRefresh() throws RepositoryException, InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void triggerSchemaCacheRefresh() throws RepositoryException, InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isSchemaCacheRefreshEnabled() throws RepositoryException, InterruptedException {
        return false;
    }

    @Override
    public void close() throws IOException {
    }

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

    @Override
    public Set<SchemaId> findDirectSubtypes(SchemaId schemaId) throws InterruptedException, RepositoryException {
        return findSubTypes(schemaId, 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, RepositoryException {
        collectSubTypes(recordTypeId, result, new ArrayDeque<SchemaId>(), recursive);
    }

    private void collectSubTypes(SchemaId recordTypeId, Set<SchemaId> result, Deque<SchemaId> parents,
            boolean recursive) throws InterruptedException, RepositoryException {
        // 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 = getRecordTypeById(recordTypeId, null).getSupertypes().keySet();
        for (SchemaId subtype : subtypes) {
            if (!parents.contains(subtype)) {
                result.add(subtype);
                if (recursive) {
                    collectSubTypes(subtype, result, parents, recursive);
                }
            } else {
                // Loop detected in type hierarchy
                throw new RepositoryException(
                        "Error while refreshing subtypes of record type " + recordTypeId.toString());
            }
        }
        parents.pop();
    }

    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 Set<QName> findSubtypes(QName qName) throws InterruptedException, RepositoryException {
        return findSubTypes(qName, true);
    }

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

    @Override
    public RecordType updateRecordType(RecordType recordType, boolean b)
            throws RepositoryException, InterruptedException {
        return updateRecordType(recordType, b, new ArrayDeque<SchemaId>());
    }

    public RecordType createOrUpdateRecordType(RecordType recordType, boolean refreshSubtypes)
            throws RepositoryException, InterruptedException {
        if (recordType.getId() != null) {
            return updateRecordType(recordType, refreshSubtypes);
        } else {
            if (recordType.getName() == null) {
                throw new IllegalArgumentException("No id or name specified in supplied record type.");
            }

            boolean exists = this.recordTypeByName.containsKey(recordType.getName());


            if (exists) {
                try {
                    return updateRecordType(recordType, refreshSubtypes);
                } catch (RecordTypeNotFoundException e) {
                    // record type was renamed in the meantime, retry
                    exists = false;
                }
            } else {
                try {
                    return createRecordType(recordType);
                } catch (RecordTypeExistsException e) {
                    // record type was created in the meantime, retry
                    exists = true;
                }
            }
        }
        throw new TypeException("Record type create-or-update failed");
    }

    private RecordType updateRecordType(RecordType recordType, boolean refreshSubtypes, Deque<SchemaId> parents)
            throws RepositoryException, InterruptedException {
        // First update the record type
        RecordType updatedRecordType = updateRecordType(recordType);

        if (!refreshSubtypes) {
            return updatedRecordType;
        }

        parents.push(updatedRecordType.getId());

        try {
            Set<SchemaId> subtypes = findDirectSubtypes(updatedRecordType.getId());

            for (SchemaId subtype : subtypes) {
                if (!parents.contains(subtype)) {
                    RecordType subRecordType = getRecordTypeById(subtype, null);
                    for (Map.Entry<SchemaId, Long> supertype : subRecordType.getSupertypes().entrySet()) {
                        if (supertype.getKey().equals(updatedRecordType.getId())) {
                            if (!supertype.getValue().equals(updatedRecordType.getVersion())) {
                                subRecordType.addSupertype(updatedRecordType.getId(), updatedRecordType.getVersion());
                                // Store the change, and recursively adjust the pointers in this record type's subtypes as well
                                updateRecordType(subRecordType, true, parents);
                            }
                            break;
                        }
                    }
                } else {
                    // Loop detected in type hierarchy, log a warning about this
                    throw new RepositoryException("Loop in the record hierarchy");
                }
            }
        } catch (RepositoryException e) {
            throw new RepositoryException("Error while refreshing subtypes of record type " + recordType.getName(), e);
        }

        parents.pop();

        return updatedRecordType;
    }
}
TOP

Related Classes of org.lilyproject.repository.fake.FakeTypeManager

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.