/* * 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.services; import co.cask.cdap.api.Resources; import co.cask.cdap.api.metrics.MetricsContext; import co.cask.cdap.api.service.Service; import co.cask.cdap.api.service.ServiceConfigurer; import co.cask.cdap.api.service.ServiceSpecification; import co.cask.cdap.api.service.http.HttpServiceContext; import co.cask.cdap.api.service.http.HttpServiceHandler; import co.cask.cdap.api.service.http.HttpServiceHandlerSpecification; import co.cask.cdap.common.metrics.NoOpMetricsCollectionService; import co.cask.cdap.internal.app.DefaultPluginConfigurer; import co.cask.cdap.internal.app.runtime.artifact.ArtifactRepository; import co.cask.cdap.internal.app.runtime.plugin.PluginInstantiator; import co.cask.cdap.internal.app.runtime.service.http.DelegatorContext; import co.cask.cdap.internal.app.runtime.service.http.HttpHandlerFactory; import co.cask.cdap.proto.Id; import co.cask.http.HttpHandler; import co.cask.http.NettyHttpService; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; import org.apache.twill.common.Cancellable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A default implementation of {@link ServiceConfigurer}. */ public class DefaultServiceConfigurer extends DefaultPluginConfigurer implements ServiceConfigurer { private static final Logger LOG = LoggerFactory.getLogger(DefaultServiceConfigurer.class); private final String className; private final Id.Artifact artifactId; private final ArtifactRepository artifactRepository; private final PluginInstantiator pluginInstantiator; private String name; private String description; private List<HttpServiceHandler> handlers; private Resources resources; private int instances; /** * Create an instance of {@link DefaultServiceConfigurer} */ public DefaultServiceConfigurer(Service service, Id.Namespace namespace, Id.Artifact artifactId, ArtifactRepository artifactRepository, PluginInstantiator pluginInstantiator) { super(namespace, artifactId, artifactRepository, pluginInstantiator); this.className = service.getClass().getName(); this.name = service.getClass().getSimpleName(); this.description = ""; this.handlers = Lists.newArrayList(); this.resources = new Resources(); this.instances = 1; this.artifactId = artifactId; this.artifactRepository = artifactRepository; this.pluginInstantiator = pluginInstantiator; } @Override public void setName(String name) { this.name = name; } @Override public void setDescription(String description) { this.description = description; } @Override public void addHandlers(Iterable<? extends HttpServiceHandler> serviceHandlers) { Iterables.addAll(handlers, serviceHandlers); } @Override public void setInstances(int instances) { Preconditions.checkArgument(instances > 0, "Instances must be > 0."); this.instances = instances; } @Override public void setResources(Resources resources) { Preconditions.checkArgument(resources != null, "Resources cannot be null."); this.resources = resources; } public ServiceSpecification createSpecification() { Map<String, HttpServiceHandlerSpecification> handleSpecs = createHandlerSpecs(handlers); return new ServiceSpecification(className, name, description, handleSpecs, resources, instances); } /** * Constructs HttpServiceSpecifications for each of the handlers in the {@param handlers} list. * Also performs verifications on these handlers (that a NettyHttpService can be constructed from them). */ private Map<String, HttpServiceHandlerSpecification> createHandlerSpecs(List<? extends HttpServiceHandler> handlers) { verifyHandlers(handlers); Map<String, HttpServiceHandlerSpecification> handleSpecs = Maps.newHashMap(); for (HttpServiceHandler handler : handlers) { DefaultHttpServiceHandlerConfigurer configurer = new DefaultHttpServiceHandlerConfigurer( handler, deployNamespace, artifactId, artifactRepository, pluginInstantiator); handler.configure(configurer); HttpServiceHandlerSpecification spec = configurer.createSpecification(); Preconditions.checkArgument(!handleSpecs.containsKey(spec.getName()), "Handler with name %s already existed.", spec.getName()); handleSpecs.put(spec.getName(), spec); addStreams(configurer.getStreams()); addDatasetModules(configurer.getDatasetModules()); addDatasetSpecs(configurer.getDatasetSpecs()); addPlugins(configurer.getPlugins()); } return handleSpecs; } private void verifyHandlers(List<? extends HttpServiceHandler> handlers) { Preconditions.checkArgument(!Iterables.isEmpty(handlers), "Service %s should have at least one handler", name); try { List<HttpHandler> httpHandlers = Lists.newArrayList(); for (HttpServiceHandler handler : handlers) { httpHandlers.add(createHttpHandler(handler)); } // Constructs a NettyHttpService, to verify that the handlers passed in by the user are valid. NettyHttpService.builder() .addHttpHandlers(httpHandlers) .build(); } catch (Throwable t) { String errMessage = String.format("Invalid handlers in service: %s.", name); LOG.error(errMessage, t); throw new IllegalArgumentException(errMessage, t); } } private <T extends HttpServiceHandler> HttpHandler createHttpHandler(T handler) { MetricsContext noOpsMetricsContext = new NoOpMetricsCollectionService().getContext(new HashMap<String, String>()); HttpHandlerFactory factory = new HttpHandlerFactory("", noOpsMetricsContext); @SuppressWarnings("unchecked") TypeToken<T> type = (TypeToken<T>) TypeToken.of(handler.getClass()); return factory.createHttpHandler(type, new VerificationDelegateContext<>(handler)); } private static final class VerificationDelegateContext<T extends HttpServiceHandler> implements DelegatorContext<T> { private final T handler; private VerificationDelegateContext(T handler) { this.handler = handler; } @Override public T getHandler() { return handler; } @Override public HttpServiceContext getServiceContext() { // Never used. (It's only used during server runtime, which we don't verify). return null; } @Override public Cancellable capture() { return new Cancellable() { @Override public void cancel() { // no-op } }; } } }