
com.talanlabs.mm.engine.SubDictionary Maven / Gradle / Ivy
The newest version!
package com.talanlabs.mm.engine;
import com.talanlabs.java.lambda.Try;
import com.talanlabs.mm.engine.exception.InvalidDictionaryOperationException;
import com.talanlabs.mm.engine.exception.UnknownDictionaryException;
import com.talanlabs.mm.engine.exception.UnknownErrorException;
import com.talanlabs.mm.engine.model.IProcessingResult;
import com.talanlabs.mm.engine.model.ProcessingResultBuilder;
import com.talanlabs.mm.shared.model.IErrorType;
import com.talanlabs.mm.shared.model.IProcessError;
import com.talanlabs.mm.shared.model.domain.ErrorImpact;
import com.talanlabs.mm.shared.model.domain.ErrorRecyclingKind;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This object contains the configuration of each message type, and is linked to a main dictionary.
* Subsets dictionaries can be added by using {@link #addSubsetDictionary}
* Created by NicolasP on 29/10/2015.
*/
public class SubDictionary {
private static final Log LOG = LogFactory.getLog(SubDictionary.class);
/**
* list of known errors for current dictionary
*/
private final List errorTypeList;
final ReadWriteLock lock;
private final String dictionaryName;
private final Map subsetDictionaryMap;
private final SubDictionary parentDictionary;
private boolean burnAfterUse;
/**
* Add a sub dictionary using {@link MMDictionary#addSubsetDictionary(String)}
*/
SubDictionary(String name, SubDictionary parentDictionary, ReadWriteLock lock) {
super();
this.dictionaryName = name;
this.parentDictionary = parentDictionary;
this.lock = lock;
this.errorTypeList = new ArrayList<>();
this.subsetDictionaryMap = new HashMap<>();
}
/**
* If set to true, this dictionary will be destroyed after the {@link #getProcessingResult} method has been called
*/
public final void setBurnAfterUse(boolean burnAfterUse) {
this.burnAfterUse = burnAfterUse;
}
/**
* Returns the name of the dictionary
*/
public final String getDictionaryName() {
return StringUtils.substring(dictionaryName, dictionaryName.lastIndexOf(".") + 1);
}
/**
* Returns true if the given dictionary exists in the current sub dictionary. For performances issues, doesn't check dots to get subset of a subset
*/
public final boolean existsSubsetDictionary(String dictionaryName) {
return subsetDictionaryMap.keySet().contains(dictionaryName);
}
/**
* Get a subset dictionary from current. Use dots to get subset of a subset
* An {@link UnknownDictionaryException} is thrown if the dictionary is unknown
*/
public final SubDictionary getSubsetDictionary(String dictionaryName) throws UnknownDictionaryException {
if (StringUtils.isBlank(dictionaryName)) {
throw new UnknownDictionaryException("Dictionary name is null or blank");
}
String[] keys = dictionaryName.split("\\."); //$NON-NLS-1$
SubDictionary dictionary = this;
for (String key : keys) {
dictionary = dictionary.subsetDictionaryMap.get(key);
if (dictionary == null) {
throw new UnknownDictionaryException("'" + key + "'" + getDictionaryExceptionString()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return dictionary;
}
/**
* Add a subset dictionary to the current dictionary. The name is unique, a {@link InvalidDictionaryOperationException} is raised.
* The dictionary name cannot be blank. If it is, a {@link InvalidDictionaryOperationException} is raised.
*/
public final SubDictionary addSubsetDictionary(String dictionaryName) throws InvalidDictionaryOperationException {
return getOrCreateSubsetDictionary(dictionaryName, true);
}
/**
* Add a subset dictionary to the current dictionary. If it already exists, it returns the current subset dictionary
*/
public final SubDictionary getOrCreateSubsetDictionary(String dictionaryName) throws InvalidDictionaryOperationException {
return getOrCreateSubsetDictionary(dictionaryName, false);
}
/**
* checkUnique is true if an exception should be raised if the dictionary already exists
*/
private SubDictionary getOrCreateSubsetDictionary(String dictionaryName, boolean checkUnique) throws InvalidDictionaryOperationException {
validateDictionaryName(dictionaryName);
int idx = dictionaryName.indexOf("."); //$NON-NLS-1$
if (idx > -1) {
String name = dictionaryName.substring(0, idx);
validateDictionaryName(name);
SubDictionary newDictionary = subsetDictionaryMap.get(name);
if (newDictionary == null) {
newDictionary = new SubDictionary(this.dictionaryName + "." + name, this, lock); //$NON-NLS-1$
subsetDictionaryMap.put(name, newDictionary);
}
return newDictionary.getOrCreateSubsetDictionary(StringUtils.substring(dictionaryName, idx + 1), checkUnique);
} else {
SubDictionary newDictionary = subsetDictionaryMap.get(dictionaryName);
if (newDictionary != null) {
if (checkUnique) {
throw new InvalidDictionaryOperationException(dictionaryName + " is already defined");
}
} else {
newDictionary = new SubDictionary(this.dictionaryName + "." + dictionaryName, this, lock); //$NON-NLS-1$
subsetDictionaryMap.put(dictionaryName, newDictionary);
}
return newDictionary;
}
}
private void validateDictionaryName(String name) throws InvalidDictionaryOperationException {
if ("MAIN".equals(name)) { //$NON-NLS-1$
throw new InvalidDictionaryOperationException("MAIN is reserved");
}
if (StringUtils.isBlank(name) || name.matches(".*\\s.*")) { //$NON-NLS-1$
throw new InvalidDictionaryOperationException("Cannot add subset dictionary with blank(s) in name");
}
}
/**
* Build a map which represents the errors managed by this dictionary and its children
*/
public final Map getErrorMap() {
try {
lock.readLock().lock();
Map errorMap = new HashMap<>();
errorTypeList.forEach(errorType -> errorMap.put(errorType.getCode(), errorType));
subsetDictionaryMap.forEach((s, subDictionary) -> subDictionary.getErrorMap().forEach((s1, errorType) -> errorMap.put(s + "." + s1, errorType)));
return errorMap;
} finally {
lock.readLock().unlock();
}
}
/**
* Destroy a dictionary, so that it cannot be used anymore
*/
public final boolean destroy() throws InvalidDictionaryOperationException {
if (parentDictionary == null) {
throw new InvalidDictionaryOperationException("Cannot destroy the main dictionary");
}
clear();
return parentDictionary.unregister(dictionaryName.replaceAll("^(.+)\\.", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
private boolean unregister(String dictionaryName) {
return subsetDictionaryMap.remove(dictionaryName) != null;
}
/**
* Get the processing result given a message type and a list of errors raised during the process.
* It uses the dictionary to determine whether the process is valid or invalid, computes the recycling kind according to the configuration and if needed a next processing date
* If an error is unknown for the message type in the current dictionary or in a parent one, an {@link UnknownErrorException} is raised
*/
public final IProcessingResult getProcessingResult(List errorList) {
try {
lock.readLock().lock();
IProcessingResult processingResult;
if (errorList == null || errorList.isEmpty()) {
processingResult = ProcessingResultBuilder.accept();
} else {
processingResult = buildProcessingResult(errorList);
}
if (burnAfterUse) {
try {
destroy();
} catch (InvalidDictionaryOperationException ex) {
LOG.error("Couldn't destroy after use", ex);
}
}
return processingResult;
} finally {
lock.readLock().unlock();
}
}
private IProcessingResult buildProcessingResult(List errorList) {
IProcessingResult processingResult;
Worst worst = new Worst();
Map errorMap = new HashMap<>();
Try>> collect = errorList.stream().map(Try.lazyOf(this::add).andThen(trySupplier -> {
Try> pairTry = trySupplier.get();
if (pairTry.isSuccess()) {
Pair pair = pairTry.asSuccess().getResult();
errorMap.put(pair.getKey(), pair.getValue());
updateWorst(worst, pair.getValue());
} else if (pairTry.isFailure() && pairTry.asFailure().getException() instanceof UnknownErrorException) {
IProcessError processError = ((UnknownErrorException) pairTry.asFailure().getException()).getProcessError();
ErrorImpact errorImpact = ErrorImpact.of(ErrorRecyclingKind.MANUAL);
errorMap.put(processError, errorImpact);
updateWorst(worst, errorImpact);
}
return trySupplier;
})).collect(Try.collect());
Exception e = null;
if (collect.isFailure()) {
e = collect.asFailure().getException();
}
processingResult = compileProcessingResult(worst, errorMap, e);
return processingResult;
}
private Pair add(IProcessError processError) throws UnknownErrorException {
return Pair.of(processError, computeErrorImpact(processError));
}
/**
* Defines an error by adding or updating its definition of an error in the current dictionary
* Returns true if the error has been overwritten, false otherwise
*/
public final boolean defineError(IErrorType errorType) {
boolean overwritten = false;
Iterator ite = errorTypeList.iterator();
while (ite.hasNext()) {
IErrorType e = ite.next();
if (e.getCode().equals(errorType.getCode())) {
overwritten = true;
ite.remove();
}
}
errorTypeList.add(errorType);
return overwritten;
}
/**
* Clear the dictionary from all errors and subset dictionaries
*/
public void clear() {
errorTypeList.clear();
subsetDictionaryMap.clear();
}
/**
* Find the error type in the current dictionary or in a parent. If not found at all, an {@link UnknownErrorException} is raised
*/
private ErrorImpact computeErrorImpact(IProcessError processError) throws UnknownErrorException {
String errorCode = processError.getErrorCode();
Optional first = Optional.empty();
if (errorTypeList != null) {
first = errorTypeList.stream().filter(processError::matches).findFirst();
}
if (first == null || !first.isPresent()) {
if (parentDictionary == null) {
throw new UnknownErrorException(processError, "Error code '" + errorCode + "' not found " + getDictionaryExceptionString()); //$NON-NLS-1$ //$NON-NLS-2$
} else {
try {
return parentDictionary.computeErrorImpact(processError);
} catch (UnknownErrorException e) {
throw new UnknownErrorException(processError, "Error code '" + errorCode + "' not found" + getDictionaryExceptionString(), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
IErrorType errorType = first.get();
return new ErrorImpact(errorType.getRecyclingKind(), errorType.getNextRecyclingDuration());
}
private String getDictionaryExceptionString() {
return " in dictionary '" + dictionaryName + "'";
} //$NON-NLS-1$ //$NON-NLS-2$
private void updateWorst(Worst worst, ErrorImpact errorImpact) {
worst.errorRecyclingKind = ErrorRecyclingKind.getWorst(errorImpact.getRecyclingKind(), worst.errorRecyclingKind);
worst.delay = Math.max(errorImpact.getNextRecyclingDuration() != null ? errorImpact.getNextRecyclingDuration() : 0, worst.delay != null ? worst.delay : 0);
}
private IProcessingResult compileProcessingResult(Worst worst, Map errorMap, Exception e) {
switch (worst.errorRecyclingKind) {
case AUTOMATIC:
Instant nextProcessingDate = Instant.now();
nextProcessingDate.plus(worst.delay, ChronoUnit.MINUTES);
return ProcessingResultBuilder.rejectAutomatically(nextProcessingDate, errorMap, e);
case MANUAL:
return ProcessingResultBuilder.rejectManually(errorMap, e);
case NOT_RECYCLABLE:
return ProcessingResultBuilder.rejectDefinitely(errorMap, e);
default:
// case of the WARNING enum value
return ProcessingResultBuilder.acceptWithWarning(errorMap, e);
}
}
private class Worst {
ErrorRecyclingKind errorRecyclingKind;
Integer delay;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy