gobblin.service.FlowConfigsResource.java Source code

Java tutorial

Introduction

Here is the source code for gobblin.service.FlowConfigsResource.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 gobblin.service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.linkedin.data.template.StringMap;
import com.linkedin.restli.common.ComplexResourceKey;
import com.linkedin.restli.common.EmptyRecord;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.server.CreateResponse;
import com.linkedin.restli.server.RestLiServiceException;
import com.linkedin.restli.server.UpdateResponse;
import com.linkedin.restli.server.annotations.RestLiCollection;
import com.linkedin.restli.server.resources.ComplexKeyResourceTemplate;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

import gobblin.config.ConfigBuilder;
import gobblin.configuration.ConfigurationKeys;
import gobblin.runtime.api.FlowSpec;
import gobblin.runtime.api.SpecNotFoundException;
import gobblin.runtime.spec_catalog.FlowCatalog;

/**
 * Resource for handling flow configuration requests
 */
@RestLiCollection(name = "flowconfigs", namespace = "gobblin.service", keyName = "id")
public class FlowConfigsResource extends ComplexKeyResourceTemplate<FlowId, EmptyRecord, FlowConfig> {
    private static final Logger LOG = LoggerFactory.getLogger(FlowConfigsResource.class);

    @edu.umd.cs.findbugs.annotations.SuppressWarnings("MS_SHOULD_BE_FINAL")
    public static FlowCatalog _globalFlowCatalog;

    @Inject
    @Named("flowCatalog")
    private FlowCatalog _flowCatalog;

    // For blocking use of this resource until it is ready
    @Inject
    @Named("readyToUse")
    private Boolean readyToUse = Boolean.FALSE;

    public FlowConfigsResource() {
    }

    /**
     * Logs message and throws Rest.li exception
     * @param status HTTP status code
     * @param msg error message
     * @param e exception
     */
    public void logAndThrowRestLiServiceException(HttpStatus status, String msg, Exception e) {
        if (e != null) {
            LOG.error(msg, e);
            throw new RestLiServiceException(status, msg + " cause = " + e.getMessage());
        } else {
            LOG.error(msg);
            throw new RestLiServiceException(status, msg);
        }
    }

