package com.threatconnect.sdk.parser.service.save;
import com.threatconnect.sdk.config.Configuration;
import com.threatconnect.sdk.conn.Connection;
import com.threatconnect.sdk.parser.model.Adversary;
import com.threatconnect.sdk.parser.model.Document;
import com.threatconnect.sdk.parser.model.Email;
import com.threatconnect.sdk.parser.model.File;
import com.threatconnect.sdk.parser.model.Group;
import com.threatconnect.sdk.parser.model.Incident;
import com.threatconnect.sdk.parser.model.Indicator;
import com.threatconnect.sdk.parser.model.Item;
import com.threatconnect.sdk.parser.model.ItemType;
import com.threatconnect.sdk.parser.model.Signature;
import com.threatconnect.sdk.parser.model.Threat;
import com.threatconnect.sdk.parser.service.writer.AdversaryWriter;
import com.threatconnect.sdk.parser.service.writer.BatchIndicatorWriter;
import com.threatconnect.sdk.parser.service.writer.DocumentWriter;
import com.threatconnect.sdk.parser.service.writer.EmailWriter;
import com.threatconnect.sdk.parser.service.writer.GroupWriter;
import com.threatconnect.sdk.parser.service.writer.IncidentWriter;
import com.threatconnect.sdk.parser.service.writer.SignatureWriter;
import com.threatconnect.sdk.parser.service.writer.ThreatWriter;
import com.threatconnect.sdk.parser.util.ItemUtil;
import com.threatconnect.sdk.server.entity.BatchConfig.AttributeWriteType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class BatchApiSaveService implements SaveService
{
private static final Logger logger = LoggerFactory.getLogger(BatchApiSaveService.class);
protected final Configuration configuration;
protected final String ownerName;
public BatchApiSaveService(final Configuration configuration, final String ownerName)
{
this.configuration = configuration;
this.ownerName = ownerName;
}
/**
* Saves all of the items to the server using the APIs
*
* @param items
* @throws IOException Signals that an I/O exception of some sort has occurred. This class is the general class of
* exceptions produced by failed or interrupted I/O operations.
*/
@Override
public SaveResults saveItems(final Collection<? extends Item> items) throws IOException
{
// create a new connection object from the configuration
Connection connection = new Connection(configuration);
return saveItems(items, connection);
}
/**
* Saves all of the items to the server using the APIs
*
* @param items
* @param connection
* @throws IOException Signals that an I/O exception of some sort has occurred. This class is the general class of
* exceptions produced by failed or interrupted I/O operations.
*/
public SaveResults saveItems(final Collection<? extends Item> items, final Connection connection) throws IOException
{
SaveResults saveResults = new SaveResults();
// break the list of items into sets of groups and indicators
Set<Group> groups = new HashSet<Group>();
Set<Indicator> indicators = new HashSet<Indicator>();
ItemUtil.seperateGroupsAndIndicators(items, groups, indicators);
// save all of the groups
Map<Group, Integer> savedGroupMap = new HashMap<Group, Integer>();
saveResults.addFailedItems(saveGroups(groups, savedGroupMap, connection));
// now that the groups have been saved, we need to create a map to allow quick lookups of
// all associated group ids for a given indicator
Map<Indicator, Set<Integer>> associatedIndicatorGroupsIDs =
buildAssociatedIndicatorGroupIDs(groups, indicators, savedGroupMap);
// save all of the indicators
saveResults.addFailedItems(saveIndicators(indicators, associatedIndicatorGroupsIDs, connection));
return saveResults;
}
protected SaveResults saveGroups(final Collection<Group> groups, final Map<Group, Integer> savedGroupMap,
final Connection connection) throws IOException
{
// create a new save result to return
SaveResults saveResults = new SaveResults();
// for each of the groups
for (Group group : groups)
{
try
{
saveGroup(group, ownerName, savedGroupMap, connection, saveResults);
}
catch (SaveItemFailedException e)
{
logger.warn(e.getMessage(), e);
// add this item to the list of failed saves
saveResults.addFailedItems(group);
}
}
return saveResults;
}
/**
* Saves the associated items for a given group
*
* @param group
* @param ownerName
* @param connection
* @param writer
* @param saveResults
* @throws IOException Signals that an I/O exception of some sort has occurred. This class is the general class of
* exceptions produced by failed or interrupted I/O operations.
*/
protected void saveAssociatedItems(final Group group, final String ownerName,
final Map<Group, Integer> savedGroupMap, final Connection connection,
GroupWriter<?, ?> writer, final SaveResults saveResults) throws IOException
{
// for each of the associated items of this group
for (Item associatedItem : group.getAssociatedItems())
{
try
{
// switch based on the item type
switch (associatedItem.getItemType())
{
case GROUP:
Group associatedGroup = (Group) associatedItem;
Integer savedAssociatedGroupId =
saveGroup(associatedGroup, ownerName, savedGroupMap, connection, saveResults);
writer.associateGroup(associatedGroup.getGroupType(), savedAssociatedGroupId);
break;
case INDICATOR:
// Ignore the indicators for now, this is done later in batch save
break;
default:
break;
}
}
catch (SaveItemFailedException e)
{
logger.warn(e.getMessage(), e);
// add to the list of failed items
saveResults.addFailedItems(associatedItem);
}
catch (AssociateFailedException e)
{
logger.warn(e.getMessage(), e);
}
}
}
protected SaveResults saveIndicators(final Collection<Indicator> indicators,
final Map<Indicator, Set<Integer>> associatedIndicatorGroupsIDs, final Connection connection) throws IOException
{
return saveIndicators(indicators, associatedIndicatorGroupsIDs, connection, AttributeWriteType.Replace);
}
protected SaveResults saveIndicators(final Collection<Indicator> indicators,
final Map<Indicator, Set<Integer>> associatedIndicatorGroupsIDs, final Connection connection,
final AttributeWriteType attributeWriteType) throws IOException
{
try
{
// create a new batch indicator writer
BatchIndicatorWriter batchIndicatorWriter =
new BatchIndicatorWriter(connection, indicators, associatedIndicatorGroupsIDs);
// save the indicators
SaveResults batchSaveResults = batchIndicatorWriter.saveIndicators(ownerName, attributeWriteType);
//TODO: the batch api does not handle saving file occurrences so we have to revert to the ApiSaveService to do so
saveFileOccurrences(indicators, batchSaveResults);
return batchSaveResults;
}
catch (SaveItemFailedException e)
{
logger.warn(e.getMessage(), e);
SaveResults saveResults = new SaveResults();
saveResults.addFailedItems(ItemType.INDICATOR, indicators.size());
return saveResults;
}
}
/**
* TODO: the batch api does not handle saving file occurrences so we have to revert to the ApiSaveService to do so
*
* @return
*/
private void saveFileOccurrences(final Collection<Indicator> indicators, final SaveResults saveResults)
{
//extract all of the file objects from the set of indicators
Set<File> files = ItemUtil.extractIndicatorSet(indicators, File.class);
ApiSaveService apiSaveService = new ApiSaveService(configuration, ownerName);
//for each of the files
for (File file : files)
{
//check to see if this file has occurrences
if (!file.getFileOccurrences().isEmpty())
{
try
{
saveResults.addFailedItems(apiSaveService.saveItems(Arrays.asList(file)));
}
catch (IOException e)
{
logger.warn(e.getMessage(), e);
saveResults.addFailedItems(file);
}
}
}
}
protected SaveResults deleteIndicators(final Collection<Indicator> indicators, final Connection connection)
throws IOException
{
try
{
// create a new batch indicator writer
BatchIndicatorWriter batchIndicatorWriter = new BatchIndicatorWriter(connection, indicators);
// delete the indicators
return batchIndicatorWriter.deleteIndicators(ownerName);
}
catch (SaveItemFailedException e)
{
logger.warn(e.getMessage(), e);
SaveResults saveResults = new SaveResults();
saveResults.addFailedItems(ItemType.INDICATOR, indicators.size());
return saveResults;
}
}
/**
* Retrieves the specific writer implementation for this group
*
* @param group
* @param connection
* @return
*/
protected GroupWriter<?, ?> getGroupWriter(final Group group, final Connection connection)
{
GroupWriter<?, ?> writer = null;
// switch based on the indicator type
switch (group.getGroupType())
{
case ADVERSARY:
writer = new AdversaryWriter(connection, (Adversary) group);
break;
case DOCUMENT:
writer = new DocumentWriter(connection, (Document) group);
break;
case EMAIL:
writer = new EmailWriter(connection, (Email) group);
break;
case INCIDENT:
writer = new IncidentWriter(connection, (Incident) group);
break;
case SIGNATURE:
writer = new SignatureWriter(connection, (Signature) group);
break;
case THREAT:
writer = new ThreatWriter(connection, (Threat) group);
break;
default:
throw new IllegalArgumentException("invalid group type");
}
return writer;
}
protected Integer saveGroup(final Group group, final String ownerName,
final Map<Group, Integer> savedGroupMap, final Connection connection, final SaveResults saveResults)
throws IOException, SaveItemFailedException
{
GroupWriter<?, ?> writer = getGroupWriter(group, connection);
// check to see if this group has not already been saved
if (!savedGroupMap.containsKey(group))
{
// save the group
com.threatconnect.sdk.server.entity.Group savedGroup = writer.saveGroup(ownerName);
// store the id in the map
savedGroupMap.put(group, savedGroup.getId());
return savedGroup.getId();
}
else
{
return savedGroupMap.get(group);
}
}
/**
* Looks at all of the possible associations for each group/indicator and builds a map that can
* lookup all of the associated group ids for any given indicator object
*
* @param groups
* @param indicators
* @param savedGroupMap
* @return
*/
private Map<Indicator, Set<Integer>> buildAssociatedIndicatorGroupIDs(Set<Group> groups,
Set<Indicator> indicators, Map<Group, Integer> savedGroupMap)
{
// holds the hashmap to resolve the set of associated group ids for a given indicator
Map<Indicator, Set<Integer>> associatedIndicatorGroupsIDs = new HashMap<Indicator, Set<Integer>>();
// for each of the groups
for (Group group : groups)
{
// look up the group id for this group
Integer groupID = savedGroupMap.get(group);
// make sure this group id is not null
if (null != groupID)
{
// for each of the associated items of this group
for (Item item : group.getAssociatedItems())
{
// check to see if this item is an indicator
if (item.getItemType().equals(ItemType.INDICATOR))
{
// add this group id to the set of associated items for this indicator
Indicator indicator = (Indicator) item;
getOrCreateGroupIDSet(indicator, associatedIndicatorGroupsIDs).add(groupID);
}
}
}
else
{
// :TODO: the group id doesn't exist so it failed to save
}
}
// for each of the indicators
for (Indicator indicator : indicators)
{
// for each of the associated groups of this indicator
for (Group group : indicator.getAssociatedItems())
{
// look up the group id for this group
Integer groupID = savedGroupMap.get(group);
// make sure this group id is not null
if (null != groupID)
{
// add this group id to the set of associated items for this indicator
getOrCreateGroupIDSet(indicator, associatedIndicatorGroupsIDs).add(groupID);
}
else
{
// :TODO: the group id doesn't exist so it failed to save
}
}
}
return associatedIndicatorGroupsIDs;
}
private Set<Integer> getOrCreateGroupIDSet(final Indicator indicator,
Map<Indicator, Set<Integer>> associatedIndicatorGroupsIDs)
{
// check to see if the map contains this set of ids
Set<Integer> ids = associatedIndicatorGroupsIDs.get(indicator);
if (null == ids)
{
ids = new HashSet<Integer>();
associatedIndicatorGroupsIDs.put(indicator, ids);
}
return ids;
}
}