org.nuxeo.ecm.directory.DirectoryEntryResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.directory.DirectoryEntryResolver.java

Source

/*
 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * 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.
 *
 * Contributors:
 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
 */

package org.nuxeo.ecm.directory;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver;
import org.nuxeo.ecm.directory.api.DirectoryEntry;
import org.nuxeo.ecm.directory.api.DirectoryService;
import org.nuxeo.runtime.api.Framework;

/**
 * This {@link ObjectResolver} allows to manage integrity for fields containing references to directory's entry.
 * <p>
 * References contains the directory entry id.
 * </p>
 * <p>
 * To use it, put the following code in your schema XSD (don't forget the directory name):
 * </p>
 *
 * <pre>
 * {@code
 * <xs:element name="carBrand">
 *   <xs:simpleType>
 *     <xs:restriction base="xs:string" ref:resolver="directoryResolver" ref:directory="carBrandsDirectory" />
 *   </xs:simpleType>
 * </xs:element>
 * </pre>
 * <p>
 * For hierarchical directories, which entries reference other entries. You can manage a specific reference containing
 * the full entry path. You have to specify the parent field and the separator used to encode the reference.
 * </p>
 *
 * <pre>
 * {@code
 * <xs:element name="coverage">
 *   <xs:simpleType>
 *     <xs:restriction base="xs:string" ref:resolver="directoryResolver" ref:directory="l10ncoverage" ref:parentField="parent" ref:separator="/" />
 *   </xs:simpleType>
 * </xs:element>
 * </pre>
 * <p>
 * It's not necessary to define parentField and separator for directory using schema ending by xvocabulary. The feature
 * is automatically enable.
 * </p>
 *
 * @since 7.1
 */
public class DirectoryEntryResolver implements ObjectResolver {

    private static final long serialVersionUID = 1L;

    public static final String NAME = "directoryResolver";

    public static final String PARAM_DIRECTORY = "directory";

    public static final String PARAM_PARENT_FIELD = "parentField";

    public static final String PARAM_SEPARATOR = "separator";

    private String idField;

    private String schema;

    private Map<String, Serializable> parameters;

    private boolean hierarchical = false;

    private String parentField = null;

    private String separator = null;

    private List<Class<?>> managedClasses = null;

    private String directoryName;

    /**
     * the directory is transient - it's refetched on read object - see {@link #readObject(java.io.ObjectInputStream)}
     */
    private transient Directory directory;

    private transient DirectoryService directoryService;

    @Override
    public void configure(Map<String, String> parameters) throws IllegalArgumentException, IllegalStateException {
        if (this.parameters != null) {
            throw new IllegalStateException("cannot change configuration, may be already in use somewhere");
        }
        directoryName = parameters.get(PARAM_DIRECTORY);
        if (directoryName != null) {
            directoryName = directoryName.trim();
        }
        if (directoryName == null || directoryName.isEmpty()) {
            throw new IllegalArgumentException("missing directory parameter. A directory name is necessary");
        }
        fetchDirectory();
        idField = directory.getIdField();
        schema = directory.getSchema();
        if (schema.endsWith("xvocabulary")) {
            hierarchical = true;
            parentField = "parent";
            separator = "/";
        }
        String parentFieldParam = StringUtils.trim(parameters.get(PARAM_PARENT_FIELD));
        String separatorParam = StringUtils.trim(parameters.get(PARAM_SEPARATOR));
        if (!StringUtils.isBlank(parentFieldParam) && !StringUtils.isBlank(separatorParam)) {
            hierarchical = true;
            parentField = parentFieldParam;
            separator = separatorParam;
        }
        this.parameters = new HashMap<String, Serializable>();
        this.parameters.put(PARAM_DIRECTORY, directory.getName());
    }

    @Override
    public List<Class<?>> getManagedClasses() {
        if (managedClasses == null) {
            managedClasses = new ArrayList<Class<?>>();
            managedClasses.add(DirectoryEntry.class);
        }
        return managedClasses;
    }

    private void fetchDirectory() {
        directory = getDirectoryService().getDirectory(directoryName);
        if (directory == null) {
            throw new IllegalArgumentException(String.format("the directory \"%s\" was not found", directoryName));
        }
    }

    public DirectoryService getDirectoryService() {
        if (directoryService == null) {
            directoryService = Framework.getService(DirectoryService.class);
        }
        return directoryService;
    }

    public Directory getDirectory() {
        return directory;
    }

    public void setDirectory(Directory directory) {
        this.directory = directory;
    }

    @Override
    public String getName() {
        checkConfig();
        return NAME;
    }

    @Override
    public Map<String, Serializable> getParameters() {
        checkConfig();
        return Collections.unmodifiableMap(parameters);
    }

    @Override
    public boolean validate(Object value) throws IllegalStateException {
        checkConfig();
        return fetch(value) != null;
    }

    @Override
    public Object fetch(Object value) throws IllegalStateException {
        checkConfig();
        if (value != null && value instanceof String) {
            String id = (String) value;
            if (hierarchical) {
                String[] ids = StringUtils.split(id, separator);
                if (ids.length > 0) {
                    id = ids[ids.length - 1];
                } else {
                    return null;
                }
            }
            try (Session session = directory.getSession()) {
                DocumentModel doc = session.getEntry(id);
                if (doc != null) {
                    return new DirectoryEntry(directory.getName(), doc);
                }
                return null;
            }
        }
        return null;
    }

    @Override
    public <T> T fetch(Class<T> type, Object value) throws IllegalStateException {
        checkConfig();
        DirectoryEntry doc = (DirectoryEntry) fetch(value);
        if (doc != null) {
            if (type.isInstance(doc)) {
                return type.cast(doc);
            }
            if (type.isInstance(doc.getDocumentModel())) {
                return type.cast(doc.getDocumentModel());
            }
        }
        return null;
    }

    @Override
    public Serializable getReference(Object entity) throws IllegalStateException {
        checkConfig();
        DocumentModel entry = null;
        if (entity != null) {
            if (entity instanceof DirectoryEntry) {
                entry = ((DirectoryEntry) entity).getDocumentModel();
            } else if (entity instanceof DocumentModel) {
                entry = (DocumentModel) entity;
            }
            if (entry != null) {
                if (!entry.hasSchema(schema)) {
                    return null;
                }
                String result = (String) entry.getProperty(schema, idField);
                if (hierarchical) {
                    String parent = (String) entry.getProperty(schema, parentField);
                    try (Session session = directory.getSession()) {
                        while (parent != null) {
                            entry = session.getEntry(parent);
                            if (entry == null) {
                                break;
                            }
                            result = parent + separator + result;
                            parent = (String) entry.getProperty(schema, parentField);
                        }
                    }
                }
                return result;
            }
        }
        return null;
    }

    @Override
    public String getConstraintErrorMessage(Object invalidValue, Locale locale) {
        checkConfig();
        return Helper.getConstraintErrorMessage(this, invalidValue, locale, directory.getName());
    }

    private void checkConfig() throws IllegalStateException {
        if (parameters == null) {
            throw new IllegalStateException(
                    "you should call #configure(Map<String, String>) before. Please get this resolver throught ExternalReferenceService which is in charge of resolver configuration.");
        }
    }

    /**
     * Refetch the directory which is transient.
     *
     * @since 7.10
     */
    private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        fetchDirectory();
    }

}