    /**
     * Retrieve the flow configuration with the given key
     * @param key flow config id key containing group name and flow name
     * @return {@link FlowConfig} with flow configuration
     */
    @Override
    public FlowConfig get(ComplexResourceKey<FlowId, EmptyRecord> key) {
        String flowGroup = key.getKey().getFlowGroup();
        String flowName = key.getKey().getFlowName();

        LOG.info("Get called with flowGroup " + flowGroup + " flowName " + flowName);

        try {
            URI flowCatalogURI = new URI("gobblin-flow", null, "/", null, null);
            URI flowUri = new URI(flowCatalogURI.getScheme(), flowCatalogURI.getAuthority(),
                    "/" + flowGroup + "/" + flowName, null, null);
            FlowSpec spec = (FlowSpec) getFlowCatalog().getSpec(flowUri);
            FlowConfig flowConfig = new FlowConfig();
            Properties flowProps = spec.getConfigAsProperties();
            Schedule schedule = null;

            if (flowProps.containsKey(ConfigurationKeys.JOB_SCHEDULE_KEY)) {
                schedule = new Schedule();
                schedule.setCronSchedule(flowProps.getProperty(ConfigurationKeys.JOB_SCHEDULE_KEY));
            }
            if (flowProps.containsKey(ConfigurationKeys.JOB_TEMPLATE_PATH)) {
                flowConfig.setTemplateUris(flowProps.getProperty(ConfigurationKeys.JOB_TEMPLATE_PATH));
            } else if (spec.getTemplateURIs().isPresent()) {
                flowConfig.setTemplateUris(StringUtils.join(spec.getTemplateURIs().get(), ","));
            } else {
                flowConfig.setTemplateUris("NA");
            }
            if (schedule != null) {
                if (flowProps.containsKey(ConfigurationKeys.FLOW_RUN_IMMEDIATELY)) {
                    schedule.setRunImmediately(
                            Boolean.valueOf(flowProps.getProperty(ConfigurationKeys.FLOW_RUN_IMMEDIATELY)));
                }

                flowConfig.setSchedule(schedule);
            }

            // remove keys that were injected as part of flowSpec creation
            flowProps.remove(ConfigurationKeys.JOB_SCHEDULE_KEY);
            flowProps.remove(ConfigurationKeys.JOB_TEMPLATE_PATH);

            StringMap flowPropsAsStringMap = new StringMap();
            flowPropsAsStringMap.putAll(Maps.fromProperties(flowProps));

            return flowConfig.setId(new FlowId().setFlowGroup(flowGroup).setFlowName(flowName))
                    .setProperties(flowPropsAsStringMap);
        } catch (URISyntaxException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_400_BAD_REQUEST, "bad URI " + flowName, e);
        } catch (SpecNotFoundException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_404_NOT_FOUND,
                    "Flow requested does not exist: " + flowName, null);
        }

        return null;
    }

    /**
     * Build a {@link FlowSpec} from a {@link FlowConfig}
     * @param flowConfig flow configuration
     * @return {@link FlowSpec} created with attributes from flowConfig
     */
    private FlowSpec createFlowSpecForConfig(FlowConfig flowConfig) {
        ConfigBuilder configBuilder = ConfigBuilder.create()
                .addPrimitive(ConfigurationKeys.FLOW_GROUP_KEY, flowConfig.getId().getFlowGroup())
                .addPrimitive(ConfigurationKeys.FLOW_NAME_KEY, flowConfig.getId().getFlowName());

        if (flowConfig.hasSchedule()) {
            Schedule schedule = flowConfig.getSchedule();
            configBuilder.addPrimitive(ConfigurationKeys.JOB_SCHEDULE_KEY, schedule.getCronSchedule());
            configBuilder.addPrimitive(ConfigurationKeys.FLOW_RUN_IMMEDIATELY, schedule.isRunImmediately());
        }

        Config config = configBuilder.build();
        Config configWithFallback = config.withFallback(ConfigFactory.parseMap(flowConfig.getProperties()));

        URI templateURI = null;
        try {
            templateURI = new URI(flowConfig.getTemplateUris());
        } catch (URISyntaxException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_400_BAD_REQUEST,
                    "bad URI " + flowConfig.getTemplateUris(), e);
        }

        return FlowSpec.builder().withConfig(configWithFallback).withTemplate(templateURI).build();
    }

    /**
     * Create a flow configuration that the service will forward to execution instances for execution
     * @param flowConfig flow configuration
     * @return {@link CreateResponse}
     */
    @Override
    public CreateResponse create(FlowConfig flowConfig) {
        LOG.info("Create called with flowName " + flowConfig.getId().getFlowName());

        LOG.info("ReadyToUse is: " + readyToUse);
        LOG.info("FlowCatalog is: " + getFlowCatalog());

        if (!readyToUse && getFlowCatalog() == null) {
            throw new RuntimeException("Not ready for use.");
        }

        try {
            URI flowCatalogURI = new URI("gobblin-flow", null, "/", null, null);
            URI flowUri = new URI(flowCatalogURI.getScheme(), flowCatalogURI.getAuthority(),
                    "/" + flowConfig.getId().getFlowGroup() + "/" + flowConfig.getId().getFlowName(), null, null);

            if (getFlowCatalog().getSpec(flowUri) != null) {
                logAndThrowRestLiServiceException(HttpStatus.S_409_CONFLICT,
                        "Flow with the same name already exists: " + flowUri, null);
            }
        } catch (URISyntaxException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_400_BAD_REQUEST,
                    "bad URI " + flowConfig.getId().getFlowName(), e);
        } catch (SpecNotFoundException e) {
            // okay if flow does not exist
        }

        getFlowCatalog().put(createFlowSpecForConfig(flowConfig));

        return new CreateResponse(flowConfig.getId().getFlowName(), HttpStatus.S_201_CREATED);
    }

    /**
     * Update the flow configuration with the specified key. Running flows are not affected.
     * An error is raised if the flow configuration does not exist.
     * @param key composite key containing group name and flow name that identifies the flow to update
     * @param flowConfig new flow configuration
     * @return {@link UpdateResponse}
     */
    @Override
    public UpdateResponse update(ComplexResourceKey<FlowId, EmptyRecord> key, FlowConfig flowConfig) {
        String flowGroup = key.getKey().getFlowGroup();
        String flowName = key.getKey().getFlowName();
        URI flowUri = null;

        LOG.info("Update called with flowGroup " + flowGroup + " flowName " + flowName);

        if (!flowGroup.equals(flowConfig.getId().getFlowGroup())
                || !flowName.equals(flowConfig.getId().getFlowName())) {
            logAndThrowRestLiServiceException(HttpStatus.S_400_BAD_REQUEST,
                    "flowName and flowGroup cannot be changed in update", null);
        }

        try {
            URI flowCatalogURI = new URI("gobblin-flow", null, "/", null, null);
            flowUri = new URI(flowCatalogURI.getScheme(), flowCatalogURI.getAuthority(),
                    "/" + flowGroup + "/" + flowName, null, null);
            FlowSpec oldFlowSpec = (FlowSpec) getFlowCatalog().getSpec(flowUri);
            FlowSpec newFlowSpec = createFlowSpecForConfig(flowConfig);

            getFlowCatalog().put(newFlowSpec);

            return new UpdateResponse(HttpStatus.S_200_OK);
        } catch (URISyntaxException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_400_BAD_REQUEST, "bad URI " + flowUri, e);
        } catch (SpecNotFoundException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_404_NOT_FOUND,
                    "Flow does not exist: flowGroup " + flowGroup + " flowName " + flowName, null);
        }

        return null;
    }

    /**
     * Delete a configured flow. Running flows are not affected. The schedule will be removed for scheduled flows.
     * @param key composite key containing flow group and flow name that identifies the flow to remove from the
     * {@link FlowCatalog}
     * @return {@link UpdateResponse}
     */
    @Override
    public UpdateResponse delete(ComplexResourceKey<FlowId, EmptyRecord> key) {
        String flowGroup = key.getKey().getFlowGroup();
        String flowName = key.getKey().getFlowName();
        URI flowUri = null;

        LOG.info("Delete called with flowGroup " + flowGroup + " flowName " + flowName);

        try {
            URI flowCatalogURI = new URI("gobblin-flow", null, "/", null, null);
            flowUri = new URI(flowCatalogURI.getScheme(), flowCatalogURI.getAuthority(),
                    "/" + flowGroup + "/" + flowName, null, null);
            FlowSpec flowSpec = (FlowSpec) getFlowCatalog().getSpec(flowUri);

            getFlowCatalog().remove(flowUri);

            return new UpdateResponse(HttpStatus.S_200_OK);
        } catch (URISyntaxException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_400_BAD_REQUEST, "bad URI " + flowUri, e);
        } catch (SpecNotFoundException e) {
            logAndThrowRestLiServiceException(HttpStatus.S_404_NOT_FOUND,
                    "Flow does not exist: flowGroup " + flowGroup + " flowName " + flowName, null);
        }

        return null;
    }

    /***
     * This method is to workaround injection issues where Service has only one active global FlowCatalog
     * .. and is not able to inject it via RestLI bootstrap. We should remove this and make injected
     * .. FlowCatalog standard after injection works and recipe is documented here.
     * @return FlowCatalog in use.
     */
    private FlowCatalog getFlowCatalog() {
        if (null != _globalFlowCatalog) {
            return _globalFlowCatalog;
        }
        return this._flowCatalog;
    }
}