All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.stormpath.sdk.convert.ResourceConverter Maven / Gradle / Ivy

Go to download

Servlet-specific additions allowing one to more easily deploy the Stormpath SDK in a servlet-container-based web application.

The newest version!
/*
 * Copyright 2016 Stormpath, Inc.
 *
 * 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 com.stormpath.sdk.convert;

import com.stormpath.sdk.impl.resource.AbstractResource;
import com.stormpath.sdk.lang.Assert;
import com.stormpath.sdk.lang.Function;
import com.stormpath.sdk.lang.Instants;
import com.stormpath.sdk.lang.Strings;
import com.stormpath.sdk.resource.CollectionResource;
import com.stormpath.sdk.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Converts a {@link Resource} or {@link Map} to an output value that is structured according the specified
 * {@link #setConfig(Conversion) config}.
 *
 * @since 1.3.0
 */
public class ResourceConverter implements Function {

    private static final Logger log = LoggerFactory.getLogger(ResourceConverter.class);

    public static final Conversion DEFAULT_CONFIG = Conversions
        //.withField("href", Conversions.disabled())
        .withField("customData", Conversions.withStrategy(ConversionStrategyName.SCALARS))
        .withField("groups", Conversions.withStrategy(ConversionStrategyName.DEFINED).setElements(Conversions.each(new Conversion())));

    private Conversion config;

    public ResourceConverter() {
        this.config = DEFAULT_CONFIG;
    }

    /**
     * Returns the {@code Conversion} that indicates how to convert the function argument into an output value.
     *
     * @return the {@code Conversion} that indicates how to convert the function argument into an output value.
     */
    public Conversion getConfig() {
        return config;
    }

    /**
     * Sets the {@code Conversion} that indicates how to convert the function argument into an output value.
     *
     * @param config the {@code Conversion} that indicates how to convert the function argument into an output value.
     */
    public void setConfig(Conversion config) {
        Assert.notNull(config, "Conversion argument cannot be null.");
        this.config = config;
    }

