/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.database.execution.validator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import de.rcenvironment.components.database.common.DatabaseComponentConstants;
import de.rcenvironment.components.database.common.DatabaseStatement;
import de.rcenvironment.core.component.model.api.ComponentDescription;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDescription;
import de.rcenvironment.core.component.validation.api.ComponentValidationMessage;
import de.rcenvironment.core.component.validation.spi.AbstractComponentValidator;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;
/**
* Validator for Database component.
*
* @author Oliver Seebach
* @author Jascha Riedel
*/
public class DatabaseComponentValidator extends AbstractComponentValidator {
private static final String THE_STATEMENT = "The statement";
private static final String APOSTROPHE_PERIOD = "'.";
private static final String APOSTROPHE_BLANK = "' ";
private static final String BLANK_APOSTROPHE = " '";
private static final String SEMICOLON = ";";
@Override
public String getIdentifier() {
return DatabaseComponentConstants.COMPONENT_ID;
}
@Override
protected List<ComponentValidationMessage> validateComponentSpecific(ComponentDescription componentDescription) {
List<ComponentValidationMessage> messages = new ArrayList<>();
// CHECK IF DATABASE (host, port, scheme) IS DEFINED
checkIfDatabaseDefinitionIsNotEmpty(componentDescription, messages);
// READ STATEMENTS
ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
List<DatabaseStatement> models = new ArrayList<>();
try {
String modelsString = getProperty(componentDescription, DatabaseComponentConstants.DB_STATEMENTS_KEY);
if (modelsString != null) {
models = mapper.readValue(modelsString,
mapper.getTypeFactory().constructCollectionType(List.class, DatabaseStatement.class));
}
} catch (JsonGenerationException | JsonMappingException e) {
final ComponentValidationMessage parsingError = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DB_STATEMENTS_KEY,
"Failed to parse database statements.", "Failed to parse database statements.");
messages.add(parsingError);
} catch (IOException e) {
final ComponentValidationMessage readingFromFilesystemError = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DB_STATEMENTS_KEY,
"Failed to read database statements from filesystem.",
"Failed to read database statements from filesystem.");
messages.add(readingFromFilesystemError);
}
// CHECK IF ALL STATEMENTS HAVE A NAME -> WARNING
checkIfAllStatementsAreNamed(messages, models);
// CHECK IF NO STATEMENT IS EMPTY -> ERROR
checkIfNoStatementIsEmpty(messages, models);
// CHECK VALID BEGINNING / TYPE
checkIfStatementTypeIsValid(messages, models);
// CHECK SMALLTABLE JUST IN INSERT
checkIfSmallTableInputJustInInsert(componentDescription, messages, models);
// CHECK THAT JUST ONE STATEMENT IS PRESENT
checkIfJustOneStatementIsPresent(messages, models);
// CHECK THAT IF WRITE TO OUTPUT IS CHECKED ALSO AN OUTPUT IS SELECTED
checkIfOutputIsSelectedWhenChecked(messages, models);
// CHECK THAT SELECT STATEMENT ALSO HAS AN OUTPUT DEFINED
checkIfSelectStatementHasOutput(messages, models);
return messages;
}
@Override
protected List<ComponentValidationMessage> validateOnWorkflowStartComponentSpecific(
ComponentDescription componentDescription) {
return null;
}
private void checkIfAllStatementsAreNamed(final List<ComponentValidationMessage> messages,
List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
if (statement.getName().isEmpty()) {
String warningMessage = "The component has as least one statement with an empty name.";
final ComponentValidationMessage statementNameEmpty = new ComponentValidationMessage(
ComponentValidationMessage.Type.WARNING, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(statementNameEmpty);
break;
}
}
}
private void checkIfNoStatementIsEmpty(final List<ComponentValidationMessage> messages,
List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
if (statement.getStatement().isEmpty()) {
String warningMessage = THE_STATEMENT + BLANK_APOSTROPHE + statement.getName() + "' is empty.";
final ComponentValidationMessage statementEmpty = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(statementEmpty);
}
}
}
private void checkIfStatementTypeIsValid(final List<ComponentValidationMessage> messages,
List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
String statementPrefix = statement.getStatement().toLowerCase().split(" ")[0];
if (!(Arrays.asList(DatabaseComponentConstants.STATEMENT_PREFIX_WHITELIST_GENERAL)
.contains(statementPrefix))) {
String warningMessage = THE_STATEMENT + BLANK_APOSTROPHE + statement.getName() + "' could not be recognized. "
+ "Does it not start with 'Select', 'Insert', 'Delete' or 'Update'? "
+ "See help for further information.";
final ComponentValidationMessage notRecognizedStatementType = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(notRecognizedStatementType);
}
}
}
private void checkIfSmallTableInputJustInInsert(ComponentDescription componentDescription,
final List<ComponentValidationMessage> messages, List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
String statementPrefix = statement.getStatement().toLowerCase().split(" ")[0];
if (!(Arrays.asList(DatabaseComponentConstants.STATEMENT_PREFIX_WHITELIST_SMALLTABLE)
.contains(statementPrefix))) {
for (String smalltablePlacerholder : getSmalltableInputPlaceholders(componentDescription)) {
if (statement.getStatement().contains(smalltablePlacerholder)) {
String warningMessage = THE_STATEMENT + APOSTROPHE_BLANK + statement.getName()
+ "' contains an input of type small table but is not an 'insert' statement. The statement is: '"
+ statement.getStatement() + APOSTROPHE_PERIOD;
final ComponentValidationMessage smalltableInputNotAllowedExceptInsert = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(smalltableInputNotAllowedExceptInsert);
}
}
}
}
}
private List<String> getSmalltableInputPlaceholders(ComponentDescription componentDescription) {
List<String> smalltableInputPlaceholders = new ArrayList<>();
for (EndpointDescription endpointDescription : getInputs(componentDescription, DataType.SmallTable)) {
String placeholder = StringUtils.format(DatabaseComponentConstants.INPUT_PLACEHOLDER_PATTERN,
endpointDescription.getName());
smalltableInputPlaceholders.add(placeholder);
}
return smalltableInputPlaceholders;
}
private void checkIfJustOneStatementIsPresent(final List<ComponentValidationMessage> messages,
List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
if (statement.getStatement().contains(SEMICOLON)) {
String statementAfterFirstSemicolon = statement.getStatement()
.substring(statement.getStatement().indexOf(SEMICOLON));
for (String prefix : Arrays.asList(DatabaseComponentConstants.STATEMENT_PREFIX_WHITELIST_GENERAL)) {
if (statementAfterFirstSemicolon.toLowerCase().contains(prefix)) {
String warningMessage = THE_STATEMENT + APOSTROPHE_BLANK + statement.getName()
+ "' possibly two statements were entered in the statement textfield. " + "It is '"
+ statement.getStatement() + APOSTROPHE_PERIOD;
final ComponentValidationMessage possiblyTwoStatements = new ComponentValidationMessage(
ComponentValidationMessage.Type.WARNING, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(possiblyTwoStatements);
}
}
}
}
}
private void checkIfSelectStatementHasOutput(final List<ComponentValidationMessage> messages,
List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
if (statement.getStatement().trim().toLowerCase().startsWith(DatabaseComponentConstants.SELECT)
&& (!statement.isWillWriteToOutput() || statement.getOutputToWriteTo().isEmpty())) {
String warningMessage = THE_STATEMENT + APOSTROPHE_BLANK + statement.getName()
+ "' is a 'SELECT' statement but has not output defined to write to.";
final ComponentValidationMessage selectStatementHasNoOutput = new ComponentValidationMessage(
ComponentValidationMessage.Type.WARNING, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(selectStatementHasNoOutput);
}
}
}
private void checkIfOutputIsSelectedWhenChecked(final List<ComponentValidationMessage> messages,
List<DatabaseStatement> models) {
for (DatabaseStatement statement : models) {
if (statement.isWillWriteToOutput() && statement.getOutputToWriteTo().isEmpty()) {
String warningMessage = THE_STATEMENT + APOSTROPHE_BLANK + statement.getName()
+ "' is configured to write to an output but no output is selected yet.";
final ComponentValidationMessage outputCheckedButNoDefined = new ComponentValidationMessage(
ComponentValidationMessage.Type.WARNING, DatabaseComponentConstants.DB_STATEMENTS_KEY,
warningMessage, warningMessage);
messages.add(outputCheckedButNoDefined);
}
}
}
private void checkIfDatabaseDefinitionIsNotEmpty(ComponentDescription componentDescription,
final List<ComponentValidationMessage> messages) {
if (getProperty(componentDescription, DatabaseComponentConstants.DATABASE_HOST) == null) {
messages.add(getEmptyHostWarning());
} else if (getProperty(componentDescription, DatabaseComponentConstants.DATABASE_HOST).isEmpty()) {
messages.add(getEmptyHostWarning());
}
if (getProperty(componentDescription, DatabaseComponentConstants.DATABASE_PORT) == null) {
messages.add(getEmptyPortWarning());
} else if (getProperty(componentDescription, DatabaseComponentConstants.DATABASE_PORT).isEmpty()) {
messages.add(getEmptyPortWarning());
}
if (getProperty(componentDescription, DatabaseComponentConstants.DATABASE_SCHEME) == null) {
messages.add(getEmptySchemeWarning());
} else if (getProperty(componentDescription, DatabaseComponentConstants.DATABASE_SCHEME).isEmpty()) {
messages.add(getEmptySchemeWarning());
}
}
private ComponentValidationMessage getEmptySchemeWarning() {
String warningMessage = "Database Scheme needs to be defined - see Database tab";
final ComponentValidationMessage databaseSchemeNull = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DATABASE_SCHEME, warningMessage,
warningMessage);
return databaseSchemeNull;
}
private ComponentValidationMessage getEmptyPortWarning() {
String warningMessage = "Database Port needs to be defined - see Database tab";
final ComponentValidationMessage databasePortNull = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DATABASE_PORT, warningMessage,
warningMessage);
return databasePortNull;
}
private ComponentValidationMessage getEmptyHostWarning() {
String warningMessage = "Database Host needs to be defined - see Database tab";
final ComponentValidationMessage databaseHostNull = new ComponentValidationMessage(
ComponentValidationMessage.Type.ERROR, DatabaseComponentConstants.DATABASE_HOST, warningMessage,
warningMessage);
return databaseHostNull;
}
}