/*
* 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);
}
}