/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.camel.component.catalog.framework;
import java.util.ArrayList;
import java.util.List;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.TypeConversionException;
import org.apache.camel.impl.DefaultProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.catalog.CatalogFramework;
import ddf.catalog.data.Metacard;
import ddf.catalog.operation.CreateRequest;
import ddf.catalog.operation.CreateResponse;
import ddf.catalog.operation.DeleteRequest;
import ddf.catalog.operation.DeleteResponse;
import ddf.catalog.operation.Update;
import ddf.catalog.operation.UpdateRequest;
import ddf.catalog.operation.UpdateResponse;
import ddf.catalog.operation.impl.CreateRequestImpl;
import ddf.catalog.operation.impl.DeleteRequestImpl;
import ddf.catalog.operation.impl.UpdateRequestImpl;
import ddf.catalog.source.IngestException;
import ddf.catalog.source.SourceUnavailableException;
/**
* Producer for the custom Camel CatalogComponent. This {@link org.apache.camel.Producer} would map
* to a Camel <to> route node with a URI like <code>catalog:framework</code>. The message sent to
* this component should have header named "operation" with a value of "CREATE", "UPDATE" or
* "DELETE".
*
* For the CREATE and UPDATE operation, the message body can contain a {@link java.util.List} of
* Metacards or a single Metacard object.
*
* For the DELETE operation, the message body can contain a {@link java.util.List} of {@link String}
* or a single {@link String} object. The {@link String} objects represent the IDs of Metacards that
* you would want to delete.
*
* The exchange's "in" message will be set with the affected Metacards. In the case of a CREATE, it
* will be updated with the created Metacards. In the case of the UPDATE, it will be updated with
* the updated Metacards and with the DELETE it will contain the deleted Metacards.
*
* <table border="1">
* <tr>
* <th>USE CASE</th>
* <th>ROUTE NODE</th>
* <th>HEADER</th>
* <th>MESSAGE BODY</th>
* <th>EXCHANGE MODIFICATION</th>
* </tr>
* <tr>
* <td>Create Metacard(s)</td>
* <td>catalog:framework</td>
* <td>operation:CREATE</td>
* <td>List<Metacard> or Metacard</td>
* <td>exchange.getIn().getBody() updated with {@link java.util.List} of Metacards created</td>
* </tr>
* <tr>
* <td>Update Metacard(s)</td>
* <td>catalog:framework</td>
* <td>operation:UPDATE</td>
* <td>List<Metacard> or Metacard</td>
* <td>exchange.getIn().getBody() updated with {@link java.util.List} of Metacards updated</td>
* </tr>
* <tr>
* <td>Delete Metacard(s)</td>
* <td>catalog:framework</td>
* <td>operation:DELETE</td>
* <td>List<String> or String (IDs of Metacards to delete)</td>
* <td>exchange.getIn().getBody() updated with {@link java.util.List} of Metacards deleted</td>
* </tr>
* </table>
*
* @author Sam Patel, Lockheed Martin
* @author ddf.isgs@lmco.com
*/
public class FrameworkProducer extends DefaultProducer {
private static final transient Logger LOGGER = LoggerFactory.getLogger(FrameworkProducer.class);
private static final String CREATE_OPERATION = "CREATE";
private static final String UPDATE_OPERATION = "UPDATE";
private static final String DELETE_OPERATION = "DELETE";
private static final String OPERATION_HEADER_KEY = "operation";
private CatalogFramework catalogFramework;
/**
* Constructs the {@link org.apache.camel.Producer} for the custom Camel CatalogComponent.
*
* @param endpoint
* the Camel endpoint that created this consumer
* @param catalogFramework
* the DDF Catalog Framework to use
*/
public FrameworkProducer(Endpoint endpoint, CatalogFramework catalogFramework) {
super(endpoint);
this.catalogFramework = catalogFramework;
}
@Override
public void process(Exchange exchange) throws FrameworkProducerException {
try {
LOGGER.debug("Entering process method");
final Object operationValueObj = exchange.getIn().getHeader(OPERATION_HEADER_KEY);
String operation = null;
if (operationValueObj == null) {
exchange.getIn().setBody(new ArrayList<Metacard>());
throw new FrameworkProducerException("Missing expected header!");
}
operation = operationValueObj.toString();
if (operation.trim().equalsIgnoreCase(CREATE_OPERATION)) {
create(exchange);
} else if (operation.trim().equalsIgnoreCase(UPDATE_OPERATION)) {
update(exchange);
} else if (operation.trim().equalsIgnoreCase(DELETE_OPERATION)) {
delete(exchange);
} else {
exchange.getIn().setBody(new ArrayList<Metacard>());
LOGGER.debug(
"Missing expected header \"operation:<CREATE|UPDATE|DELETE>\" but received {}",
operation);
}
LOGGER.debug("Exiting process method");
} catch (ClassCastException cce) {
exchange.getIn().setBody(new ArrayList<Metacard>());
LOGGER.debug("Received a non-String as the operation type");
} catch (SourceUnavailableException sue) {
exchange.getIn().setBody(new ArrayList<Metacard>());
LOGGER.debug("Exception cataloging metacards", sue);
} catch (IngestException ie) {
exchange.getIn().setBody(new ArrayList<Metacard>());
LOGGER.debug("Exception cataloging metacards", ie);
}
}
/**
* Creates metacard(s) in the catalog using the Catalog Framework.
*
* @param exchange
* The {@link org.apache.camel.Exchange} can contain a
* {@link org.apache.camel.Message} with a body of type {@link java.util.List} of
* Metacard or a single Metacard.
* @throws ddf.catalog.source.SourceUnavailableException
* @throws ddf.catalog.source.IngestException
* @throws ddf.camel.component.catalog.framework.FrameworkProducerException
*/
private void create(final Exchange exchange)
throws SourceUnavailableException, IngestException, FrameworkProducerException {
CreateResponse createResponse = null;
// read in data
final List<Metacard> metacardsToBeCreated = readBodyDataAsMetacards(exchange);
if (!validateList(metacardsToBeCreated, Metacard.class)) {
processCatalogResponse(createResponse, exchange);
throw new FrameworkProducerException("Validation of Metacard list failed");
}
LOGGER.debug("Validation of Metacard list passed...");
final CreateRequest createRequest = new CreateRequestImpl(metacardsToBeCreated);
int expectedNumberOfCreatedMetacards = metacardsToBeCreated.size();
if (expectedNumberOfCreatedMetacards < 1) {
LOGGER.debug("Empty list of Metacards...nothing to process");
processCatalogResponse(createResponse, exchange);
return;
}
LOGGER.debug("Making CREATE call to Catalog Framework...");
createResponse = catalogFramework.create(createRequest);
if (createResponse == null) {
LOGGER.debug("CreateResponse is null from catalog framework");
processCatalogResponse(createResponse, exchange);
return;
}
final List<Metacard> createdMetacards = createResponse.getCreatedMetacards();
if (createdMetacards == null) {
LOGGER.debug("CreateResponse returned null metacards list");
processCatalogResponse(createResponse, exchange);
return;
}
final int numberOfCreatedMetacards = createdMetacards.size();
if (numberOfCreatedMetacards != expectedNumberOfCreatedMetacards) {
LOGGER.debug("Expected {} metacards created but only {} were successfully created",
expectedNumberOfCreatedMetacards, numberOfCreatedMetacards);
processCatalogResponse(createResponse, exchange);
return;
}
LOGGER.debug("Created {} metacards", numberOfCreatedMetacards);
processCatalogResponse(createResponse, exchange);
}
/**
* Updates metacard(s) in the catalog using the Catalog Framework.
*
* @param exchange
* The {@link org.apache.camel.Exchange} can contain a
* {@link org.apache.camel.Message} with a body of type {@link java.util.List} of
* Metacard or a single Metacard.
* @throws ddf.catalog.source.SourceUnavailableException
* @throws ddf.catalog.source.IngestException
* @throws ddf.camel.component.catalog.framework.FrameworkProducerException
*/
private void update(final Exchange exchange)
throws SourceUnavailableException, IngestException, FrameworkProducerException {
UpdateResponse updateResponse = null;
// read in data from exchange
final List<Metacard> metacardsToBeUpdated = readBodyDataAsMetacards(exchange);
// process data if valid
if (!validateList(metacardsToBeUpdated, Metacard.class)) {
processCatalogResponse(updateResponse, exchange);
throw new FrameworkProducerException("Validation of Metacard list failed");
}
LOGGER.debug("Validation of Metacard list passed...");
final String[] metacardIds = new String[metacardsToBeUpdated.size()];
for (int i = 0; i < metacardsToBeUpdated.size(); i++) {
metacardIds[i] = metacardsToBeUpdated.get(i).getId();
}
final UpdateRequest updateRequest = new UpdateRequestImpl(metacardIds,
metacardsToBeUpdated);
final int expectedNumberOfUpdatedMetacards = metacardsToBeUpdated.size();
if (expectedNumberOfUpdatedMetacards < 1) {
LOGGER.debug("Empty list of Metacards...nothing to process");
processCatalogResponse(updateResponse, exchange);
return;
}
LOGGER.debug("Making UPDATE call to Catalog Framework...");
updateResponse = catalogFramework.update(updateRequest);
if (updateResponse == null) {
LOGGER.debug("UpdateResponse is null from catalog framework");
processCatalogResponse(updateResponse, exchange);
return;
}
final List<Update> updatedMetacards = updateResponse.getUpdatedMetacards();
if (updatedMetacards == null) {
LOGGER.debug("UpdateResponse returned null metacards list");
processCatalogResponse(updateResponse, exchange);
return;
}
final int numberOfUpdatedMetacards = updatedMetacards.size();
if (numberOfUpdatedMetacards != expectedNumberOfUpdatedMetacards) {
LOGGER.debug("Expected {} metacards updated but only {} were successfully updated",
expectedNumberOfUpdatedMetacards, numberOfUpdatedMetacards);
processCatalogResponse(updateResponse, exchange);
return;
}
LOGGER.debug("Updated {} metacards", numberOfUpdatedMetacards);
processCatalogResponse(updateResponse, exchange);
}
/**
* Deletes metacard(s) in the catalog using the Catalog Framework.
*
* @param exchange
* The {@link org.apache.camel.Exchange} can contain a
* {@link org.apache.camel.Message} with a body of type {@link java.util.List} of
* {@link String} or a single {@link String}. Each String represents the ID of a
* Metacard to be deleted.
* @throws ddf.catalog.source.SourceUnavailableException
* @throws ddf.catalog.source.IngestException
* @throws ddf.camel.component.catalog.framework.FrameworkProducerException
*/
private void delete(final Exchange exchange)
throws SourceUnavailableException, IngestException, FrameworkProducerException {
DeleteResponse deleteResponse = null;
// read in data
final List<String> metacardIdsToBeDeleted = readBodyDataAsMetacardIds(exchange);
// process if data is valid
if (!validateList(metacardIdsToBeDeleted, String.class)) {
LOGGER.debug("Validation of Metacard id list failed");
processCatalogResponse(deleteResponse, exchange);
throw new FrameworkProducerException("Validation of Metacard id list failed");
}
LOGGER.debug("Validation of Metacard id list passed...");
final String[] metacardIdsToBeDeletedArray = new String[metacardIdsToBeDeleted.size()];
final DeleteRequest deleteRequest = new DeleteRequestImpl(
metacardIdsToBeDeleted.toArray(metacardIdsToBeDeletedArray));
final int expectedNumberOfDeletedMetacards = metacardIdsToBeDeleted.size();
if (expectedNumberOfDeletedMetacards < 1) {
LOGGER.debug("Empty list of Metacard id...nothing to process");
processCatalogResponse(deleteResponse, exchange);
return;
}
LOGGER.debug("Making DELETE call to Catalog Framework...");
deleteResponse = catalogFramework.delete(deleteRequest);
if (deleteResponse == null) {
LOGGER.debug("DeleteResponse is null from catalog framework");
processCatalogResponse(deleteResponse, exchange);
return;
}
final List<Metacard> deletedMetacards = deleteResponse.getDeletedMetacards();
if (deletedMetacards == null) {
LOGGER.debug("DeleteResponse returned null metacards list");
processCatalogResponse(deleteResponse, exchange);
return;
}
final int numberOfDeletedMetacards = deletedMetacards.size();
if (numberOfDeletedMetacards != expectedNumberOfDeletedMetacards) {
LOGGER.debug("Expected {} metacards deleted but only {} were successfully deleted",
expectedNumberOfDeletedMetacards, numberOfDeletedMetacards);
processCatalogResponse(deleteResponse, exchange);
return;
}
LOGGER.debug("Deleted {} metacards", numberOfDeletedMetacards);
processCatalogResponse(deleteResponse, exchange);
}
/**
* Makes sure that a Metacard or Metacard ID list contains objects of a particular type
*
* @param list
* {@link java.util.List} of Metacard IDs
* @param cls
* {@link java.lang.Class} type that the objects inside the list should be
* @return true if the list is not empty and has valid types inside, else false.
*/
private boolean validateList(List<?> list, Class<?> cls) {
if (list.size() == 0) {
LOGGER.debug("No Metacard or Metacard IDs to process");
return false;
}
for (int i = 0; i < list.size(); i++) {
final Object o = list.get(i);
if (!cls.isInstance(o)) {
LOGGER.debug("Received a list of non-{} objects", cls.getName());
return false;
}
}
return true;
}
/**
* Processes the response from the Catalog Framework and updates the exchange accordingly.
*
* @param response
* response of type CreateResponse
* @param exchange
* the exchange to update
*/
private void processCatalogResponse(final CreateResponse response, final Exchange exchange) {
if (response == null) {
LOGGER.debug("Catalog response object is null");
exchange.getIn().setBody((List) (new ArrayList<Metacard>()));
return;
}
if (response.getCreatedMetacards() == null) {
LOGGER.debug("No Metacards created by catalog framework");
exchange.getIn().setBody((List) (new ArrayList<Metacard>()));
return;
}
exchange.getIn().setBody(response.getCreatedMetacards());
}
/**
* Processes the response from the Catalog Framework and updates the exchange accordingly.
*
* @param response
* response of type UpdateResponse
* @param exchange
* the exchange to update
*/
private void processCatalogResponse(final UpdateResponse response, final Exchange exchange) {
if (response == null) {
LOGGER.debug("Catalog response object is null");
exchange.getIn().setBody((List) (new ArrayList<Metacard>()));
return;
}
if (response.getUpdatedMetacards() == null) {
LOGGER.debug("No Metacards updated by catalog framework");
exchange.getIn().setBody((List) (new ArrayList<Metacard>()));
return;
}
exchange.getIn().setBody(response.getUpdatedMetacards());
}
/**
* Processes the response from the Catalog Framework and updates the exchange accordingly.
*
* @param response
* response of type DeleteResponse
* @param exchange
* the exchange to update
*/
private void processCatalogResponse(final DeleteResponse response, final Exchange exchange) {
if (response == null) {
LOGGER.debug("Catalog response object is null");
exchange.getIn().setBody((List) (new ArrayList<Metacard>()));
return;
}
if (response.getDeletedMetacards() == null) {
LOGGER.debug("No Metacards deleted by catalog framework");
exchange.getIn().setBody((List) (new ArrayList<Metacard>()));
return;
}
exchange.getIn().setBody(response.getDeletedMetacards());
}
/**
* Reads in Metacard ids from message body of exchange
*
* @param exchange
* the exchange with the message payload
* @return {@link java.util.List} of {@link String} representing Metacard IDs
*/
private List<String> readBodyDataAsMetacardIds(final Exchange exchange) {
List<String> metacardIdsToBeProcessed = new ArrayList<String>();
try {
if (exchange.getIn().getBody() == null) {
LOGGER.debug("Body is null");
return metacardIdsToBeProcessed;
}
// first see if we have a have List<String>
LOGGER.debug("Reading in body data as List<?>...");
metacardIdsToBeProcessed = exchange.getIn().getBody(List.class);
if (metacardIdsToBeProcessed != null) {
LOGGER.debug("Successfully read in body data as List<?>");
return metacardIdsToBeProcessed;
}
LOGGER.debug("Problem reading in body data as List<?>");
LOGGER.debug("Reading in body data as String...");
// if we get here, see if we have a single ID as a String
final String metacardIdToBeProcessed = exchange.getIn().getBody(String.class);
if (metacardIdToBeProcessed != null) {
metacardIdsToBeProcessed = new ArrayList<String>();
metacardIdsToBeProcessed.add(metacardIdToBeProcessed);
LOGGER.debug("Successfully read in body data as String");
return metacardIdsToBeProcessed;
}
// if we get here, we neither had String or List<?>, so set a
// default list
metacardIdsToBeProcessed = new ArrayList<String>();
} catch (TypeConversionException tce1) {
LOGGER.debug("Invalid message body. Expected either String or List<String>", tce1);
}
return metacardIdsToBeProcessed;
}
/**
* Reads in Metacard data from message body of exchange
*
* @param exchange
* the exchange containing the message data
* @return {@link java.util.List} of Metacard objects
*/
private List<Metacard> readBodyDataAsMetacards(final Exchange exchange) {
List<Metacard> metacardsToProcess = new ArrayList<Metacard>();
try {
if (exchange.getIn().getBody() == null) {
LOGGER.debug("Body is null");
return metacardsToProcess;
}
// first try to read in a single Metacard
LOGGER.debug("Reading in body data as Metacard...");
final Metacard metacardToProcess = exchange.getIn().getBody(Metacard.class);
if (metacardToProcess != null) {
metacardsToProcess.add(metacardToProcess);
LOGGER.debug("Successfully read in body data as Metacard ");
return metacardsToProcess;
}
LOGGER.debug("Problem reading in body data as Metacard");
// if we get here, then we possibly have List<Metacard>
LOGGER.debug("Reading in body data as List<Metacard>...");
metacardsToProcess = exchange.getIn().getBody(List.class);
if (metacardsToProcess == null) {
LOGGER.debug("Problem reading in body data as List<?>");
metacardsToProcess = new ArrayList<Metacard>();
return metacardsToProcess;
}
LOGGER.debug("Successfully read in body data as List<?>");
} catch (TypeConversionException tce1) {
LOGGER.debug("Invalid message body. Expected either Metacard or List<Metacard>", tce1);
}
return metacardsToProcess;
}
}