/*
* 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.nifi.update.attributes.api;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
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.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.apache.nifi.update.attributes.Action;
import org.apache.nifi.update.attributes.Condition;
import org.apache.nifi.update.attributes.Criteria;
import org.apache.nifi.update.attributes.Rule;
import org.apache.nifi.update.attributes.UpdateAttributeModelFactory;
import org.apache.nifi.update.attributes.dto.DtoFactory;
import org.apache.nifi.update.attributes.dto.RuleDTO;
import org.apache.nifi.update.attributes.entity.ActionEntity;
import org.apache.nifi.update.attributes.entity.ConditionEntity;
import org.apache.nifi.update.attributes.entity.RuleEntity;
import org.apache.nifi.update.attributes.entity.RulesEntity;
import org.apache.nifi.update.attributes.serde.CriteriaSerDe;
import org.apache.nifi.web.InvalidRevisionException;
import org.apache.nifi.web.Revision;
import org.apache.commons.lang3.StringUtils;
import com.sun.jersey.api.NotFoundException;
import org.apache.nifi.update.attributes.FlowFilePolicy;
import org.apache.nifi.update.attributes.entity.EvaluationContextEntity;
import org.apache.nifi.web.ComponentDetails;
import org.apache.nifi.web.HttpServletConfigurationRequestContext;
import org.apache.nifi.web.HttpServletRequestContext;
import org.apache.nifi.web.NiFiWebConfigurationContext;
import org.apache.nifi.web.NiFiWebConfigurationRequestContext;
import org.apache.nifi.web.NiFiWebRequestContext;
import org.apache.nifi.web.UiExtensionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
@Path("/criteria")
public class RuleResource {
private static final Logger logger = LoggerFactory.getLogger(RuleResource.class);
@Context
private ServletContext servletContext;
@Context
private HttpServletRequest request;
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/evaluation-context")
public Response getEvaluationContext(@QueryParam("processorId") final String processorId) {
// get the web context
final NiFiWebConfigurationContext nifiWebContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// build the web context config
final NiFiWebRequestContext contextConfig = getRequestContext(processorId);
// load the criteria
final Criteria criteria = getCriteria(nifiWebContext, contextConfig);
// create the response entity
final EvaluationContextEntity responseEntity = new EvaluationContextEntity();
responseEntity.setProcessorId(processorId);
responseEntity.setFlowFilePolicy(criteria.getFlowFilePolicy().name());
responseEntity.setRuleOrder(criteria.getRuleOrder());
// generate the response
final ResponseBuilder response = Response.ok(responseEntity);
return noCache(response).build();
}
@PUT
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/evaluation-context")
public Response updateEvaluationContext(
@Context final UriInfo uriInfo,
final EvaluationContextEntity requestEntity) {
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// ensure the evaluation context has been specified
if (requestEntity == null) {
throw new WebApplicationException(badRequest("The evaluation context must be specified."));
}
// ensure the id has been specified
if (requestEntity.getRuleOrder() == null && requestEntity.getFlowFilePolicy() == null) {
throw new WebApplicationException(badRequest("Either the rule order or the matching strategy must be specified."));
}
// build the web context config
final NiFiWebConfigurationRequestContext requestContext = getConfigurationRequestContext(
requestEntity.getProcessorId(), requestEntity.getRevision(), requestEntity.getClientId());
// load the criteria
final Criteria criteria = getCriteria(configurationContext, requestContext);
// if a new rule order is specified, attempt to set it
if (requestEntity.getRuleOrder() != null) {
try {
criteria.reorder(requestEntity.getRuleOrder());
} catch (final IllegalArgumentException iae) {
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
}
}
// if a new matching strategy is specified, attempt to set it
if (requestEntity.getFlowFilePolicy() != null) {
try {
criteria.setFlowFilePolicy(FlowFilePolicy.valueOf(requestEntity.getFlowFilePolicy()));
} catch (final IllegalArgumentException iae) {
throw new WebApplicationException(iae, badRequest("The specified matching strategy is unknown: " + requestEntity.getFlowFilePolicy()));
}
}
// save the criteria
saveCriteria(requestContext, criteria);
// create the response entity
final EvaluationContextEntity responseEntity = new EvaluationContextEntity();
responseEntity.setClientId(requestEntity.getClientId());
responseEntity.setRevision(requestEntity.getRevision());
responseEntity.setProcessorId(requestEntity.getProcessorId());
responseEntity.setFlowFilePolicy(criteria.getFlowFilePolicy().name());
responseEntity.setRuleOrder(criteria.getRuleOrder());
// generate the response
final ResponseBuilder response = Response.ok(responseEntity);
return noCache(response).build();
}
@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules")
public Response createRule(
@Context final UriInfo uriInfo,
final RuleEntity requestEntity) {
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// ensure the rule has been specified
if (requestEntity == null || requestEntity.getRule() == null) {
throw new WebApplicationException(badRequest("The rule must be specified."));
}
final RuleDTO ruleDto = requestEntity.getRule();
// ensure the id hasn't been specified
if (ruleDto.getId() != null) {
throw new WebApplicationException(badRequest("The rule id cannot be specified."));
}
// ensure there are some conditions
if (ruleDto.getConditions() == null || ruleDto.getConditions().isEmpty()) {
throw new WebApplicationException(badRequest("The rule conditions must be set."));
}
// ensure there are some actions
if (ruleDto.getActions() == null || ruleDto.getActions().isEmpty()) {
throw new WebApplicationException(badRequest("The rule actions must be set."));
}
// generate a new id
final String uuid = UUID.randomUUID().toString();
// build the request context
final NiFiWebConfigurationRequestContext requestContext = getConfigurationRequestContext(
requestEntity.getProcessorId(), requestEntity.getRevision(), requestEntity.getClientId());
// load the criteria
final Criteria criteria = getCriteria(configurationContext, requestContext);
final UpdateAttributeModelFactory factory = new UpdateAttributeModelFactory();
// create the new rule
final Rule rule;
try {
rule = factory.createRule(ruleDto);
rule.setId(uuid);
} catch (final IllegalArgumentException iae) {
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
}
// add the rule
criteria.addRule(rule);
// save the criteria
saveCriteria(requestContext, criteria);
// create the response entity
final RuleEntity responseEntity = new RuleEntity();
responseEntity.setClientId(requestEntity.getClientId());
responseEntity.setRevision(requestEntity.getRevision());
responseEntity.setProcessorId(requestEntity.getProcessorId());
responseEntity.setRule(DtoFactory.createRuleDTO(rule));
// generate the response
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
final ResponseBuilder response = Response.created(uriBuilder.path(uuid).build()).entity(responseEntity);
return noCache(response).build();
}
@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules/conditions")
public Response createCondition(
@Context final UriInfo uriInfo,
@PathParam("id") final String ruleId,
final ConditionEntity requestEntity) {
// generate a new id
final String uuid = UUID.randomUUID().toString();
final Condition condition;
try {
// create the condition object
final UpdateAttributeModelFactory factory = new UpdateAttributeModelFactory();
condition = factory.createCondition(requestEntity.getCondition());
condition.setId(uuid);
} catch (final IllegalArgumentException iae) {
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
}
// build the response
final ConditionEntity responseEntity = new ConditionEntity();
responseEntity.setClientId(requestEntity.getClientId());
responseEntity.setProcessorId(requestEntity.getProcessorId());
responseEntity.setRevision(requestEntity.getRevision());
responseEntity.setCondition(DtoFactory.createConditionDTO(condition));
// generate the response
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
final ResponseBuilder response = Response.created(uriBuilder.path(uuid).build()).entity(responseEntity);
return noCache(response).build();
}
@POST
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules/actions")
public Response createAction(
@Context final UriInfo uriInfo,
@PathParam("id") final String ruleId,
final ActionEntity requestEntity) {
// generate a new id
final String uuid = UUID.randomUUID().toString();
final Action action;
try {
// create the condition object
final UpdateAttributeModelFactory factory = new UpdateAttributeModelFactory();
action = factory.createAction(requestEntity.getAction());
action.setId(uuid);
} catch (final IllegalArgumentException iae) {
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
}
// build the response
final ActionEntity responseEntity = new ActionEntity();
responseEntity.setClientId(requestEntity.getClientId());
responseEntity.setProcessorId(requestEntity.getProcessorId());
responseEntity.setRevision(requestEntity.getRevision());
responseEntity.setAction(DtoFactory.createActionDTO(action));
// generate the response
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
final ResponseBuilder response = Response.created(uriBuilder.path(uuid).build()).entity(responseEntity);
return noCache(response).build();
}
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules/{id}")
public Response getRule(
@PathParam("id") final String ruleId,
@QueryParam("processorId") final String processorId,
@DefaultValue("false") @QueryParam("verbose") final Boolean verbose) {
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// build the web context config
final NiFiWebRequestContext requestContext = getRequestContext(processorId);
// load the criteria and get the rule
final Criteria criteria = getCriteria(configurationContext, requestContext);
final Rule rule = criteria.getRule(ruleId);
if (rule == null) {
throw new NotFoundException();
}
// convert to a dto
final RuleDTO ruleDto = DtoFactory.createRuleDTO(rule);
// prune if appropriate
if (!verbose) {
ruleDto.setConditions(null);
ruleDto.setActions(null);
}
// create the response entity
final RuleEntity responseEntity = new RuleEntity();
responseEntity.setProcessorId(processorId);
responseEntity.setRule(ruleDto);
// generate the response
final ResponseBuilder response = Response.ok(responseEntity);
return noCache(response).build();
}
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules")
public Response getRules(
@QueryParam("processorId") final String processorId,
@DefaultValue("false") @QueryParam("verbose") final Boolean verbose) {
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// build the web context config
final NiFiWebRequestContext requestContext = getRequestContext(processorId);
// load the criteria
final Criteria criteria = getCriteria(configurationContext, requestContext);
final List<Rule> rules = criteria.getRules();
// generate the rules
List<RuleDTO> ruleDtos = null;
if (rules != null) {
ruleDtos = new ArrayList<>(rules.size());
for (final Rule rule : rules) {
final RuleDTO ruleDto = DtoFactory.createRuleDTO(rule);
ruleDtos.add(ruleDto);
// prune if appropriate
if (!verbose) {
ruleDto.setConditions(null);
ruleDto.setActions(null);
}
}
}
// create the response entity
final RulesEntity responseEntity = new RulesEntity();
responseEntity.setProcessorId(processorId);
responseEntity.setRules(ruleDtos);
// generate the response
final ResponseBuilder response = Response.ok(responseEntity);
return noCache(response).build();
}
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules/search-results")
public Response searchRules(
@QueryParam("processorId") final String processorId,
@QueryParam("q") final String term) {
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// build the web context config
final NiFiWebRequestContext requestContext = getRequestContext(processorId);
// load the criteria
final Criteria criteria = getCriteria(configurationContext, requestContext);
final List<Rule> rules = criteria.getRules();
// generate the rules
List<RuleDTO> ruleDtos = null;
if (rules != null) {
ruleDtos = new ArrayList<>(rules.size());
for (final Rule rule : rules) {
if (StringUtils.containsIgnoreCase(rule.getName(), term)) {
final RuleDTO ruleDto = DtoFactory.createRuleDTO(rule);
ruleDtos.add(ruleDto);
}
}
}
// sort the rules
Collections.sort(ruleDtos, new Comparator<RuleDTO>() {
@Override
public int compare(RuleDTO r1, RuleDTO r2) {
final Collator collator = Collator.getInstance(Locale.US);
return collator.compare(r1.getName(), r2.getName());
}
});
// create the response entity
final RulesEntity responseEntity = new RulesEntity();
responseEntity.setProcessorId(processorId);
responseEntity.setRules(ruleDtos);
// generate the response
final ResponseBuilder response = Response.ok(responseEntity);
return noCache(response).build();
}
@PUT
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules/{id}")
public Response updateRule(
@Context final UriInfo uriInfo,
@PathParam("id") final String ruleId,
final RuleEntity requestEntity) {
// get the web context
final NiFiWebConfigurationContext nifiWebContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// ensure the rule has been specified
if (requestEntity == null || requestEntity.getRule() == null) {
throw new WebApplicationException(badRequest("The rule must be specified."));
}
final RuleDTO ruleDto = requestEntity.getRule();
// ensure the id has been specified
if (ruleDto.getId() == null) {
throw new WebApplicationException(badRequest("The rule id must be specified."));
}
if (!ruleDto.getId().equals(ruleId)) {
throw new WebApplicationException(badRequest("The rule id in the path does not equal the rule id in the request body."));
}
// ensure the rule name was specified
if (ruleDto.getName() == null || ruleDto.getName().isEmpty()) {
throw new WebApplicationException(badRequest("The rule name must be specified and cannot be blank."));
}
// ensure there are some conditions
if (ruleDto.getConditions() == null || ruleDto.getConditions().isEmpty()) {
throw new WebApplicationException(badRequest("The rule conditions must be set."));
}
// ensure there are some actions
if (ruleDto.getActions() == null || ruleDto.getActions().isEmpty()) {
throw new WebApplicationException(badRequest("The rule actions must be set."));
}
// build the web context config
final NiFiWebConfigurationRequestContext requestContext = getConfigurationRequestContext(
requestEntity.getProcessorId(), requestEntity.getRevision(), requestEntity.getClientId());
// load the criteria
final UpdateAttributeModelFactory factory = new UpdateAttributeModelFactory();
final Criteria criteria = getCriteria(nifiWebContext, requestContext);
// attempt to locate the rule
Rule rule = criteria.getRule(ruleId);
// if the rule isn't found add it
boolean newRule = false;
if (rule == null) {
newRule = true;
rule = new Rule();
rule.setId(ruleId);
}
try {
// evaluate the conditions and actions before modifying the rule
final Set<Condition> conditions = factory.createConditions(ruleDto.getConditions());
final Set<Action> actions = factory.createActions(ruleDto.getActions());
// update the rule
rule.setName(ruleDto.getName());
rule.setConditions(conditions);
rule.setActions(actions);
} catch (final IllegalArgumentException iae) {
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
}
// add the new rule if application
if (newRule) {
criteria.addRule(rule);
}
// save the criteria
saveCriteria(requestContext, criteria);
// create the response entity
final RuleEntity responseEntity = new RuleEntity();
responseEntity.setClientId(requestEntity.getClientId());
responseEntity.setRevision(requestEntity.getRevision());
responseEntity.setProcessorId(requestEntity.getProcessorId());
responseEntity.setRule(DtoFactory.createRuleDTO(rule));
// generate the response
final ResponseBuilder response;
if (newRule) {
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
response = Response.created(uriBuilder.path(ruleId).build()).entity(responseEntity);
} else {
response = Response.ok(responseEntity);
}
return noCache(response).build();
}
@DELETE
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/rules/{id}")
public Response deleteRule(
@PathParam("id") final String ruleId,
@QueryParam("processorId") final String processorId,
@QueryParam("clientId") final String clientId,
@QueryParam("revision") final Long revision) {
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
// build the web context config
final NiFiWebConfigurationRequestContext requestContext = getConfigurationRequestContext(processorId, revision, clientId);
// load the criteria and get the rule
final Criteria criteria = getCriteria(configurationContext, requestContext);
final Rule rule = criteria.getRule(ruleId);
if (rule == null) {
throw new NotFoundException();
}
// delete the rule
criteria.deleteRule(rule);
// save the criteria
saveCriteria(requestContext, criteria);
// create the response entity
final RulesEntity responseEntity = new RulesEntity();
responseEntity.setClientId(clientId);
responseEntity.setRevision(revision);
responseEntity.setProcessorId(processorId);
// generate the response
final ResponseBuilder response = Response.ok(responseEntity);
return noCache(response).build();
}
private Criteria getCriteria(final NiFiWebConfigurationContext configurationContext, final NiFiWebRequestContext requestContext) {
final ComponentDetails processorDetails;
try {
// load the processor configuration
processorDetails = configurationContext.getComponentDetails(requestContext);
} catch (final InvalidRevisionException ire) {
throw new WebApplicationException(ire, invalidRevision(ire.getMessage()));
} catch (final Exception e) {
final String message = String.format("Unable to get UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
logger.error(message, e);
throw new WebApplicationException(e, error(message));
}
Criteria criteria = null;
if (processorDetails != null) {
try {
criteria = CriteriaSerDe.deserialize(processorDetails.getAnnotationData());
} catch (final IllegalArgumentException iae) {
final String message = String.format("Unable to deserialize existing rules for UpdateAttribute[id=%s]. Deserialization error: %s", requestContext.getId(), iae);
logger.error(message, iae);
throw new WebApplicationException(iae, error(message));
}
}
// ensure the criteria isn't null
if (criteria == null) {
criteria = new Criteria();
}
return criteria;
}
private void saveCriteria(final NiFiWebConfigurationRequestContext requestContext, final Criteria criteria) {
// serialize the criteria
final String annotationData = CriteriaSerDe.serialize(criteria);
// get the web context
final NiFiWebConfigurationContext configurationContext = (NiFiWebConfigurationContext) servletContext.getAttribute("nifi-web-configuration-context");
try {
// save the annotation data
configurationContext.updateComponent(requestContext, annotationData, null);
} catch (final InvalidRevisionException ire) {
throw new WebApplicationException(ire, invalidRevision(ire.getMessage()));
} catch (final Exception e) {
final String message = String.format("Unable to save UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
logger.error(message, e);
throw new WebApplicationException(e, error(message));
}
}
private NiFiWebRequestContext getRequestContext(final String processorId) {
return new HttpServletRequestContext(UiExtensionType.ProcessorConfiguration, request) {
@Override
public String getId() {
return processorId;
}
};
}
private NiFiWebConfigurationRequestContext getConfigurationRequestContext(final String processorId, final Long revision, final String clientId) {
return new HttpServletConfigurationRequestContext(UiExtensionType.ProcessorConfiguration, request) {
@Override
public String getId() {
return processorId;
}
@Override
public Revision getRevision() {
return new Revision(revision, clientId, processorId);
}
};
}
private Response badRequest(final String message) {
return Response.status(Response.Status.BAD_REQUEST).entity(message).type("text/plain").build();
}
private Response invalidRevision(final String message) {
return Response.status(Response.Status.CONFLICT).entity(message).type("text/plain").build();
}
private Response error(final String message) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(message).type("text/plain").build();
}
private ResponseBuilder noCache(ResponseBuilder response) {
CacheControl cacheControl = new CacheControl();
cacheControl.setPrivate(true);
cacheControl.setNoCache(true);
cacheControl.setNoStore(true);
return response.cacheControl(cacheControl);
}
}