org.amplafi.hivemind.factory.servicessetter.ServicesSetterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.amplafi.hivemind.factory.servicessetter.ServicesSetterImpl.java

Source

/*
 * 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.amplafi.hivemind.factory.servicessetter;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.amplafi.hivemind.annotations.InjectService;
import org.amplafi.hivemind.annotations.NotService;

import com.sworddance.core.ServicesSetter;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.internal.Module;
import org.apache.hivemind.util.PropertyAdaptor;

import static org.apache.commons.lang.StringUtils.*;
import static org.apache.hivemind.util.PropertyUtils.*;

/**
 * Utility class that allows wiring up existing services (from a hivemind
 * registry) into a given object.
 *
 * @author andyhot
 */
public class ServicesSetterImpl implements ServicesSetter {

    private Module module;

    private Log log;

    /**
     * Key is concatenation of class and the excluded method names.
     */
    private ConcurrentMap<Class<?>, Set<String>> cachedAlwaysExcludedMap = new ConcurrentHashMap<Class<?>, Set<String>>();

    private ConcurrentMap<Class<?>, ConcurrentMap<String, String>> serviceMap = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, String>>();

    /**
     * Used to record classes that have not been found by the property type to avoid repeated attempts that will fail.
     */
    private ConcurrentMap<Class<?>, Exception> noServiceForType = new ConcurrentHashMap<>();

    public ServicesSetterImpl() {
    }

