/**
* 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 org.apache.sqoop.handler;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.apache.log4j.Logger;
import org.apache.sqoop.audit.AuditLoggerManager;
import org.apache.sqoop.common.Direction;
import org.apache.sqoop.common.SqoopException;
import org.apache.sqoop.connector.ConnectorManager;
import org.apache.sqoop.connector.spi.SqoopConnector;
import org.apache.sqoop.driver.Driver;
import org.apache.sqoop.driver.JobManager;
import org.apache.sqoop.json.JSONUtils;
import org.apache.sqoop.json.JobBean;
import org.apache.sqoop.json.JobsBean;
import org.apache.sqoop.json.JsonBean;
import org.apache.sqoop.json.SubmissionBean;
import org.apache.sqoop.json.ValidationResultBean;
import org.apache.sqoop.model.ConfigUtils;
import org.apache.sqoop.model.MDriverConfig;
import org.apache.sqoop.model.MFromConfig;
import org.apache.sqoop.model.MJob;
import org.apache.sqoop.model.MPersistableEntity;
import org.apache.sqoop.model.MResource;
import org.apache.sqoop.model.MSubmission;
import org.apache.sqoop.model.MToConfig;
import org.apache.sqoop.repository.Repository;
import org.apache.sqoop.repository.RepositoryManager;
import org.apache.sqoop.request.HttpEventContext;
import org.apache.sqoop.security.Authorization.AuthorizationEngine;
import org.apache.sqoop.security.AuthorizationManager;
import org.apache.sqoop.server.RequestContext;
import org.apache.sqoop.server.RequestHandler;
import org.apache.sqoop.server.common.ServerError;
import org.apache.sqoop.validation.ConfigValidationResult;
import org.apache.sqoop.validation.Status;
import org.json.simple.JSONObject;
public class JobRequestHandler implements RequestHandler {
/** enum for representing the actions supported on the job resource*/
enum JobAction {
ENABLE("enable"),
DISABLE("disable"),
START("start"),
STOP("stop"),
;
JobAction(String name) {
this.name = name;
}
String name;
public static JobAction fromString(String name) {
if (name != null) {
for (JobAction action : JobAction.values()) {
if (name.equalsIgnoreCase(action.name)) {
return action;
}
}
}
return null;
}
}
private static final Logger LOG = Logger.getLogger(JobRequestHandler.class);
static final String JOBS_PATH = "jobs";
static final String JOB_PATH = "job";
static final String STATUS = "status";
public JobRequestHandler() {
LOG.info("JobRequestHandler initialized");
}
@Override
public JsonBean handleEvent(RequestContext ctx) {
LOG.info("Got job request");
switch (ctx.getMethod()) {
case GET:
if (STATUS.equals(ctx.getLastURLElement())) {
return getJobStatus(ctx);
}
return getJobs(ctx);
case POST:
return createUpdateJob(ctx, true);
case PUT:
JobAction action = JobAction.fromString(ctx.getLastURLElement());
if (action != null) {
switch (action) {
case ENABLE:
return enableJob(ctx, true);
case DISABLE:
return enableJob(ctx, false);
case START:
return startJob(ctx);
case STOP:
return stopJob(ctx);
}
}
return createUpdateJob(ctx, false);
case DELETE:
return deleteJob(ctx);
}
return null;
}
/**
* Delete job from repository.
*
* @param ctx
* Context object
* @return Empty bean
*/
private JsonBean deleteJob(RequestContext ctx) {
Repository repository = RepositoryManager.getInstance().getRepository();
String jobIdentifier = ctx.getLastURLElement();
long jobId = HandlerUtils.getJobIdFromIdentifier(jobIdentifier, repository);
// Authorization check
AuthorizationEngine.deleteJob(String.valueOf(jobId));
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "delete", "job", jobIdentifier);
repository.deleteJob(jobId);
MResource resource = new MResource(String.valueOf(jobId), MResource.TYPE.JOB);
AuthorizationManager.getAuthorizationHandler().removeResource(resource);
return JsonBean.EMPTY_BEAN;
}
/**
* Update or create job in repository.
*
* @param ctx
* Context object
* @return Validation bean object
*/
private JsonBean createUpdateJob(RequestContext ctx, boolean create) {
Repository repository = RepositoryManager.getInstance().getRepository();
JobBean bean = new JobBean();
try {
JSONObject json = JSONUtils.parse(ctx.getRequest().getReader());
bean.restore(json);
} catch (IOException e) {
throw new SqoopException(ServerError.SERVER_0003, "Can't read request content", e);
}
String username = ctx.getUserName();
// Get job object
List<MJob> jobs = bean.getJobs();
if (jobs.size() != 1) {
throw new SqoopException(ServerError.SERVER_0003, "Expected one job but got " + jobs.size());
}
// Job object
MJob postedJob = jobs.get(0);
// Authorization check
if (create) {
AuthorizationEngine.createJob(String.valueOf(postedJob.getFromLinkId()),
String.valueOf(postedJob.getToLinkId()));
} else {
AuthorizationEngine.updateJob(String.valueOf(postedJob.getFromLinkId()),
String.valueOf(postedJob.getToLinkId()),
String.valueOf(postedJob.getPersistenceId()));
}
// Verify that user is not trying to spoof us
MFromConfig fromConfig = ConnectorManager.getInstance()
.getConnectorConfigurable(postedJob.getConnectorId(Direction.FROM)).getFromConfig();
MToConfig toConfig = ConnectorManager.getInstance()
.getConnectorConfigurable(postedJob.getConnectorId(Direction.TO)).getToConfig();
MDriverConfig driverConfig = Driver.getInstance().getDriver().getDriverConfig();
if (!fromConfig.equals(postedJob.getJobConfig(Direction.FROM))
|| !driverConfig.equals(postedJob.getDriverConfig())
|| !toConfig.equals(postedJob.getJobConfig(Direction.TO))) {
throw new SqoopException(ServerError.SERVER_0003, "Detected incorrect config structure");
}
// if update get the job id from the request URI
if (!create) {
String jobIdentifier = ctx.getLastURLElement();
// support jobName or jobId for the api
long jobId = HandlerUtils.getJobIdFromIdentifier(jobIdentifier, repository);
if (postedJob.getPersistenceId() == MPersistableEntity.PERSISTANCE_ID_DEFAULT) {
MJob existingJob = repository.findJob(jobId);
postedJob.setPersistenceId(existingJob.getPersistenceId());
}
}
// Corresponding connectors for this
SqoopConnector fromConnector = ConnectorManager.getInstance().getSqoopConnector(
postedJob.getConnectorId(Direction.FROM));
SqoopConnector toConnector = ConnectorManager.getInstance().getSqoopConnector(
postedJob.getConnectorId(Direction.TO));
if (!fromConnector.getSupportedDirections().contains(Direction.FROM)) {
throw new SqoopException(ServerError.SERVER_0004, "Connector "
+ fromConnector.getClass().getCanonicalName() + " does not support FROM direction.");
}
if (!toConnector.getSupportedDirections().contains(Direction.TO)) {
throw new SqoopException(ServerError.SERVER_0004, "Connector "
+ toConnector.getClass().getCanonicalName() + " does not support TO direction.");
}
// Validate user supplied data
ConfigValidationResult fromConfigValidator = ConfigUtils.validateConfigs(
postedJob.getJobConfig(Direction.FROM).getConfigs(),
fromConnector.getJobConfigurationClass(Direction.FROM));
ConfigValidationResult toConfigValidator = ConfigUtils.validateConfigs(
postedJob.getJobConfig(Direction.TO).getConfigs(),
toConnector.getJobConfigurationClass(Direction.TO));
ConfigValidationResult driverConfigValidator = ConfigUtils.validateConfigs(postedJob
.getDriverConfig().getConfigs(), Driver.getInstance().getDriverJobConfigurationClass());
Status finalStatus = Status.getWorstStatus(fromConfigValidator.getStatus(),
toConfigValidator.getStatus(), driverConfigValidator.getStatus());
// Return back validations in all cases
ValidationResultBean validationResultBean = new ValidationResultBean(fromConfigValidator, toConfigValidator, driverConfigValidator);
// If we're good enough let's perform the action
if (finalStatus.canProceed()) {
if (create) {
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "create", "job",
String.valueOf(postedJob.getPersistenceId()));
postedJob.setCreationUser(username);
postedJob.setLastUpdateUser(username);
repository.createJob(postedJob);
validationResultBean.setId(postedJob.getPersistenceId());
} else {
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "update", "job",
String.valueOf(postedJob.getPersistenceId()));
postedJob.setLastUpdateUser(username);
repository.updateJob(postedJob);
}
}
return validationResultBean;
}
private JsonBean getJobs(RequestContext ctx) {
String connectorIdentifier = ctx.getLastURLElement();
JobBean jobBean;
Locale locale = ctx.getAcceptLanguageHeader();
Repository repository = RepositoryManager.getInstance().getRepository();
// jobs by connector
if (ctx.getParameterValue(CONNECTOR_NAME_QUERY_PARAM) != null) {
connectorIdentifier = ctx.getParameterValue(CONNECTOR_NAME_QUERY_PARAM);
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "get", "jobsByConnector", connectorIdentifier);
long connectorId = HandlerUtils.getConnectorIdFromIdentifier(connectorIdentifier);
List<MJob> jobList = repository.findJobsForConnector(connectorId);
// Authorization check
jobList = AuthorizationEngine.filterResource(MResource.TYPE.JOB, jobList);
jobBean = createJobsBean(jobList, locale);
} else
// all jobs in the system
if (ctx.getPath().contains(JOBS_PATH)
|| (ctx.getPath().contains(JOB_PATH) && connectorIdentifier.equals("all"))) {
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "get", "jobs", "all");
List<MJob> jobList = repository.findJobs();
// Authorization check
jobList = AuthorizationEngine.filterResource(MResource.TYPE.JOB, jobList);
jobBean = createJobsBean(jobList, locale);
}
// job by Id
else {
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "get", "job", connectorIdentifier);
long jobId = HandlerUtils.getJobIdFromIdentifier(connectorIdentifier, repository);
MJob job = repository.findJob(jobId);
// Authorization check
AuthorizationEngine.readJob(String.valueOf(job.getPersistenceId()));
jobBean = createJobBean(Arrays.asList(job), locale);
}
return jobBean;
}
private JobBean createJobBean(List<MJob> jobs, Locale locale) {
JobBean jobBean = new JobBean(jobs);
addJob(jobs, locale, jobBean);
return jobBean;
}
private JobsBean createJobsBean(List<MJob> jobs, Locale locale) {
JobsBean jobsBean = new JobsBean(jobs);
addJob(jobs, locale, jobsBean);
return jobsBean;
}
private void addJob(List<MJob> jobs, Locale locale, JobBean bean) {
// Add associated resources into the bean
for (MJob job : jobs) {
long fromConnectorId = job.getConnectorId(Direction.FROM);
long toConnectorId = job.getConnectorId(Direction.TO);
// replace it only if it does not already exist
if (!bean.hasConnectorConfigBundle(fromConnectorId)) {
bean.addConnectorConfigBundle(fromConnectorId, ConnectorManager.getInstance()
.getResourceBundle(fromConnectorId, locale));
}
if (!bean.hasConnectorConfigBundle(toConnectorId)) {
bean.addConnectorConfigBundle(toConnectorId, ConnectorManager.getInstance()
.getResourceBundle(toConnectorId, locale));
}
}
}
private JsonBean enableJob(RequestContext ctx, boolean enabled) {
Repository repository = RepositoryManager.getInstance().getRepository();
String[] elements = ctx.getUrlElements();
String jobIdentifier = elements[elements.length - 2];
long jobId = HandlerUtils.getJobIdFromIdentifier(jobIdentifier, repository);
// Authorization check
AuthorizationEngine.enableDisableJob(String.valueOf(jobId));
repository.enableJob(jobId, enabled);
return JsonBean.EMPTY_BEAN;
}
private JsonBean startJob(RequestContext ctx) {
Repository repository = RepositoryManager.getInstance().getRepository();
String[] elements = ctx.getUrlElements();
String jobIdentifier = elements[elements.length - 2];
long jobId = HandlerUtils.getJobIdFromIdentifier(jobIdentifier, repository);
// Authorization check
AuthorizationEngine.startJob(String.valueOf(jobId));
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "submit", "job", String.valueOf(jobId));
// TODO(SQOOP-1638): This should be outsourced somewhere more suitable than
// here
if (JobManager.getInstance().getNotificationBaseUrl() == null) {
String url = ctx.getRequest().getRequestURL().toString();
JobManager.getInstance().setNotificationBaseUrl(
url.split("v1")[0] + "/v1/job/status/notification/");
}
MSubmission submission = JobManager.getInstance()
.start(jobId, prepareRequestEventContext(ctx));
return new SubmissionBean(submission);
}
private JsonBean stopJob(RequestContext ctx) {
Repository repository = RepositoryManager.getInstance().getRepository();
String[] elements = ctx.getUrlElements();
String jobIdentifier = elements[elements.length - 2];
long jobId = HandlerUtils.getJobIdFromIdentifier(jobIdentifier, repository);
// Authorization check
AuthorizationEngine.stopJob(String.valueOf(jobId));
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "stop", "job", String.valueOf(jobId));
MSubmission submission = JobManager.getInstance().stop(jobId, prepareRequestEventContext(ctx));
return new SubmissionBean(submission);
}
private JsonBean getJobStatus(RequestContext ctx) {
Repository repository = RepositoryManager.getInstance().getRepository();
String[] elements = ctx.getUrlElements();
String jobIdentifier = elements[elements.length - 2];
long jobId = HandlerUtils.getJobIdFromIdentifier(jobIdentifier, repository);
// Authorization check
AuthorizationEngine.statusJob(String.valueOf(jobId));
AuditLoggerManager.getInstance().logAuditEvent(ctx.getUserName(),
ctx.getRequest().getRemoteAddr(), "status", "job", String.valueOf(jobId));
MSubmission submission = JobManager.getInstance().status(jobId);
return new SubmissionBean(submission);
}
private HttpEventContext prepareRequestEventContext(RequestContext ctx) {
HttpEventContext httpEventContext = new HttpEventContext();
httpEventContext.setUsername(ctx.getUserName());
return httpEventContext;
}
}