org.zodiark.server.ZodiarkObjectFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.zodiark.server.ZodiarkObjectFactory.java

Source

/*
 * Copyright 2013-2014 High-Level Technologies
 *
 * 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.zodiark.server;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.AtmosphereFramework;
import org.atmosphere.cpr.AtmosphereObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zodiark.service.db.result.Result;
import org.zodiark.service.rest.AHCBlockingRestClient;
import org.zodiark.service.rest.InMemoryRestClient;
import org.zodiark.service.rest.RestClient;
import org.zodiark.service.rest.RestService;
import org.zodiark.service.rest.RestServiceImpl;
import org.zodiark.service.session.StreamingRequest;
import org.zodiark.service.state.AuthConfig;
import org.zodiark.service.state.EndpointState;
import org.zodiark.service.util.StreamingRequestImpl;
import org.zodiark.service.util.mock.OKAuthConfig;
import org.zodiark.service.wowza.WowzaEndpointManager;
import org.zodiark.service.wowza.WowzaEndpointManagerImpl;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * An {@link AtmosphereObjectFactory} that handles the injection of Zodiark's object. Field injection are discovered via the
 * {@link javax.inject.Inject} annotation. Injection uses {@link Injectable} and {@link org.zodiark.server.ZodiarkObjectFactory.Implementable} implementation to discover what to inject and which
 * default implementation class to use. Those default can be overridden by calling the {@link #implementatble(Class, org.zodiark.server.ZodiarkObjectFactory.Implementable)}
 * and {@link #injectable(Class, org.zodiark.server.ZodiarkObjectFactory.Injectable)}
 * <p/>
 * Injectable instances are
 * {@link EventBus}, {@link ObjectMapper}, {@link WowzaEndpointManager}, {@link StreamingRequest}
 * <p/>
 * Extendable classes are
 * {@link AuthConfig}, {@link org.zodiark.service.rest.RestService}
 * <p/>
 * Injectable and Extendable can be replaced.
 * <p/>
 * This class contains all default instanced used by the {@link org.zodiark.service.Service} and object used by this framework.
 * Override this class to replace the default object injection.
 */
public class ZodiarkObjectFactory implements AtmosphereObjectFactory {
    private final Logger logger = LoggerFactory.getLogger(ZodiarkObjectFactory.class);
    public final static String DB_URL = "zodiark.db.url";

    private final ConcurrentHashMap<Class<?>, Injectable> injectRepository = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Class<?>, Implementable> implementationRepository = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Class<? extends Object>, Object> instanceRepository = new ConcurrentHashMap<>();

    private final AtomicBoolean added = new AtomicBoolean();
    private ScheduledExecutorService timer = Executors.newScheduledThreadPool(200);

    public ZodiarkObjectFactory() {
        // Install the default injectable
        injectable(EventBus.class, new Injectable<EventBus>() {

            @Override
            public EventBus construct(Class<EventBus> t) {
                return EventBusFactory.getDefault().eventBus();
            }
        });
        injectable(ObjectMapper.class, new Injectable<ObjectMapper>() {

            private final ObjectMapper mapper = new ObjectMapper();

            @Override
            public ObjectMapper construct(Class<ObjectMapper> t) {
                return mapper;
            }
        });
        injectable(WowzaEndpointManager.class, new Injectable<WowzaEndpointManager>() {

            private final WowzaEndpointManager wowzaService = new WowzaEndpointManagerImpl();

            @Override
            public WowzaEndpointManager construct(Class<WowzaEndpointManager> t) {
                return wowzaService;
            }
        });
        injectable(StreamingRequest.class, new Injectable<StreamingRequest>() {

            private final StreamingRequest streamingRequest = new StreamingRequestImpl();

            @Override
            public StreamingRequest construct(Class<StreamingRequest> t) {
                return streamingRequest;
            }
        });

        injectable(URI.class, new Injectable<URI>() {
            @Override
            public URI construct(Class<URI> t) {
                return URI.create(System.getProperty(DB_URL, "0.0.0.0"));
            }
        });

        // Install the default extensable
        implementatble(AuthConfig.class, new Implementable<AuthConfig>() {
            @Override
            public Class<OKAuthConfig> extend(Class<AuthConfig> t) {
                return OKAuthConfig.class;
            }
        });

        if (System.getProperty(DB_URL, "").isEmpty()) {
            implementatble(RestClient.class, new Implementable<RestClient>() {
                @Override
                public Class<InMemoryRestClient> extend(Class<RestClient> t) {
                    return InMemoryRestClient.class;
                }
            });
        } else {
            implementatble(RestClient.class, new Implementable<RestClient>() {
                @Override
                public Class<AHCBlockingRestClient> extend(Class<RestClient> t) {
                    return AHCBlockingRestClient.class;
                }
            });
        }

        implementatble(RestService.class, new Implementable<RestService>() {
            @Override
            public Class<RestServiceImpl> extend(Class<RestService> t) {
                return RestServiceImpl.class;
            }
        });

    }