    /**
     * a {@link Resource} or {@link Map} to an output value that is structured according the specified
     * {@link #setConfig(Conversion) config}.
     *
     * @param t the resource or Map to convert
     * @return an output value that is constructed according to the specified {@link #setConfig(Conversion) config}.
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object apply(T t) {
        Assert.notNull(t, "Argument cannot be null");
        return convert(t, this.config, "");
    }

    public Object convert(Object o, Conversion config, String path) {

        Assert.notNull(o, "Object argument cannot be null");

        ConversionStrategyName strategy = config.getStrategy();
        Assert.notNull(strategy, "config strategy value cannot be null.");

        if (o instanceof AbstractResource) {
            AbstractResource ar = (AbstractResource) o;
            if (!ar.isMaterialized()) {
                ar.materialize();
            }
        }

        boolean isCollection = o instanceof Iterable;

        Map props = new LinkedHashMap<>();

        if (strategy == ConversionStrategyName.SINGLE) {
            String fieldName = config.getField();
            if (Strings.hasText(fieldName) && hasProperty(o, fieldName)) {
                return getProperty(o, fieldName);
            } else if (o instanceof AbstractResource) {
                return ((AbstractResource) o).getHref();
            } else {
                return null; //TODO: error or log message?
            }
        }

        Map fieldsConfig = config.getFields();

        Set fieldNames = getPropertyNames(o);

        for (String fieldName : fieldNames) {

            String outputName = fieldName;
            Object value = null;
            boolean defined;
            boolean enabled;

            //if the current object is a collection, the elements in the collection are not represented by the
            //fields config - instead they are represented by the 'elements' config.  So if we're visiting an 'items'
            //field, we need to see if it is enabled by checking the 'elements' config, not the 'fields' config:
            if (isCollection && fieldName.equals("items")) {
                ElementsConversion elementsConfig = config.getElements();
                defined = elementsConfig != null;
                enabled = !defined || elementsConfig.isEnabled();
            } else {
                //a field - check the fieldsConfig:
                defined = fieldsConfig.containsKey(fieldName);
                enabled = isEnabled(fieldsConfig, fieldName);
            }

            if (strategy == ConversionStrategyName.DEFINED) {
                enabled = defined && enabled;
            }

            if (enabled) {
                value = getProperty(o, fieldName);
            }

            boolean compound = isCompound(value);

            if (strategy == ConversionStrategyName.SCALARS && compound && !defined) {
                enabled = false;
            }

            if (enabled) {

                Conversion fieldConfig = fieldsConfig.get(fieldName);
                if (fieldConfig != null) {
                    String name = fieldConfig.getName();
                    if (Strings.hasText(name)) {
                        outputName = name;
                    }
                }

                if (compound) {

                    if (isCollection) {
                        String elementsPath = joinPath(path, "elements");
                        ElementsConversion elementsConfig = config.getElements();
                        String eachPath = joinPath(elementsPath, "each");
                        Conversion elementConfig = elementsConfig.getEach();
                        List elements = new ArrayList<>();

                        int i = 0;
                        for (Object element : ((Iterable) o)) {
                            Object elementValue = convert(element, elementConfig, eachPath + "[" + i + "]");
                            if (elementValue != null) {
                                elements.add(elementValue);
                            }
                            i++;
                        }

                        value = elements;

                        if (strategy == ConversionStrategyName.LIST) {
                            return value;
                        }

                        String elementsName = elementsConfig.getName();
                        if (Strings.hasText(elementsName)) {
                            outputName = elementsName;
                        }

                    } else {
                        String fieldsPath = joinPath(path, "fields");
                        fieldConfig = fieldsConfig.get(fieldName);
                        String newPath = joinPath(fieldsPath, fieldName);
                        value = convert(value, fieldConfig, newPath);
                    }
                }

                props.put(outputName, value);
            }
        }

        if (props.isEmpty()) {
            //nothing configured, TODO: print warning?
            if (o instanceof AbstractResource) {
                props.put(AbstractResource.HREF_PROP_NAME, ((AbstractResource) o).getHref());
            }
        }

        return props;
    }

    @SuppressWarnings("unchecked")
    protected Set getPropertyNames(Object o) {
        if (o instanceof AbstractResource) {
            return ((AbstractResource) o).getPropertyNames();
        } else if (o instanceof Map) {
            return ((Map) o).keySet();
        }
        throw new IllegalArgumentException("Argument must be an AbstractResource or Map.");
    }

    protected boolean hasProperty(Object o, String name) {
        if (o instanceof AbstractResource) {
            return ((AbstractResource) o).hasProperty(name);
        } else if (o instanceof Map) {
            return ((Map) o).containsKey(name);
        }
        throw new IllegalArgumentException("Argument must be an AbstractResource or Map.");
    }

    protected Object getProperty(Object o, String name) {

        Object value;

        if (o instanceof Map) {

            value = ((Map) o).get(name);

        } else if (o instanceof AbstractResource) {

            AbstractResource resource = (AbstractResource) o;

            if (o instanceof CollectionResource && name.equals("items")) {
                value = resource.getProperty(name);
            } else {
                try {
                    final Class resourceClass = resource.getClass();
                    final String methodName = "get" + Strings.capitalize(name);
                    Method method = resourceClass.getMethod(methodName);
                    value = method.invoke(o);
                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                    if (log.isDebugEnabled()) {
                        String msg = "Unable to access resource property '" + name + "': " + e.getMessage();
                        log.debug(msg, e);
                    }
                    value = resource.getProperty(name);
                }
            }

        } else {
            throw new IllegalArgumentException("Argument must be an AbstractResource or Map.");
        }

        if (value instanceof Date) {
            Date date = (Date) value;
            value = Instants.toUtcIso8601(date);
        }

        return value;
    }

    protected boolean isCompound(Object value) {
        return value instanceof Collection || value instanceof Map || value instanceof Resource;
    }

    protected boolean isEnabled(Map fieldConfig, String field) {
        if ("password".equals(field)) {
            return false;
        }
        Conversion config = fieldConfig.get(field);
        return config == null || config.isEnabled();
    }

    protected String joinPath(String parent, String child) {
        StringBuilder sb = new StringBuilder(parent);
        if (!"".equals(parent)) {
            sb.append('.');
        }
        sb.append(child);
        return sb.toString();
    }
}