com.genologics.ri.userdefined.UDF.java Source code

Java tutorial

Introduction

Here is the source code for com.genologics.ri.userdefined.UDF.java

Source

/*
 * CRUK-CI Genologics REST API Java Client.
 * Copyright (C) 2013 Cancer Research UK Cambridge Institute.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.genologics.ri.userdefined;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;

import com.genologics.ri.LimsEntityLinkable;
import com.genologics.ri.Locatable;
import com.genologics.ri.configuration.FieldType;

/**
 *
 * Field is the value and data type of a user-defined field.
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "field")
public class UDF implements Serializable {
    private static final long serialVersionUID = -3019516615900521203L;

    private static final String UDF_METHOD_NAME = "getUserDefinedFields";

    private static Map<Class<?>, Method> classUdfMethods = Collections
            .synchronizedMap(new HashMap<Class<?>, Method>());

    @XmlAttribute(name = "name", required = true)
    protected String name;

    @XmlAttribute(name = "type")
    protected FieldType type;

    @XmlAttribute(name = "unit")
    protected String unit;

    @XmlValue
    protected String value;

    public UDF() {
    }

    public UDF(String name, FieldType type) {
        this(name, type, null);
    }

    public UDF(String name, Object value) {
        this(name, null, value);
    }

    public UDF(String name, FieldType type, Object value) {
        setName(name);
        setType(type);
        setValue(value);
    }

    public String getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value == null ? null : value.toString();
    }

    public String getUnit() {
        return unit;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public FieldType getType() {
        return type;
    }

    public void setType(FieldType type) {
        this.type = type;
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder(200);
        b.append("UDF[name=").append(name);
        b.append(",type=").append(type);
        b.append(",value=").append(value);
        b.append(']');
        return b.toString();
    }

    /**
     * Finds a UDF by name in a collection of UDF objects.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     *
     * @return The UDF object with the same name, or {@code null} if there
     * is no matching UDF.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     */
    public static UDF getUDF(Collection<UDF> udfs, String name) {
        return getUDF(udfs, name, false, null);
    }

    /**
     * Finds a UDF by name in a collection of UDF objects and, if found, returns its value.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     *
     * @return The value of the UDF, or {@code null} if there
     * is no matching UDF.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     */
    public static String getUDFValue(Collection<UDF> udfs, String name) {
        return getUDFValue(udfs, name, false, null);
    }

    /**
     * Finds a UDF by name in a collection of UDF objects and, if found, returns its value.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     * @param defaultValue The value to return if there is no matching UDF.
     *
     * @return The value of the UDF, or {@code defaultValue} if there
     * is no matching UDF.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     *
     * @since 2.22
     */
    public static String getUDFValue(Collection<UDF> udfs, String name, String defaultValue) {
        String value = getUDFValue(udfs, name, false, null);
        return StringUtils.isNotEmpty(value) ? value : defaultValue;
    }

    /**
     * Finds a UDF by name in a collection of UDF objects.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     *
     * @return The UDF object with the same name, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static UDF getUDF(Collection<UDF> udfs, String name, boolean fail) {
        return getUDF(udfs, name, fail, null);
    }

    /**
     * Finds a UDF by name in a collection of UDF objects and returns its value.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     *
     * @return The value of the UDF, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static String getUDFValue(Collection<UDF> udfs, String name, boolean fail) {
        return getUDFValue(udfs, name, fail, null);
    }

    /**
     * Finds a UDF by name in a collection of UDF objects.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     * @param failMessage The message to put in the {@code MissingUDFException} when
     * such an exception is raised.
     *
     * @return The UDF object with the same name, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static UDF getUDF(Collection<UDF> udfs, String name, boolean fail, String failMessage) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }

        if (udfs != null) {
            for (UDF u : udfs) {
                if (u != null && name.equals(u.getName())) {
                    return u;
                }
            }
        }

        if (fail) {
            throw new MissingUDFException(name, getMissingUDFMessage(name, failMessage));
        }

        return null;
    }

    /**
     * Finds a UDF by name in a collection of UDF objects and returns its value.
     *
     * @param udfs The collection to search.
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     * @param failMessage The message to put in the {@code MissingUDFException} when
     * such an exception is raised.
     *
     * @return The value of the UDF, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static String getUDFValue(Collection<UDF> udfs, String name, boolean fail, String failMessage) {
        UDF udf = getUDF(udfs, name, fail, failMessage);
        if (udf != null && fail && StringUtils.isEmpty(udf.getValue())) {
            throw new MissingUDFException(name, getMissingUDFMessage(name, failMessage));
        }
        return udf == null || StringUtils.isEmpty(udf.getValue()) ? null : udf.getValue();
    }

    /**
     * Finds a UDF by name in the UDFs of the given object.
     *
     * @param thing The object that should have the UDFs. Needs to have a method
     * "getUserDefinedFields".
     * @param name The name of the UDF to find.
     *
     * @return The UDF object with the same name, or {@code null} if there
     * is no matching UDF.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     */
    public static UDF getUDF(Object thing, String name) {
        return getUDF(thing, name, false, null);
    }

    /**
     * Finds a UDF by name in the UDFs of the given object and, if found, returns its value.
     *
     * @param thing The object that should have the UDFs. Needs to have a method
     * "getUserDefinedFields".
     * @param name The name of the UDF to find.
     *
     * @return The value of the UDF, or {@code null} if there
     * is no matching UDF.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     */
    public static String getUDFValue(Object thing, String name) {
        return getUDFValue(thing, name, false, null);
    }

    /**
     * Finds a UDF by name in the UDFs of the given object and, if found, returns its value.
     *
     * @param thing The object that should have the UDFs. Needs to have a method
     * "getUserDefinedFields".
     * @param name The name of the UDF to find.
     * @param defaultValue The value to return if there is no matching UDF.
     *
     * @return The value of the UDF, or {@code defaultValue} if there
     * is no matching UDF.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     *
     * @since 2.22
     */
    public static String getUDFValue(Object thing, String name, String defaultValue) {
        String value = getUDFValue(thing, name, false, null);
        return StringUtils.isNotEmpty(value) ? value : defaultValue;
    }

    /**
     * Finds a UDF by name in the UDFs of the given object.
     *
     * @param thing The object that should have the UDFs. Needs to have a method
     * "getUserDefinedFields".
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     *
     * @return The UDF object with the same name, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static UDF getUDF(Object thing, String name, boolean fail) {
        return getUDF(thing, name, fail, null);
    }

    /**
     * Finds a UDF by name in the UDFs of the given object and returns its value.
     *
     * @param thing The object that should have the UDFs. Needs to have a method
     * "getUserDefinedFields".
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     *
     * @return The value of the UDF, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static String getUDFValue(Object thing, String name, boolean fail) {
        return getUDFValue(thing, name, fail, null);
    }

    /**
     * Finds a UDF by name in the UDFs of the given object.
     *
     * <p>The given object needs to have a method "getUserDefinedFields" returning a {@link Collection}
     * of UDFs.</p>
     *
     * @param thing The object that should have the UDFs.
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     * @param failMessage The message to put in the {@code MissingUDFException} when
     * such an exception is raised.
     *
     * @return The UDF object with the same name, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static UDF getUDF(Object thing, String name, boolean fail, String failMessage) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }

        if (thing == null && fail) {
            throw new MissingUDFException(name, getMissingUDFMessage(thing, name, failMessage));
        }

        UDF udf = null;

        if (thing != null) {
            try {
                Collection<UDF> udfs = getUDFCollection(thing);
                udf = getUDF(udfs, name, fail, failMessage);
            } catch (MissingUDFException e) {
                // If there is no explicit fail message, we might be able to provide a better one
                // than that given by getUDF(...).
                throw new MissingUDFException(name, getMissingUDFMessage(thing, name, failMessage));
            }
        }

        return udf;
    }

    /**
     * Create a message for the MissingUDFException when {@code failMessage} is not set.
     *
     * @param name The name of the UDF.
     * @param failMessage The fail message supplied by the caller.
     *
     * @return A suitable failure message for the exception.
     */
    private static String getMissingUDFMessage(String name, String failMessage) {
        if (failMessage == null) {
            failMessage = "UDF \"" + name + "\" does not exist.";
        }
        return failMessage;
    }

    /**
     * Create a message for the MissingUDFException when {@code failMessage} is not set.
     *
     * @param thing The object the UDF is set on.
     * @param name The name of the UDF.
     * @param failMessage The fail message supplied by the caller.
     *
     * @return A suitable failure message for the exception.
     */
    private static String getMissingUDFMessage(Object thing, String name, String failMessage) {
        if (failMessage == null) {
            if (thing == null) {
                failMessage = "UDF \"" + name + "\" does not exist because 'thing' is null.";
            } else {
                String better = "UDF \"{0}\" does not exist on {1} {2}";
                if (thing instanceof LimsEntityLinkable<?>) {
                    LimsEntityLinkable<?> linkable = (LimsEntityLinkable<?>) thing;
                    failMessage = MessageFormat.format(better, name, ClassUtils.getShortClassName(thing.getClass()),
                            linkable.getLimsid());
                } else if (thing instanceof Locatable) {
                    Locatable locatable = (Locatable) thing;
                    failMessage = MessageFormat.format(better, name, ClassUtils.getShortClassName(thing.getClass()),
                            locatable.getUri());
                } else {
                    failMessage = MessageFormat.format(better, name, ClassUtils.getShortClassName(thing.getClass()),
                            Integer.toHexString(System.identityHashCode(thing)));
                }
            }
        }
        return failMessage;
    }

    /**
     * Finds a UDF by name in the UDFs of the given object and returns its value.
     *
     * @param thing The object that should have the UDFs. Needs to have a method
     * "getUserDefinedFields".
     * @param name The name of the UDF to find.
     * @param fail Whether to fail with a {@code MissingUDFException} if the field
     * is not found.
     * @param failMessage The message to put in the {@code MissingUDFException} when
     * such an exception is raised.
     *
     * @return The value of the UDF, or {@code null} if there
     * is no matching UDF and {@code fail} is false.
     *
     * @throws IllegalArgumentException if {@code name} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     * @throws MissingUDFException if {@code fail} is true and there is no matching UDF.
     */
    public static String getUDFValue(Object thing, String name, boolean fail, String failMessage) {
        UDF udf = getUDF(thing, name, fail, failMessage);
        if (udf != null && fail && StringUtils.isEmpty(udf.getValue())) {
            throw new MissingUDFException(name, getMissingUDFMessage(thing, name, failMessage));
        }
        return udf == null ? null : udf.getValue();
    }

    /**
     * Helper method to get the collection of UDFs from an object.
     *
     * @param thing The object that should have the UDFs.
     *
     * @return The collection of UDFs from {@code thing}.
     *
     * @throws IllegalArgumentException if {@code thing} is null, or if {@code thing}
     * does not have a publicly visible "getUserDefinedFields" method returning a {@code Collection}.
     */
    static Collection<UDF> getUDFCollection(Object thing) {
        if (thing == null) {
            throw new IllegalArgumentException("thing cannot be null");
        }

        Method getPropsMethod = classUdfMethods.get(thing.getClass());
        if (getPropsMethod == null) {
            try {
                getPropsMethod = thing.getClass().getMethod(UDF_METHOD_NAME);
                if (!Collection.class.isAssignableFrom(getPropsMethod.getReturnType())) {
                    throw new IllegalArgumentException(
                            MessageFormat.format("The \"{0}\" method on {1} does not return a Collection.",
                                    UDF_METHOD_NAME, thing.getClass().getName()));
                }

                classUdfMethods.put(thing.getClass(), getPropsMethod);
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException(MessageFormat.format("There is no method \"{0}\" method on {1}.",
                        UDF_METHOD_NAME, thing.getClass().getName()));
            }
        }

        try {
            @SuppressWarnings("unchecked")
            Collection<UDF> udfs = (Collection<UDF>) getPropsMethod.invoke(thing);

            return udfs;
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException(MessageFormat.format("Cannot call method \"{0}\" method on {1}: {2}",
                    UDF_METHOD_NAME, thing.getClass().getName(), e.getMessage()));
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Exception while fetching UDFs from " + thing.getClass().getName(),
                    e.getTargetException());
        }
    }

    /**
     * Adds, sets or removes a UDF from the collection of fields on the given object. If the named
     * UDF exists in the collection, it either has its value set (if "value" is not null)
     * or is removed (if "value" is null). If it doesn't exist, a new {@code UDF}
     * object is created for the field and is added to the collection.
     *
     * @param thing The object containing UDFs.
     * @param name The name of the field to change.
     * @param value The value to set the field to.
     *
     * @return The UDF object found or created, or null if it is removed or not found.
     *
     * @throws IllegalArgumentException if either of {@code thing} or {@code name}
     * are null, or if {@code thing} does not have a publicly visible
     * "getUserDefinedFields" method returning a {@code Collection}.
     */
    public static UDF setUDF(Object thing, String name, Object value) {
        Collection<UDF> udfs = getUDFCollection(thing);

        return setUDF(udfs, name, value);
    }

    /**
     * Adds, sets or removes a UDF from the given collection of fields. If the named
     * UDF exists in the collection, it either has its value set (if "value" is not null)
     * or is removed (if "value" is null). If it doesn't exist, a new {@code UDF}
     * object is created for the field and is added to the collection.
     *
     * @param udfs The collection of UDF objects.
     * @param name The name of the field to change.
     * @param value The value to set the field to.
     *
     * @return The UDF object found or created, or null if it is removed or not found.
     *
     * @throws IllegalArgumentException if either of {@code udfs} or {@code name}
     * are null.
     */
    public static UDF setUDF(Collection<UDF> udfs, String name, Object value) {
        if (udfs == null) {
            throw new IllegalArgumentException("udfs cannot be null");
        }
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }

        UDF udf = getUDF(udfs, name, false);

        String strValue = value == null ? null : value.toString();

        if (StringUtils.isEmpty(strValue)) {
            if (udf != null) {
                // Need to set the value to empty so when the call is made the
                // API will actually set the value (or rather clear it). It seems
                // that the assumption that it can just be removed from the list
                // is wrong.
                udf.setValue(StringUtils.EMPTY);
            }
        } else {
            if (udf != null) {
                // Just change the value.
                udf.setValue(strValue);
            } else {
                udf = new UDF(name, strValue);
                udfs.add(udf);
            }
        }

        return udf;
    }
}