Package org.lilyproject.repository.impl.valuetype

Source Code of org.lilyproject.repository.impl.valuetype.RecordValueType$RecordValueTypeFactory

/*
* 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.valuetype;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.lilyproject.bytes.api.DataInput;
import org.lilyproject.bytes.api.DataOutput;
import org.lilyproject.bytes.impl.DataOutputImpl;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.FieldTypeEntry;
import org.lilyproject.repository.api.IdentityRecordStack;
import org.lilyproject.repository.api.InvalidRecordException;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
import org.lilyproject.repository.api.RecordException;
import org.lilyproject.repository.api.RecordType;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.Scope;
import org.lilyproject.repository.api.TypeManager;
import org.lilyproject.repository.api.ValueType;
import org.lilyproject.repository.api.ValueTypeFactory;
import org.lilyproject.repository.impl.IdRecordImpl;
import org.lilyproject.repository.impl.RecordImpl;
import org.lilyproject.repository.impl.RecordRvtImpl;
import org.lilyproject.repository.impl.id.SchemaIdImpl;

public class RecordValueType extends AbstractValueType implements ValueType {

    public static final String NAME = "RECORD";
    private String fullName;

    private static final byte ENCODING_VERSION = (byte)1;
    private static final byte UNDEFINED = (byte)0;
    private static final byte DEFINED = (byte)1;

    private final TypeManager typeManager;
    private QName valueTypeRecordTypeName = null;

    public RecordValueType(TypeManager typeManager, String recordTypeName) throws IllegalArgumentException,
            RepositoryException, InterruptedException {
        this.typeManager = typeManager;
        if (recordTypeName != null) {
            this.valueTypeRecordTypeName = QName.fromString(recordTypeName);
            this.fullName = NAME + "<" + recordTypeName + ">";
        } else {
            this.fullName = NAME;
        }
    }

    @Override
    public String getBaseName() {
        return NAME;
    }

    @Override
    public String getName() {
        return fullName;
    }

    @Override
    public ValueType getDeepestValueType() {
        return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Record read(byte[] data) throws RepositoryException, InterruptedException {
        return new RecordRvtImpl(data, this);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Record read(DataInput dataInput) throws RepositoryException, InterruptedException {
        Record record = new RecordImpl();
        dataInput.readByte(); // Ignore, there is currently only one encoding : 1
        int length = dataInput.readVInt();
        byte[] recordTypeId = dataInput.readBytes(length);
        Long recordTypeVersion = dataInput.readLong();
        RecordType recordType = typeManager.getRecordTypeById(new SchemaIdImpl(recordTypeId), recordTypeVersion);
        record.setRecordType(recordType.getName(), recordTypeVersion);

        Map<SchemaId, QName> idToQNameMapping = new HashMap<SchemaId, QName>();
        List<FieldType> fieldTypes = getSortedFieldTypes(recordType);
        for (FieldType fieldType : fieldTypes) {
            byte readByte = dataInput.readByte();
            if (DEFINED == readByte) {
                Object value = fieldType.getValueType().read(dataInput);
                record.setField(fieldType.getName(), value);
                idToQNameMapping.put(fieldType.getId(), fieldType.getName());
            }
        }

        Map<Scope, SchemaId> recordTypeIds = new EnumMap<Scope, SchemaId>(Scope.class);
        recordTypeIds.put(Scope.NON_VERSIONED, recordType.getId());

        return new IdRecordImpl(record, idToQNameMapping, recordTypeIds);
    }

    @Override
    public byte[] toBytes(Object value, IdentityRecordStack parentRecords) throws RepositoryException,
            InterruptedException {
        if (value instanceof RecordRvtImpl) {
            byte[] bytes = ((RecordRvtImpl)value).getBytes();
            if (bytes != null) {
                return bytes;
            }
        }
        DataOutput dataOutput = new DataOutputImpl();
        encodeData(value, dataOutput, parentRecords);
        return dataOutput.toByteArray();
    }

    @Override
    public void write(Object value, DataOutput dataOutput, IdentityRecordStack parentRecords)
            throws RepositoryException, InterruptedException {
        if (value instanceof RecordRvtImpl) {
            byte[] bytes = ((RecordRvtImpl)value).getBytes();
            if (bytes != null) {
                dataOutput.writeBytes(bytes);
                return;
            }
        }
        encodeData(value, dataOutput, parentRecords);
    }

    private void encodeData(Object value, DataOutput dataOutput, IdentityRecordStack parentRecords)
            throws RepositoryException, InterruptedException {
        Record record = (Record)value;
        if (parentRecords.contains(record)) {
            throw new RecordException("A record may not be nested in itself: " + record.getId());
        }


        RecordType recordType;
        QName recordRecordTypeName = record.getRecordTypeName();
        if (recordRecordTypeName != null) {
            if (valueTypeRecordTypeName != null) {
                // Validate the same record type is being used
                // 20130314: temporarily disabled this, see LILY-1279
//                if (!valueTypeRecordTypeName.equals(recordRecordTypeName)) {
//                    throw new RecordException("The record's Record Type '" + recordRecordTypeName +
//                            "' does not match the record value type's record type '" + valueTypeRecordTypeName + "'");
//                }
            }
            recordType = typeManager.getRecordTypeByName(recordRecordTypeName, null);
        } else if (valueTypeRecordTypeName != null) {
                recordType = typeManager.getRecordTypeByName(valueTypeRecordTypeName, null);
        } else {
            throw new RecordException("The record '" + record + "' should specify a record type");
        }

        // Get and sort the field type entries that should be in the record
        List<FieldType> fieldTypes = getSortedFieldTypes(recordType);

        Map<QName, Object> recordFields = record.getFields();
        List<QName> expectedFields = new ArrayList<QName>();

        // Write the record type information
        // Encoding:
        // - encoding version : byte (1)
        // - nr of bytes in recordtype id : vInt
        // - recordtype id : bytes
        // - recordtype version : long
        dataOutput.writeByte(ENCODING_VERSION);
        byte[] recordIdBytes = recordType.getId().getBytes();
        dataOutput.writeVInt(recordIdBytes.length);
        dataOutput.writeBytes(recordIdBytes);
        dataOutput.writeLong(recordType.getVersion());

        // Write the content of the fields
        // Encoding: for each field :
        // - if not present in the record : undefined marker : byte (0)
        // - if present in the record : defined marker : byte (1)
        //      - fieldValue : bytes
        for (FieldType fieldType : fieldTypes) {
            QName name = fieldType.getName();
            expectedFields.add(name);
            Object fieldValue = recordFields.get(name);
            if (fieldValue == null) {
                dataOutput.writeByte(UNDEFINED);
            } else {
                dataOutput.writeByte(DEFINED);
                parentRecords.push(record);
                fieldType.getValueType().write(fieldValue, dataOutput, parentRecords);
                parentRecords.pop();
            }
        }

        // Check if the record does contain fields that are not defined in the record type
        if (!expectedFields.containsAll(recordFields.keySet())) {
            throw new InvalidRecordException("Record contains fields not part of the record type '" +
                    recordType.getName() + "'", record.getId());
        }
    }

    private List<FieldType> getSortedFieldTypes(RecordType recordType) throws RepositoryException,
            InterruptedException {
        Collection<FieldTypeEntry> fieldTypeEntries = getFieldTypeEntries(recordType);
        List<FieldType> fieldTypes = new ArrayList<FieldType>();
        for (FieldTypeEntry fieldTypeEntry : fieldTypeEntries) {
            fieldTypes.add(typeManager.getFieldTypeById(fieldTypeEntry.getFieldTypeId()));
        }
        return fieldTypes;
    }

    private Collection<FieldTypeEntry> getFieldTypeEntries(RecordType recordType)
            throws RepositoryException, InterruptedException {

        // Wrap the list as an array list since we don't know if the collection will actually support the .addAll() methodq
        Collection<FieldTypeEntry> fieldTypeEntries = new ArrayList<FieldTypeEntry>(recordType.getFieldTypeEntries());
        Map<SchemaId, Long> supertypes = recordType.getSupertypes();
        for (Entry<SchemaId, Long> supertypeEntry: supertypes.entrySet()) {
            RecordType supertypeRecordType = typeManager.getRecordTypeById(supertypeEntry.getKey(), supertypeEntry.getValue());
            fieldTypeEntries.addAll(getFieldTypeEntries(supertypeRecordType));
        }
        return fieldTypeEntries;
    }

    @Override
    public Class getType() {
        return Record.class;
    }

    @Override
    public Comparator getComparator() {
        return null;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + fullName.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        return fullName.equals(((RecordValueType) obj).fullName);
    }

    //
    // Factory
    //
    public static ValueTypeFactory factory(TypeManager typeManager) {
        return new RecordValueTypeFactory(typeManager);
    }

    public static class RecordValueTypeFactory implements ValueTypeFactory {
        private TypeManager typeManager;

        public RecordValueTypeFactory(TypeManager typeManager) {
            this.typeManager = typeManager;
        }

        @Override
        public ValueType getValueType(String recordName) throws IllegalArgumentException, RepositoryException,
                InterruptedException {
            return new RecordValueType(typeManager, recordName);
        }
    }
}
TOP

Related Classes of org.lilyproject.repository.impl.valuetype.RecordValueType$RecordValueTypeFactory

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.