com.hortonworks.streamline.streams.service.TopologyComponentBundleResource.java Source code

Java tutorial

Introduction

Here is the source code for com.hortonworks.streamline.streams.service.TopologyComponentBundleResource.java

Source

/**
  * Copyright 2017 Hortonworks.
  *
  * 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 com.hortonworks.streamline.streams.service;

import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hortonworks.registries.common.Schema;
import com.hortonworks.streamline.common.QueryParam;
import com.hortonworks.streamline.common.exception.service.exception.request.BadRequestException;
import com.hortonworks.streamline.common.exception.service.exception.request.CustomProcessorOnlyException;
import com.hortonworks.streamline.common.exception.service.exception.request.EntityNotFoundException;
import com.hortonworks.streamline.common.util.FileUtil;
import com.hortonworks.streamline.common.util.ProxyUtil;
import com.hortonworks.streamline.common.util.WSUtils;
import com.hortonworks.streamline.streams.catalog.Namespace;
import com.hortonworks.streamline.streams.catalog.processor.CustomProcessorInfo;
import com.hortonworks.streamline.streams.catalog.service.StreamCatalogService;
import com.hortonworks.streamline.streams.catalog.topology.TopologyComponentBundle;
import com.hortonworks.streamline.streams.cluster.bundle.ComponentBundleHintProvider;
import com.hortonworks.streamline.streams.cluster.service.EnvironmentService;
import com.hortonworks.streamline.streams.layout.exception.ComponentConfigException;
import com.hortonworks.streamline.streams.runtime.CustomProcessorRuntime;
import com.hortonworks.streamline.streams.security.Roles;
import com.hortonworks.streamline.streams.security.SecurityUtil;
import com.hortonworks.streamline.streams.security.StreamlineAuthorizer;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.Subject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.CREATED;
import static javax.ws.rs.core.Response.Status.OK;

@Path("/v1/catalog/streams")
@Produces(MediaType.APPLICATION_JSON)
public class TopologyComponentBundleResource {
    private static final Logger LOG = LoggerFactory.getLogger(TopologyComponentBundleResource.class);
    public static final String JAR_FILE_PARAM_NAME = "jarFile";
    public static final String CP_INFO_PARAM_NAME = "customProcessorInfo";
    public static final String BUNDLE_JAR_FILE_PARAM_NAME = "bundleJar";
    public static final String TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME = "topologyComponentBundle";
    private final StreamlineAuthorizer authorizer;
    private final StreamCatalogService catalogService;
    private final EnvironmentService environmentService;
    private final Subject subject;
    private final ProxyUtil<ComponentBundleHintProvider> hintProviderProxyUtil;

    public TopologyComponentBundleResource(StreamlineAuthorizer authorizer, StreamCatalogService catalogService,
            EnvironmentService environmentService, Subject subject) {
        this.authorizer = authorizer;
        this.catalogService = catalogService;
        this.environmentService = environmentService;
        this.subject = subject;
        this.hintProviderProxyUtil = new ProxyUtil<>(ComponentBundleHintProvider.class);
    }

    /**
     * List all component bundle types supported by streams builder
     * <p>
     * GET api/v1/catalog/streams/componentbundles
     * </p>
     * <pre>
     *{"responseCode":1000,"responseMessage":"Success","entities":["SOURCE","PROCESSOR","LINK","SINK","ACTION","TRANSFORM"]}
     * </pre>
     */
    @GET
    @Path("/componentbundles")
    @Timed
    public Response listTopologyComponentBundleTypes(@Context SecurityContext securityContext) {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_USER);
        Collection<TopologyComponentBundle.TopologyComponentType> topologyComponents = catalogService
                .listTopologyComponentBundleTypes();
        if (topologyComponents != null) {
            return WSUtils.respondEntities(topologyComponents, OK);
        }

        throw EntityNotFoundException.byFilter("");
    }

    /**
     * List all component bundles registered for a type(SOURCE, SINK, etc) or only the ones that match query params
     * <p>
     * GET api/v1/catalog/streams/componentbundles/SOURCE?name=kafkaSpoutComponent
     * </p>
     */
    @GET
    @Path("/componentbundles/{component}")
    @Timed
    public Response listTopologyComponentBundlesForTypeWithFilter(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            @Context UriInfo uriInfo, @Context SecurityContext securityContext) {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_USER);
        List<QueryParam> queryParams;
        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
        queryParams = WSUtils.buildQueryParameters(params);
        Collection<TopologyComponentBundle> topologyComponentBundles = catalogService
                .listTopologyComponentBundlesForTypeWithFilter(componentType, queryParams);
        if (topologyComponentBundles != null) {
            return WSUtils.respondEntities(topologyComponentBundles, OK);
        }

        throw EntityNotFoundException.byFilter(queryParams.toString());
    }

    /**
     * Get component bundle registered for a type(SOURCE, SINK, etc) matching the id
     * <p>
     * GET api/v1/catalog/streams/componentbundles/SOURCE/5
     * </p>
     */
    @GET
    @Path("/componentbundles/{component}/{id}")
    @Timed
    @Produces(MediaType.APPLICATION_JSON)
    public Response getTopologyComponentBundleById(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            @PathParam("id") Long id, @Context SecurityContext securityContext) {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_USER);
        TopologyComponentBundle result = catalogService.getTopologyComponentBundle(id);
        if (result != null) {
            return WSUtils.respondEntity(result, OK);
        }

        throw EntityNotFoundException.byId(id.toString());
    }

    /**
     * Add a new topology component bundle.
     * <p>
     * curl -sS -X POST -i -F topologyComponentBundle=@kafka-topology-bundle -F bundleJar=@/Users/pshah/dev/IoTaS/streams/runners/storm/layout/target/streams-layout-storm-0.1.0-SNAPSHOT.jar  http://localhost:8080/api/v1/catalog/streams/componentbundles/SOURCE/
     * </p>
     */
    @POST
    @Path("/componentbundles/{component}")
    @Timed
    public Response addTopologyComponentBundle(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            FormDataMultiPart form, @Context SecurityContext securityContext)
            throws IOException, ComponentConfigException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        InputStream bundleJar = null;
        try {
            String bundleJsonString = this.getFormDataFromMultiPartRequestAs(String.class, form,
                    TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME);
            TopologyComponentBundle topologyComponentBundle = new ObjectMapper().readValue(bundleJsonString,
                    TopologyComponentBundle.class);
            if (topologyComponentBundle == null) {
                LOG.debug(TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME + " is missing or invalid");
                throw BadRequestException.missingParameter(TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME);
            }
            List<QueryParam> queryParams;
            MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
            params.putSingle(TopologyComponentBundle.STREAMING_ENGINE,
                    topologyComponentBundle.getStreamingEngine());
            params.putSingle(TopologyComponentBundle.SUB_TYPE, topologyComponentBundle.getSubType());
            queryParams = WSUtils.buildQueryParameters(params);
            Collection<TopologyComponentBundle> topologyComponentBundles = catalogService
                    .listTopologyComponentBundlesForTypeWithFilter(componentType, queryParams);
            if (topologyComponentBundles != null && !topologyComponentBundles.isEmpty()) {
                LOG.warn("Received a post request for an already registered bundle. Not creating entity for "
                        + topologyComponentBundle);
                return WSUtils.respondEntity(topologyComponentBundle, CONFLICT);
            }
            if (!topologyComponentBundle.getBuiltin()) {
                bundleJar = this.getFormDataFromMultiPartRequestAs(InputStream.class, form,
                        BUNDLE_JAR_FILE_PARAM_NAME);
                if (bundleJar == null) {
                    LOG.debug(BUNDLE_JAR_FILE_PARAM_NAME + " is missing or invalid");
                    throw BadRequestException.missingParameter(BUNDLE_JAR_FILE_PARAM_NAME);
                }
            }
            validateTopologyBundle(topologyComponentBundle);
            topologyComponentBundle.setType(componentType);
            TopologyComponentBundle createdBundle = catalogService
                    .addTopologyComponentBundle(topologyComponentBundle, bundleJar);
            return WSUtils.respondEntity(createdBundle, CREATED);
        } catch (Exception e) {
            LOG.debug("Error occured while adding topology component bundle", e);
            throw e;
        } finally {
            try {
                if (bundleJar != null) {
                    bundleJar.close();
                }
            } catch (IOException e) {
                LOG.debug("Error while closing jar file stream", e);
            }
        }
    }

    /**
     * Update a topology component bundle by id.
     * <p>
     * curl -sS -X PUT -i -F topologyComponentBundle=@kafka-topology-bundle -F bundleJar=@/Users/pshah/dev/IoTaS/streams/runners/storm/layout/target/streams-layout-storm-0.1.0-SNAPSHOT.jar  http://localhost:8080/api/v1/catalog/streams/componentbundles/SOURCE/1
     * </p>
     */
    @PUT
    @Path("/componentbundles/{component}/{id}")
    @Timed
    public Response addOrUpdateTopologyComponentBundleById(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            @PathParam("id") Long id, FormDataMultiPart form, @Context SecurityContext securityContext)
            throws IOException, ComponentConfigException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        InputStream bundleJar = null;
        try {
            String bundleJsonString = this.getFormDataFromMultiPartRequestAs(String.class, form,
                    TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME);
            TopologyComponentBundle topologyComponentBundle = new ObjectMapper().readValue(bundleJsonString,
                    TopologyComponentBundle.class);
            if (topologyComponentBundle == null) {
                LOG.debug(TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME + " is missing or invalid");
                throw BadRequestException.missingParameter(TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME);
            }
            if (!topologyComponentBundle.getBuiltin()) {
                bundleJar = this.getFormDataFromMultiPartRequestAs(InputStream.class, form,
                        BUNDLE_JAR_FILE_PARAM_NAME);
                if (bundleJar == null) {
                    LOG.debug(BUNDLE_JAR_FILE_PARAM_NAME + " is missing or invalid");
                    throw BadRequestException.missingParameter(BUNDLE_JAR_FILE_PARAM_NAME);
                }
            }
            validateTopologyBundle(topologyComponentBundle);
            topologyComponentBundle.setType(componentType);
            TopologyComponentBundle updatedBundle = catalogService.addOrUpdateTopologyComponentBundle(id,
                    topologyComponentBundle, bundleJar);
            return WSUtils.respondEntity(updatedBundle, OK);
        } catch (Exception e) {
            LOG.debug("Error occured while updating topology component bundle", e);
            throw e;
        } finally {
            try {
                if (bundleJar != null) {
                    bundleJar.close();
                }
            } catch (IOException e) {
                LOG.debug("Error while closing jar file stream", e);
            }
        }
    }

    /**
     * Update a topology component bundle by trying to find id using type, sub type and streaming engine.
     * <p>
     * curl -sS -X PUT -i -F topologyComponentBundle=@kafka-topology-bundle -F bundleJar=@/Users/pshah/dev/IoTaS/streams/runners/storm/layout/target/streams-layout-storm-0.1.0-SNAPSHOT.jar  http://localhost:8080/api/v1/catalog/streams/componentbundles/SOURCE/
     * </p>
     */
    @PUT
    @Path("/componentbundles/{component}")
    @Timed
    public Response addOrUpdateTopologyComponentBundle(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            FormDataMultiPart form, @Context SecurityContext securityContext)
            throws IOException, ComponentConfigException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        InputStream bundleJar = null;
        try {
            String bundleJsonString = this.getFormDataFromMultiPartRequestAs(String.class, form,
                    TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME);
            TopologyComponentBundle topologyComponentBundle = new ObjectMapper().readValue(bundleJsonString,
                    TopologyComponentBundle.class);
            if (topologyComponentBundle == null) {
                LOG.debug(TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME + " is missing or invalid");
                throw BadRequestException.missingParameter(TOPOLOGY_COMPONENT_BUNDLE_PARAM_NAME);
            }
            if (!componentType.equals(topologyComponentBundle.getType())) {
                String message = "Cant update a " + topologyComponentBundle.getType() + " on " + componentType
                        + " endpoint. Verify PUT request";
                LOG.debug(message);
                throw BadRequestException.message(message);
            }
            if (!topologyComponentBundle.getBuiltin()) {
                bundleJar = this.getFormDataFromMultiPartRequestAs(InputStream.class, form,
                        BUNDLE_JAR_FILE_PARAM_NAME);
                if (bundleJar == null) {
                    LOG.debug(BUNDLE_JAR_FILE_PARAM_NAME + " is missing or invalid");
                    throw BadRequestException.missingParameter(BUNDLE_JAR_FILE_PARAM_NAME);
                }
            }
            validateTopologyBundle(topologyComponentBundle);
            List<QueryParam> queryParams = new ArrayList<>();
            queryParams.add(new QueryParam(TopologyComponentBundle.STREAMING_ENGINE,
                    topologyComponentBundle.getStreamingEngine()));
            queryParams.add(new QueryParam(TopologyComponentBundle.TYPE, topologyComponentBundle.getType().name()));
            queryParams.add(new QueryParam(TopologyComponentBundle.SUB_TYPE, topologyComponentBundle.getSubType()));
            Collection<TopologyComponentBundle> existing = catalogService
                    .listTopologyComponentBundlesForTypeWithFilter(componentType, queryParams);
            if (existing != null && existing.size() == 1) {
                TopologyComponentBundle updatedBundle = catalogService.addOrUpdateTopologyComponentBundle(
                        existing.iterator().next().getId(), topologyComponentBundle, bundleJar);
                return WSUtils.respondEntity(updatedBundle, OK);
            } else {
                String message = "Cant update because lookup using streaming engine, type and subtype returned either no existing bundle or more than one";
                LOG.debug(message);
                throw BadRequestException.message(message);
            }
        } catch (Exception e) {
            LOG.debug("Error occured while updating topology component bundle", e);
            throw e;
        } finally {
            try {
                if (bundleJar != null) {
                    bundleJar.close();
                }
            } catch (IOException e) {
                LOG.debug("Error while closing jar file stream", e);
            }
        }
    }

    /**
     * Delete a topology component bundle.
     * <p>
     * curl -sS -X DELETE -i   http://localhost:8080/api/v1/catalog/streams/componentbundles/SOURCE/3
     * </p>
     */
    @DELETE
    @Path("/componentbundles/{component}/{id}")
    @Timed
    public Response removeTopologyComponentBundle(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            @PathParam("id") Long id, @Context SecurityContext securityContext) throws IOException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        TopologyComponentBundle removedTopologyComponentBundle = catalogService.removeTopologyComponentBundle(id);
        if (removedTopologyComponentBundle != null) {
            return WSUtils.respondEntity(removedTopologyComponentBundle, OK);
        }

        throw EntityNotFoundException.byId(id.toString());
    }

    @Timed
    @GET
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    @Path("/componentbundles/{processor}/custom/{fileName}")
    public Response downloadCustomProcessorFile(
            @PathParam("processor") TopologyComponentBundle.TopologyComponentType componentType,
            @PathParam("fileName") String fileName, @Context SecurityContext securityContext) throws IOException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_USER);
        if (!TopologyComponentBundle.TopologyComponentType.PROCESSOR.equals(componentType)) {
            throw new CustomProcessorOnlyException();
        }
        final InputStream inputStream = catalogService.getFileFromJarStorage(fileName);
        if (inputStream != null) {
            StreamingOutput streamOutput = WSUtils.wrapWithStreamingOutput(inputStream);
            return Response.ok(streamOutput).build();
        }

        throw EntityNotFoundException.byId(fileName);
    }

    /**
     * List custom processors matching specific query parameter filters.
     */
    @GET
    @Path("/componentbundles/{processor}/custom")
    @Timed
    public Response listCustomProcessorsWithFilters(
            @PathParam("processor") TopologyComponentBundle.TopologyComponentType componentType,
            @Context UriInfo uriInfo, @Context SecurityContext securityContext) throws IOException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_USER);
        if (!TopologyComponentBundle.TopologyComponentType.PROCESSOR.equals(componentType)) {
            throw new CustomProcessorOnlyException();
        }
        List<QueryParam> queryParams;
        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
        queryParams = WSUtils.buildQueryParameters(params);
        Collection<CustomProcessorInfo> customProcessorInfos = catalogService
                .listCustomProcessorsFromBundleWithFilter(queryParams);
        if (customProcessorInfos != null) {
            return WSUtils.respondEntities(customProcessorInfos, OK);
        }

        throw EntityNotFoundException.byFilter(queryParams.toString());
    }

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Path("/componentbundles/{processor}/custom")
    @Timed
    public Response addCustomProcessor(
            @PathParam("processor") TopologyComponentBundle.TopologyComponentType componentType,
            FormDataMultiPart form, @Context SecurityContext securityContext)
            throws IOException, ComponentConfigException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        if (!TopologyComponentBundle.TopologyComponentType.PROCESSOR.equals(componentType)) {
            throw new CustomProcessorOnlyException();
        }
        InputStream jarFile = null;
        try {
            jarFile = this.getFormDataFromMultiPartRequestAs(InputStream.class, form, JAR_FILE_PARAM_NAME);
            byte[] jarBytes = getBytesForInputStream(jarFile);
            String customProcessorInfoStr = this.getFormDataFromMultiPartRequestAs(String.class, form,
                    CP_INFO_PARAM_NAME);
            String missingParam = (jarBytes == null ? JAR_FILE_PARAM_NAME
                    : (customProcessorInfoStr == null ? CP_INFO_PARAM_NAME : null));
            if (missingParam != null) {
                LOG.debug(missingParam + " is missing or invalid while adding custom processor");
                throw BadRequestException.missingParameter(missingParam);
            }
            CustomProcessorInfo customProcessorInfo = new ObjectMapper().readValue(customProcessorInfoStr,
                    CustomProcessorInfo.class);
            if (!isValidOutputSchema(customProcessorInfo.getOutputSchema())) {
                LOG.debug("Output schema is missing while adding custom processor");
                throw BadRequestException.missingParameter(CustomProcessorInfo.OUTPUT_SCHEMA);
            }
            if (!verifyCustomProcessorImplFromJar(new ByteArrayInputStream(jarBytes), customProcessorInfo)) {
                String message = "Custom Processor jar file is missing customProcessorImpl class "
                        + customProcessorInfo.getCustomProcessorImpl();
                LOG.debug(message);
                throw BadRequestException.message(message);
            }
            CustomProcessorInfo createdCustomProcessor = catalogService
                    .addCustomProcessorInfoAsBundle(customProcessorInfo, new ByteArrayInputStream(jarBytes));
            return WSUtils.respondEntity(createdCustomProcessor, CREATED);
        } catch (Exception e) {
            LOG.debug("Exception thrown while trying to add a custom processor", e);
            throw e;
        } finally {
            try {
                jarFile.close();
            } catch (IOException e) {
                LOG.debug("Error while closing jar file stream", e);
            }
        }
    }

    @PUT
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Path("/componentbundles/{processor}/custom")
    @Timed
    public Response updateCustomProcessor(
            @PathParam("processor") TopologyComponentBundle.TopologyComponentType componentType,
            FormDataMultiPart form, @Context SecurityContext securityContext)
            throws IOException, ComponentConfigException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        if (!TopologyComponentBundle.TopologyComponentType.PROCESSOR.equals(componentType)) {
            throw new CustomProcessorOnlyException();
        }
        InputStream jarFile = null;
        try {
            jarFile = this.getFormDataFromMultiPartRequestAs(InputStream.class, form, JAR_FILE_PARAM_NAME);
            byte[] jarBytes = getBytesForInputStream(jarFile);
            String customProcessorInfoStr = this.getFormDataFromMultiPartRequestAs(String.class, form,
                    CP_INFO_PARAM_NAME);
            String missingParam = (jarBytes == null ? JAR_FILE_PARAM_NAME
                    : (customProcessorInfoStr == null ? CP_INFO_PARAM_NAME : null));
            if (missingParam != null) {
                LOG.debug(missingParam + " is missing or invalid while adding/updating custom processor");
                throw BadRequestException.missingParameter(missingParam);
            }
            CustomProcessorInfo customProcessorInfo = new ObjectMapper().readValue(customProcessorInfoStr,
                    CustomProcessorInfo.class);
            if (!isValidOutputSchema(customProcessorInfo.getOutputSchema())) {
                LOG.debug("Output schema is missing while updating custom processor");
                throw BadRequestException.missingParameter(CustomProcessorInfo.OUTPUT_SCHEMA);
            }
            if (!verifyCustomProcessorImplFromJar(new ByteArrayInputStream(jarBytes), customProcessorInfo)) {
                String message = "Custom Processor jar file is missing customProcessorImpl class "
                        + customProcessorInfo.getCustomProcessorImpl();
                LOG.debug(message);
                throw BadRequestException.message(message);
            }
            CustomProcessorInfo updatedCustomProcessor = catalogService
                    .updateCustomProcessorInfoAsBundle(customProcessorInfo, new ByteArrayInputStream(jarBytes));
            return WSUtils.respondEntity(updatedCustomProcessor, OK);
        } catch (Exception e) {
            LOG.debug("Exception thrown while trying to add/update a custom processor", e);
            throw e;
        } finally {
            try {
                jarFile.close();
            } catch (IOException e) {
                LOG.debug("Error while closing jar file stream", e);
            }
        }
    }

    @DELETE
    @Path("/componentbundles/{processor}/custom/{name}")
    @Timed
    public Response removeCustomProcessorInfo(
            @PathParam("processor") TopologyComponentBundle.TopologyComponentType componentType,
            @PathParam("name") String name, @Context SecurityContext securityContext) throws IOException {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_ADMIN);
        if (!TopologyComponentBundle.TopologyComponentType.PROCESSOR.equals(componentType)) {
            throw new CustomProcessorOnlyException();
        }
        CustomProcessorInfo removedCustomProcessorInfo = catalogService.removeCustomProcessorInfoAsBundle(name);
        if (removedCustomProcessorInfo != null) {
            return WSUtils.respondEntity(removedCustomProcessorInfo, OK);
        }

        throw EntityNotFoundException.byName(name);
    }

    @GET
    @Path("/componentbundles/{component}/{id}/hints/namespaces/{namespaceId}")
    @Timed
    public Response getFieldHints(
            @PathParam("component") TopologyComponentBundle.TopologyComponentType componentType,
            @PathParam("id") Long id, @PathParam("namespaceId") Long namespaceId,
            @Context SecurityContext securityContext) throws Exception {
        SecurityUtil.checkRole(authorizer, securityContext, Roles.ROLE_TOPOLOGY_COMPONENT_BUNDLE_USER);
        TopologyComponentBundle bundle = catalogService.getTopologyComponentBundle(id);
        if (bundle == null || !bundle.getType().equals(componentType)) {
            throw EntityNotFoundException.byId("component bundle id: " + id + " with type: " + componentType);
        }

        String providerClass = bundle.getFieldHintProviderClass();
        if (StringUtils.isNotEmpty(providerClass)) {
            ComponentBundleHintProvider provider;
            if (bundle.getBuiltin()) {
                Class<ComponentBundleHintProvider> clazz = (Class<ComponentBundleHintProvider>) Class
                        .forName(providerClass);
                provider = clazz.newInstance();
            } else {
                provider = hintProviderProxyUtil.loadClassFromJar(bundle.getBundleJar(), providerClass);
            }

            provider.init(environmentService);

            Namespace namespace = environmentService.getNamespace(namespaceId);
            if (namespace == null) {
                throw EntityNotFoundException.byId("namespace id: " + namespaceId);
            }

            Map<Long, ComponentBundleHintProvider.BundleHintsResponse> hints = provider.provide(namespace,
                    securityContext, subject);
            return WSUtils.respondEntity(hints, OK);
        } else {
            return WSUtils.respondEntity(Collections.emptyMap(), OK);
        }
    }

    private void validateTopologyBundle(TopologyComponentBundle topologyComponentBundle) {
        Optional<String> missingParam = Optional.empty();
        if (StringUtils.isEmpty(topologyComponentBundle.getName())) {
            missingParam = Optional.of(TopologyComponentBundle.NAME);
        }
        if (StringUtils.isEmpty(topologyComponentBundle.getStreamingEngine())) {
            missingParam = Optional.of(TopologyComponentBundle.STREAMING_ENGINE);
        }
        if (StringUtils.isEmpty(topologyComponentBundle.getSubType())) {
            missingParam = Optional.of(TopologyComponentBundle.SUB_TYPE);
        }
        if (StringUtils.isEmpty(topologyComponentBundle.getTransformationClass())) {
            missingParam = Optional.of(TopologyComponentBundle.TRANSFORMATION_CLASS);
        }
        if (topologyComponentBundle.getTopologyComponentUISpecification() == null) {
            missingParam = Optional.of(TopologyComponentBundle.UI_SPECIFICATION);
        }

        if (missingParam.isPresent()) {
            throw BadRequestException.missingParameter(missingParam.get());
        }
    }

    private <T> T getFormDataFromMultiPartRequestAs(Class<T> clazz, FormDataMultiPart form, String paramName) {
        T result = null;
        try {
            FormDataBodyPart part = form.getField(paramName);
            if (part != null) {
                result = part.getValueAs(clazz);
            }
        } catch (Exception e) {
            LOG.debug("Cannot get param " + paramName + " as" + clazz + " from multipart form");
        }
        return result;
    }

    private boolean verifyCustomProcessorImplFromJar(InputStream inputStream,
            CustomProcessorInfo customProcessorInfo) {
        final File tmpFile;
        boolean result = false;
        try {
            tmpFile = FileUtil.writeInputStreamToTempFile(inputStream, ".jar");
            Collection<String> impls = ProxyUtil
                    .canonicalNames(ProxyUtil.loadAllClassesFromJar(tmpFile, CustomProcessorRuntime.class));
            if ((impls != null) && impls.contains(customProcessorInfo.getCustomProcessorImpl())) {
                result = true;
            }
        } catch (IOException e) {
            //swallow to return false
        }
        return result;
    }

    private byte[] getBytesForInputStream(InputStream inputStream) {
        byte[] result = null;
        if (inputStream != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                org.apache.commons.io.IOUtils.copy(inputStream, baos);
                result = baos.toByteArray();
            } catch (IOException e) {
                // print, swallow and return null
                LOG.info("Error reading CP jar file", e);
            }

        }
        return result;
    }

    private boolean isValidOutputSchema(Schema outputSchema) {
        if (outputSchema == null || outputSchema.getFields() == null || outputSchema.getFields().isEmpty()) {
            return false;
        }
        return true;
    }
}