org.lilyproject.util.repo.SystemFields.java Source code

Java tutorial

Introduction

Here is the source code for org.lilyproject.util.repo.SystemFields.java

Source

/*
 * Copyright 2012 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.util.repo;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.logging.LogFactory;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.FieldTypeNotFoundException;
import org.lilyproject.repository.api.IdGenerator;
import org.lilyproject.repository.api.Link;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
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;

/**
 * The idea behind SystemFields is to make system properties of records addressable as normal fields,
 * that is, to make them addressable by a QName. This way, both fields and system properties are
 * accessible through a uniform interface. This avoids the need to have two different addressing
 * systems in situations that want to provide access to both.
 *
 * <p>The namespace for all system fields is org.lilyproject.system</p>
 *
 * <p>At the time of this writing, SystemFields is used by the indexer and by conditional
 * record updates.</p>
 *
 * <p>It was not the intention to make the abstraction work the whole way, i.e. Record objects are not
 * decorated to make the system fields appear, nor are they reported as 'changed fields' in the record events, etc.
 * It seemed this could have all sorts of undesirable side-effects.
    
 * <p>For each of the supported system fields, corresponding 'fake' FieldType objects are available.
 * The UUIDs of these field type objects are name-based UUIDs, so they should never collide with those
 * generated by Lily.
 *
 * <p>The FieldType objects returned by this class are not clones, so be careful not to modify them.
 */
public class SystemFields {

    public static final String NS = "org.lilyproject.system";

