/*
* Copyright © 2014 Cask Data, 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 co.cask.cdap.internal.app.runtime.service.http;
import co.cask.cdap.api.metrics.MetricsContext;
import co.cask.cdap.api.service.http.HttpServiceHandler;
import co.cask.cdap.internal.asm.ByteCodeClassLoader;
import co.cask.cdap.internal.asm.ClassDefinition;
import co.cask.http.HttpHandler;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
/**
* A factory for creating {@link co.cask.http.HttpHandler} from user provided instance of
* {@link HttpServiceHandler}.
*/
public final class HttpHandlerFactory {
private static final Logger LOG = LoggerFactory.getLogger(HttpHandlerFactory.class);
private final LoadingCache<TypeToken<? extends HttpServiceHandler>, Class<?>> handlerClasses;
private final MetricsContext metricsContext;
/**
* Creates an instance that could generate {@link HttpHandler} that always binds to service Path that starts with
* the given prefix.
*/
public HttpHandlerFactory(final String pathPrefix, final MetricsContext metricsContext) {
this.metricsContext = metricsContext;
handlerClasses = CacheBuilder.newBuilder().build(
new CacheLoader<TypeToken<? extends HttpServiceHandler>, Class<?>>() {
@Override
public Class<?> load(TypeToken<? extends HttpServiceHandler> key) throws Exception {
// Generate the new class if it hasn't before and load it through a ByteCodeClassLoader.
ClassDefinition classDefinition = new HttpHandlerGenerator().generate(key, pathPrefix);
// The ClassLoader of the generated HttpHandler has CDAP system ClassLoader as parent.
// The ClassDefinition contains list of classes that should not be loaded by the generated class ClassLoader
ByteCodeClassLoader classLoader = new ByteCodeClassLoader(HttpHandlerFactory.class.getClassLoader());
classLoader.addClass(classDefinition);
return classLoader.loadClass(classDefinition.getClassName());
}
});
}
/**
* Creates an implementation of {@link HttpHandler} that delegates all public {@link javax.ws.rs.Path @Path} methods
* to the user delegate.
*/
public <T extends HttpServiceHandler> HttpHandler createHttpHandler(TypeToken<T> delegateType,
DelegatorContext<T> context) {
Class<?> cls = handlerClasses.getUnchecked(delegateType);
Preconditions.checkState(HttpHandler.class.isAssignableFrom(cls),
"Fatal error: %s is not instance of %s", cls, HttpHandler.class);
@SuppressWarnings("unchecked")
Class<? extends HttpHandler> handlerClass = (Class<? extends HttpHandler>) cls;
try {
Constructor<? extends HttpHandler> constuctor = handlerClass.getConstructor(DelegatorContext.class,
MetricsContext.class);
return constuctor.newInstance(context, metricsContext);
} catch (Exception e) {
LOG.error("Failed to instantiate generated HttpHandler {}", handlerClass, e);
throw Throwables.propagate(e);
}
}
}