    @Override
    public <T, U extends T> T newClassInstance(final AtmosphereFramework framework, Class<T> classType,
            Class<U> tClass) throws InstantiationException, IllegalAccessException {
        logger.debug("About to create {}", tClass.getName());
        if (!added.getAndSet(true) && framework != null) {
            framework.getAtmosphereConfig().shutdownHook(new AtmosphereConfig.ShutdownHook() {
                @Override
                public void shutdown() {
                    timer.shutdown();
                }
            });
            framework.getAtmosphereConfig().startupHook(new AtmosphereConfig.StartupHook() {

                @Override
                public void started(AtmosphereFramework framework) {
                    injectRepository.clear();
                    implementationRepository.clear();
                    instanceRepository.clear();
                }
            });

        }

        Class<? extends T> impl = implement(classType);

        boolean needsPostConstruct = (impl == null || instanceRepository.get(impl) == null);
        T instance = impl != null ? newInstance(impl) : tClass.newInstance();

        Field[] fields = tClass.equals(instance.getClass()) ? tClass.getDeclaredFields() : impl.getFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Inject.class)) {
                if (field.getType().isAssignableFrom(ObjectMapper.class)) {
                    field.set(instance, inject(ObjectMapper.class));
                } else if (field.getType().isAssignableFrom(EventBus.class)) {
                    field.set(instance, inject(EventBus.class));
                } else if (field.getType().isAssignableFrom(RestService.class)) {
                    field.set(instance, newClassInstance(framework, RestService.class, RestService.class));
                } else if (field.getType().isAssignableFrom(WowzaEndpointManager.class)) {
                    field.set(instance, inject(WowzaEndpointManager.class));
                } else if (field.getType().isAssignableFrom(Context.class)) {
                    field.set(instance, new Context() {

                        @Override
                        public <T> T newInstance(Class<T> t) {
                            try {
                                return newClassInstance(framework, t, t);
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                    });
                } else if (field.getType().isAssignableFrom(AuthConfig.class)) {
                    field.set(instance, newClassInstance(framework, Result.class, AuthConfig.class));
                } else if (field.getType().isAssignableFrom(EndpointState.class)) {
                    field.set(instance, newClassInstance(framework, EndpointState.class, EndpointState.class));
                } else if (field.getType().isAssignableFrom(StreamingRequest.class)) {
                    field.set(instance, inject(StreamingRequest.class));
                } else if (field.getType().isAssignableFrom(ScheduledExecutorService.class)) {
                    field.set(instance, timer);
                } else if (field.getType().isAssignableFrom(RestClient.class)) {
                    field.set(instance, newClassInstance(framework, RestClient.class, RestClient.class));
                } else if (field.getType().isAssignableFrom(URI.class)) {
                    field.set(instance, inject(URI.class));
                }
            }
        }

        if (needsPostConstruct) {
            Method[] methods = tClass.equals(instance.getClass()) ? tClass.getMethods()
                    : instance.getClass().getMethods();
            for (Method m : methods) {
                if (m.isAnnotationPresent(PostConstruct.class)) {
                    try {
                        m.invoke(instance);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return instance;
    }

    private <T> T newInstance(Class<? extends T> impl) throws IllegalAccessException, InstantiationException {
        T instance = (T) instanceRepository.get(impl);
        if (instance == null) {
            instance = inject(impl);
            if (instance == null) {
                instance = impl.newInstance();
            }
        }
        instanceRepository.put(impl, instance);
        return instance;
    }

    private <T> T inject(Class<T> clazz) {
        Injectable<T> t = injectRepository.get(clazz);
        if (t != null) {
            return t.construct(clazz);
        }
        return null;
    }

    private <T> Class<? extends T> implement(Class<T> clazz) {
        Implementable<T> t = implementationRepository.get(clazz);
        if (t != null) {
            return t.extend(clazz);
        }
        return null;
    }

    /**
     * Register an {@link Injectable} that will be responsible for creation class T
     *
     * @param clazz      a class
     * @param injectable its associated {@link Injectable}
     * @return this
     */
    public <T> ZodiarkObjectFactory injectable(Class<T> clazz, Injectable<T> injectable) {
        injectRepository.put(clazz, injectable);
        return this;
    }

    /**
     * Register an {@link org.zodiark.server.ZodiarkObjectFactory.Implementable} that will return the associated implementation of class T
     *
     * @param clazz         a class
     * @param implementable its associated {@link Injectable}
     * @return this
     */
    public <T> ZodiarkObjectFactory implementatble(Class<T> clazz, Implementable<T> implementable) {
        implementationRepository.put(clazz, implementable);
        return this;
    }

    /**
     * Handle creation of object of type T
     *
     * @param <T> A class of type T
     */
    public static interface Injectable<T> {
        /**
         * Create an instance of T
         *
         * @param t a class to be created
         * @return an instance of T
         */
        T construct(Class<T> t);
    }

    /**
     * Handle implementation of class of type T
     *
     * @param <T>
     */
    public static interface Implementable<T> {
        /**
         * Return the class' extending of T
         *
         * @param t A class extending T
         * @return a class extending T
         */
        Class<? extends T> extend(Class<T> t);
    }
}