    private static final SystemField[] fields = new SystemField[] { new SystemField("version", "LONG", false) {
        @Override
        public Object eval(Record record, TypeManager typeManager) {
            // Special about the version field is that it can evaluate to null, while normal fields
            // will never be null. Or should we rather throw a FieldNotFoundException when its null?
            return record.getVersion();
        }
    }, new SystemField("recordType", "STRING", false) {
        @Override
        public Object eval(Record record, TypeManager typeManager) {
            return formatName(record.getRecordTypeName());
        }
    }, new SystemField("recordTypeName", "STRING", false) {
        @Override
        public Object eval(Record record, TypeManager typeManager) {
            return record.getRecordTypeName().getName();
        }
    }, new SystemField("recordTypeNamespace", "STRING", false) {
        @Override
        public Object eval(Record record, TypeManager typeManager) {
            return record.getRecordTypeName().getNamespace();
        }
    }, new SystemField("recordTypeVersion", "LONG", false) {
        @Override
        public Object eval(Record record, TypeManager typeManager) {
            return record.getRecordTypeVersion();
        }
    }, new SystemField("recordTypeWithVersion", "STRING", false) {
        @Override
        public Object eval(Record record, TypeManager typeManager) {
            return formatNameVersion(record.getRecordTypeName(), record.getRecordTypeVersion());
        }
    }, new SystemField("supertypes", "STRING", true) {
        @Override
        public Object eval(Record record, TypeManager typeManager)
                throws RepositoryException, InterruptedException {
            final List<String> result = new NoDupsList<String>();
            forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                @Override
                public void handle(RecordType recordType) {
                    result.add(formatName(recordType.getName()));
                }
            });
            return result;
        }
    }, new SystemField("supertypesWithVersion", "STRING", true) {
        @Override
        public Object eval(Record record, TypeManager typeManager)
                throws RepositoryException, InterruptedException {
            final List<String> result = new NoDupsList<String>();
            forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                @Override
                public void handle(RecordType recordType) {
                    result.add(formatNameVersion(recordType.getName(), recordType.getVersion()));
                }
            });
            return result;
        }
    }, new SystemField("supertypeNames", "STRING", true) {
        @Override
        public Object eval(Record record, TypeManager typeManager)
                throws RepositoryException, InterruptedException {
            final List<String> result = new NoDupsList<String>();
            forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                @Override
                public void handle(RecordType recordType) {
                    result.add(recordType.getName().getName());
                }
            });
            return result;
        }
    }, new SystemField("supertypeNamespaces", "STRING", true) {
        @Override
        public Object eval(Record record, TypeManager typeManager)
                throws RepositoryException, InterruptedException {
            final List<String> result = new NoDupsList<String>();
            forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                @Override
                public void handle(RecordType recordType) {
                    result.add(recordType.getName().getNamespace());
                }
            });
            return result;
        }
    },
            /* The mixin related fields were deprecated in 2.2, they can be removed in 2.4 */
            new SystemField("mixins", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    LogFactory.getLog("lily.deprecation")
                            .warn("System field mixins is deprecated, use supertypes instead.");
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(formatName(recordType.getName()));
                        }
                    });
                    return result;
                }
            }, new SystemField("mixinsWithVersion", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    LogFactory.getLog("lily.deprecation").warn(
                            "System field mixinsWithVersion is deprecated, use supertypesWithVersion instead.");
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(formatNameVersion(recordType.getName(), recordType.getVersion()));
                        }
                    });
                    return result;
                }
            }, new SystemField("mixinNames", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    LogFactory.getLog("lily.deprecation")
                            .warn("System field mixinNames is deprecated, use supertypeNames instead.");
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(recordType.getName().getName());
                        }
                    });
                    return result;
                }
            }, new SystemField("mixinNamespaces", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    LogFactory.getLog("lily.deprecation")
                            .warn("System field mixinNamespaces is deprecated, use supertypeNamespaces instead.");
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, false, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(recordType.getName().getNamespace());
                        }
                    });
                    return result;
                }
            }, new SystemField("recordTypes", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, true, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(formatName(recordType.getName()));
                        }
                    });
                    return result;
                }
            }, new SystemField("recordTypesWithVersion", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, true, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(formatNameVersion(recordType.getName(), recordType.getVersion()));
                        }
                    });
                    return result;
                }
            }, new SystemField("recordTypeNames", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, true, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(recordType.getName().getName());
                        }
                    });
                    return result;
                }
            }, new SystemField("recordTypeNamespaces", "STRING", true) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    final List<String> result = new NoDupsList<String>();
                    forEachSupertype(record, typeManager, true, new SupertypeCallback() {
                        @Override
                        public void handle(RecordType recordType) {
                            result.add(recordType.getName().getNamespace());
                        }
                    });
                    return result;
                }
            }, new SystemField("id", "STRING", false) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    return record.getId();
                }
            }, new SystemField("link", "LINK", false) {
                @Override
                public Object eval(Record record, TypeManager typeManager)
                        throws RepositoryException, InterruptedException {
                    return new Link(record.getId());
                }
            } };

    private static SystemFields INSTANCE;

    private Map<QName, SystemField> fieldsByName;
    private Map<SchemaId, SystemField> fieldsById;

    public SystemFields(Map<QName, SystemField> fieldsByName, Map<SchemaId, SystemField> fieldsById) {
        this.fieldsByName = fieldsByName;
        this.fieldsById = fieldsById;
    }

    public static synchronized SystemFields getInstance(TypeManager typeManager, IdGenerator idGenerator) {

        if (INSTANCE == null) {

            Map<QName, SystemField> fieldsByName = new HashMap<QName, SystemField>();
            Map<SchemaId, SystemField> fieldsById = new HashMap<SchemaId, SystemField>();

            for (SystemField field : fields) {
                String stringId = "{" + NS + "}" + field.name;
                UUID id;
                try {
                    // Normally, the bytes should be prefixed with the UUID of the namespace to which the name
                    // belongs, but for our purpose this is a good enough.
                    id = UUID.nameUUIDFromBytes(stringId.getBytes("UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e); // rare enough
                }
                SchemaId schemaId = idGenerator.getSchemaId(id);

                ValueType valueType;
                try {
                    valueType = field.multiValue ? typeManager.getValueType("LIST<" + field.type + ">")
                            : typeManager.getValueType(field.type);
                } catch (RepositoryException e) {
                    throw new RuntimeException(e); // unlikely to occur
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e); // unlikely to occur
                }

                FieldType fieldType = typeManager.newFieldType(valueType, new QName(NS, field.name),
                        Scope.NON_VERSIONED);
                fieldType.setId(schemaId);

                field.setFieldType(fieldType);

                fieldsByName.put(fieldType.getName(), field);
                fieldsById.put(fieldType.getId(), field);
            }

            INSTANCE = new SystemFields(fieldsByName, fieldsById);
        }

        return INSTANCE;
    }

    public boolean isSystemField(QName name) {
        return fieldsByName.containsKey(name);
    }

    public boolean isSystemField(SchemaId schemaId) {
        return fieldsById.containsKey(schemaId);
    }

    public FieldType get(QName name) throws FieldTypeNotFoundException {
        if (!fieldsByName.containsKey(name)) {
            throw new FieldTypeNotFoundException(name);
        }

        return fieldsByName.get(name).fieldType;
    }

    public FieldType get(SchemaId schemaId) throws FieldTypeNotFoundException {
        if (!fieldsById.containsKey(schemaId)) {
            throw new FieldTypeNotFoundException(schemaId);
        }
        return fieldsById.get(schemaId).fieldType;
    }

    public Object eval(Record record, FieldType fieldType, TypeManager typeManager)
            throws RepositoryException, InterruptedException {
        return fieldsById.get(fieldType.getId()).eval(record, typeManager);
    }

    /**
     * If it is a system field, evaluates it, if not, returns the normal field value from the record.
     * Does not throw a FieldNotFoundException, rather returns null.
     */
    public Object softEval(Record record, QName fieldType, TypeManager typeManager)
            throws RepositoryException, InterruptedException {
        if (isSystemField(fieldType)) {
            return fieldsByName.get(fieldType).eval(record, typeManager);
        } else {
            return record.getFields().get(fieldType);
        }
    }

    public Set<QName> getAll() {
        return fieldsByName.keySet();
    }

    private static abstract class SystemField {
        private String name;
        private String type;
        private boolean multiValue;
        private FieldType fieldType;

        SystemField(String name, String type, boolean multiValue) {
            this.name = name;
            this.type = type;
            this.multiValue = multiValue;
        }

        public void setFieldType(FieldType fieldType) {
            this.fieldType = fieldType;
        }

        public abstract Object eval(Record record, TypeManager typeManager)
                throws RepositoryException, InterruptedException;
    }

    private static String formatName(QName name) {
        return "{" + name.getNamespace() + "}" + name.getName();
    }

    private static String formatNameVersion(QName name, long version) {
        return "{" + name.getNamespace() + "}" + name.getName() + ":" + version;
    }

    private static interface SupertypeCallback {
        void handle(RecordType recordType);
    }

    private static void forEachSupertype(Record record, TypeManager typeManager, boolean includeRecordType,
            SupertypeCallback callback) throws RepositoryException, InterruptedException {

        RecordType recordType = typeManager.getRecordTypeByName(record.getRecordTypeName(),
                record.getRecordTypeVersion());

        if (includeRecordType) {
            callback.handle(recordType);
        }

        for (Map.Entry<SchemaId, Long> supertype : recordType.getSupertypes().entrySet()) {
            RecordType supertypeRt = typeManager.getRecordTypeById(supertype.getKey(), supertype.getValue());
            callback.handle(supertypeRt);
        }
    }

    /**
     * Extends list to avoid that the same item is added twice (only for plain add). For the small lists we
     * have, assumed this would be cheaper than first constructing a set and then converting it to a list.
     */
    private static class NoDupsList<T> extends ArrayList<T> {
        @Override
        public boolean add(T t) {
            if (!this.contains(t)) {
                return super.add(t);
            }
            return false;
        }
    }
}