    public void setModule(Module module) {
        this.module = module;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void wire(Object obj) {
        wire(obj, Collections.EMPTY_LIST);
    }

    @Override
    public void wire(Object obj, String... excludedProperties) {
        wire(obj, Arrays.asList(excludedProperties));
    }

    /**
     * @see com.sworddance.core.ServicesSetter#wire(java.lang.Object, java.lang.Iterable)
     */
    @Override
    @SuppressWarnings("unchecked")
    public void wire(Object obj, Iterable<String> excludedProperties) {
        if (obj == null) {
            return;
        }
        List<String> props = getWriteableProperties(obj);
        Set<String> alwaysExcludedCollection = this.cachedAlwaysExcludedMap.get(obj.getClass());

        if (alwaysExcludedCollection != null) {
            props.removeAll(alwaysExcludedCollection);
            if (getLog().isDebugEnabled()) {
                getLog().debug(obj.getClass() + ": autowiring. Class has already been filtered down to "
                        + props.size() + "properties. props={" + join(props, ",") + "}");
            }
        } else {
            if (getLog().isDebugEnabled()) {
                getLog().debug(obj.getClass() + ": autowiring for the first time. Class has at most " + props.size()
                        + "properties. props={" + join(props, ",") + "}");
            }
            alwaysExcludedCollection = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
            this.cachedAlwaysExcludedMap.putIfAbsent(obj.getClass(), alwaysExcludedCollection);
            alwaysExcludedCollection = this.cachedAlwaysExcludedMap.get(obj.getClass());
        }
        for (String exclude : excludedProperties) {
            props.remove(exclude);
        }
        int wiredCount = 0;

        for (String prop : props) {
            PropertyAdaptor type = getPropertyAdaptor(obj, prop);
            Class propertyType = type.getPropertyType();
            if (!isWireableClass(propertyType)) {
                // if it is a standard java class then lets exclude it.
                alwaysExcludedCollection.add(prop);
                continue;
            }
            // if it is not readable then, then we can't verify that
            // we are not overwriting non-null property.
            if (!type.isReadable() || !type.isWritable()) {
                alwaysExcludedCollection.add(prop);
                continue;
            }
            // check to see if we have a service to offer before bothering
            // to checking if the property can be set. This avoids triggering
            // actions caused by calling the get/setters.
            Object srv = null;
            if (type.getPropertyType() == Log.class) {
                // log is special.
                srv = LogFactory.getLog(obj.getClass());
            } else {
                ConcurrentMap<String, String> classServiceMap = this.serviceMap.get(obj.getClass());
                if (classServiceMap == null) {
                    this.serviceMap.putIfAbsent(obj.getClass(), new ConcurrentHashMap<String, String>());
                    classServiceMap = this.serviceMap.get(obj.getClass());
                }
                String serviceName = classServiceMap.get(prop);
                if (serviceName == null) {
                    InjectService service;
                    try {
                        service = findInjectService(obj, type);
                    } catch (DontInjectException e) {
                        // do nothing
                        alwaysExcludedCollection.add(prop);
                        continue;
                    }

                    if (service != null) {
                        serviceName = service.value();
                        if (isNotBlank(serviceName)) {
                            for (String attempt : new String[] { serviceName,
                                    serviceName + '.' + type.getPropertyName(),
                                    serviceName + '.' + StringUtils.capitalize(type.getPropertyName()) }) {
                                srv = getService(attempt, propertyType);
                                if (srv != null) {
                                    serviceName = attempt;
                                    break;
                                }
                            }
                        }
                    }
                    if (srv != null) {
                        classServiceMap.putIfAbsent(prop, serviceName);
                    } else {
                        // we looked but did not find... no need to look again.
                        classServiceMap.putIfAbsent(prop, "");
                    }
                } else if (!serviceName.isEmpty()) {
                    // we already found the service.
                    srv = getService(serviceName, propertyType);
                }
                if (srv == null && !noServiceForType.containsKey(propertyType)) {
                    try {
                        srv = this.module.getService(propertyType);
                    } catch (Exception e) {
                        noServiceForType.put(propertyType, e);
                        getLog().debug("Look up of class " + propertyType
                                + " failed. The failure is caused if there is not exactly 1 service implementing the class. Further searches by this property class will be ignored.");
                    }
                }
            }
            if (srv == null) {
                alwaysExcludedCollection.add(prop);
            } else if (type.read(obj) == null) {
                // Doing the read check last avoids
                // triggering problems caused by lazy initialization and read-only properties.
                if (type.getPropertyType().isAssignableFrom(srv.getClass())) {
                    type.write(obj, srv);
                    wiredCount++;
                } else {
                    // this is probably an error so we do not just add to the exclude list.
                    throw new ApplicationRuntimeException("Trying to set property " + obj.getClass() + "." + prop
                            + " however, the property type=" + type.getPropertyType()
                            + " is not a superclass or same class as " + srv.getClass() + ". srv=" + srv);
                }
            }
        }
        if (getLog().isDebugEnabled()) {
            getLog().debug(obj.getClass() + ": done autowiring. actual number of properties wired=" + wiredCount
                    + " excluded properties=" + alwaysExcludedCollection);
        }
    }

    /**
     * @param serviceId
     * @param serviceClass
     * @return
     */
    @Override
    public <SC> SC getService(String serviceId, Class serviceClass) {
        try {
            return (SC) this.module.getService(serviceId, serviceClass);
        } catch (Exception e) {
            getLog().error(e);
            return null;
        }
    }

    /**
     * @param propertyType
     * @return true class can be wired up as a service
     */
    @Override
    public boolean isWireableClass(Class<?> propertyType) {
        if (
        // exclude primitives or other things that are not mockable in any form
        propertyType.isPrimitive() || propertyType.isAnnotation() || propertyType.isArray() || propertyType.isEnum()
        // generated classes
                || propertyType.getCanonicalName() == null
                // exclude java classes
                || propertyType.getPackage().getName().startsWith("java")) {
            return false;
        } else {
            // exclude things that are explicitly labeled as not being injectable
            NotService notService = propertyType.getAnnotation(NotService.class);
            return notService == null;
        }
    }

    /**
     * @param obj
     * @param type
     * @param propertyType
     * @return
     * @throws DontInjectException
     */
    private InjectService findInjectService(Object obj, PropertyAdaptor type) throws DontInjectException {
        InjectService service;
        Class<?> propertyType = type.getPropertyType();
        String propertyAccessorMethodName = "set" + StringUtils.capitalize(type.getPropertyName());
        service = findServiceAnnotation(obj, propertyAccessorMethodName, propertyType);
        if (service == null) {
            propertyAccessorMethodName = "get" + StringUtils.capitalize(type.getPropertyName());
            service = findServiceAnnotation(obj, propertyAccessorMethodName);
        }
        if (service == null && (propertyType == boolean.class || propertyType == Boolean.class)) {
            propertyAccessorMethodName = "is" + StringUtils.capitalize(type.getPropertyName());
            service = findServiceAnnotation(obj, propertyAccessorMethodName);
        }
        return service;
    }

    private NotService findNotService(Method m) throws DontInjectException {
        NotService notService = m.getAnnotation(NotService.class);
        if (notService != null) {
            throw new DontInjectException();
        }
        return notService;
    }

    /**
     * @param obj
     * @param propertyAccessorMethodName
     * @param propertyType
     * @return
     * @throws DontInjectException
     */
    private InjectService findServiceAnnotation(Object obj, String propertyAccessorMethodName,
            Class<?>... propertyType) throws DontInjectException {
        // look for @InjectService
        InjectService service = null;

        try {
            Method m = obj.getClass().getMethod(propertyAccessorMethodName, propertyType);
            findNotService(m);
            service = m.getAnnotation(InjectService.class);
            if (service == null) {
                for (Class<?> cls : obj.getClass().getInterfaces()) {
                    try {
                        m = cls.getMethod(propertyAccessorMethodName, propertyType);
                        findNotService(m);
                        service = m.getAnnotation(InjectService.class);
                        if (service != null) {
                            break;
                        }
                    } catch (NoSuchMethodException e) {

                    }
                }
            }
        } catch (NoSuchMethodException e) {

        }
        return service;
    }

    /**
     * @param log the log to set
     */
    public void setLog(Log log) {
        this.log = log;
    }

    /**
     * @return the log
     */
    public Log getLog() {
        return this.log;
    }

    private static class DontInjectException extends Exception {

    }
}