/*******************************************************************************
* (c) Copyright 2016 Hewlett-Packard Development Company, L.P.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License v2.0 which accompany this distribution.
*
* The Apache License is available at
* http://www.apache.org/licenses/LICENSE-2.0
*
*******************************************************************************/
package io.cloudslang.lang.compiler;
import io.cloudslang.lang.compiler.caching.CacheResult;
import io.cloudslang.lang.compiler.caching.CacheValueState;
import io.cloudslang.lang.compiler.caching.CachedPrecompileService;
import io.cloudslang.lang.compiler.modeller.SlangModeller;
import io.cloudslang.lang.compiler.modeller.model.Executable;
import io.cloudslang.lang.compiler.modeller.result.CompilationModellingResult;
import io.cloudslang.lang.compiler.modeller.result.ExecutableModellingResult;
import io.cloudslang.lang.compiler.modeller.result.ParseModellingResult;
import io.cloudslang.lang.compiler.modeller.result.SystemPropertyModellingResult;
import io.cloudslang.lang.compiler.parser.YamlParser;
import io.cloudslang.lang.compiler.parser.model.ParsedSlang;
import io.cloudslang.lang.compiler.scorecompiler.ScoreCompiler;
import io.cloudslang.lang.compiler.validator.CompileValidator;
import io.cloudslang.lang.compiler.validator.SystemPropertyValidator;
import io.cloudslang.lang.entities.CompilationArtifact;
import io.cloudslang.lang.entities.SensitivityLevel;
import io.cloudslang.lang.entities.SystemProperty;
import io.cloudslang.lang.entities.bindings.values.ValueFactory;
import io.cloudslang.lang.entities.utils.SetUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.NotImplementedException;
import static io.cloudslang.lang.compiler.SlangTextualKeys.SENSITIVE_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.VALUE_KEY;
/*
* Created by orius123 on 05/11/14.
*/
public class SlangCompilerImpl implements SlangCompiler {
public static final String NOT_A_VALID_SYSTEM_PROPERTY_FILE_ERROR_MESSAGE_SUFFIX =
"is not a valid system property file.";
public static final String ERROR_LOADING_PROPERTIES_FILE_MESSAGE =
"Error loading properties source: '";
public static final String PROPERTY_LIST_ELEMENT_WRONG_TYPE_ERROR_MESSAGE_PREFIX =
"Property list element should be map in 'key: value' format. Found: ";
public static final String SIZE_OF_SYSTEM_PROPERTY_ERROR_MESSAGE_PREFIX =
"Size of system property represented as a map should be 1 (key: value). For property: '";
public static final String SYSTEM_PROPERTY_KEY_WRONG_TYPE_ERROR_MESSAGE_PREFIX =
"System property key must be string. Found: ";
public static final String DUPLICATE_SYSTEM_PROPERTY_KEY_ERROR_MESSAGE_PREFIX =
"Duplicate system property key: '";
private YamlParser yamlParser;
private SlangModeller slangModeller;
private ScoreCompiler scoreCompiler;
private CompileValidator compileValidator;
private SystemPropertyValidator systemPropertyValidator;
private CachedPrecompileService cachedPrecompileService;
@Override
public CompilationArtifact compile(SlangSource source, Set<SlangSource> dependencySources) {
return compile(source, dependencySources, PrecompileStrategy.WITHOUT_CACHE);
}
@Override
public CompilationArtifact compile(
SlangSource source,
Set<SlangSource> path,
PrecompileStrategy precompileStrategy) {
CompilationModellingResult result = compileSource(source, path, precompileStrategy);
return getCompilationArtifact(result);
}
@Override
public CompilationArtifact compile(
SlangSource source,
Set<SlangSource> path,
PrecompileStrategy precompileStrategy,
SensitivityLevel sensitivityLevel) {
CompilationModellingResult result = getCompilationModellingResult(source, path, precompileStrategy,
sensitivityLevel);
return getCompilationArtifact(result);
}
private CompilationArtifact getCompilationArtifact(CompilationModellingResult result) {
if (result.getErrors().size() > 0) {
throw result.getErrors().get(0);
}
return result.getCompilationArtifact();
}
@Override
public CompilationModellingResult compileSource(SlangSource source, Set<SlangSource> dependencySources) {
return compileSource(source, dependencySources, PrecompileStrategy.WITHOUT_CACHE);
}
@Override
public CompilationModellingResult compileSource(
SlangSource source,
Set<SlangSource> path,
PrecompileStrategy precompileStrategy) {
return getCompilationModellingResult(source, path, precompileStrategy, SensitivityLevel.ENCRYPTED);
}
private CompilationModellingResult getCompilationModellingResult(SlangSource source, Set<SlangSource> path,
PrecompileStrategy precompileStrategy,
SensitivityLevel sensitivityLevel) {
ExecutableModellingResult executableModellingResult = preCompileSource(source, precompileStrategy,
sensitivityLevel);
List<RuntimeException> errors = executableModellingResult.getErrors();
// we transform also all of the files in the given dependency sources to model objects
Map<Executable, SlangSource> executablePairs = new HashMap<>();
executablePairs.put(executableModellingResult.getExecutable(), source);
if (CollectionUtils.isNotEmpty(path)) {
for (SlangSource currentSource : path) {
ExecutableModellingResult result = preCompileSource(currentSource, precompileStrategy,
sensitivityLevel);
Executable preCompiledCurrentSource = result.getExecutable();
errors.addAll(result.getErrors());
List<RuntimeException> validatorErrors = compileValidator
.validateNoDuplicateExecutables(preCompiledCurrentSource, currentSource, executablePairs);
errors.addAll(validatorErrors);
executablePairs.put(preCompiledCurrentSource, currentSource);
}
}
executablePairs.remove(executableModellingResult.getExecutable());
CompilationModellingResult result = scoreCompiler
.compileSource(executableModellingResult.getExecutable(), executablePairs.keySet());
errors.addAll(result.getErrors());
return new CompilationModellingResult(result.getCompilationArtifact(), errors);
}
@Override
public Executable preCompile(SlangSource source) {
return preCompile(source, PrecompileStrategy.WITHOUT_CACHE);
}
@Override
public Executable preCompile(SlangSource source, PrecompileStrategy precompileStrategy) {
ExecutableModellingResult result = preCompileSource(source, precompileStrategy);
if (result.getErrors().size() > 0) {
throw result.getErrors().get(0);
}
return result.getExecutable();
}
@Override
public ExecutableModellingResult preCompileSource(SlangSource source) {
return preCompileSource(source, PrecompileStrategy.WITHOUT_CACHE);
}
@Override
public ExecutableModellingResult preCompileSource(SlangSource source, PrecompileStrategy precompileStrategy) {
return getExecutableModellingResult(source, precompileStrategy, SensitivityLevel.ENCRYPTED);
}
@Override
public ExecutableModellingResult preCompileSource(SlangSource source, PrecompileStrategy precompileStrategy,
SensitivityLevel sensitivityLevel) {
return getExecutableModellingResult(source, precompileStrategy, sensitivityLevel);
}
private ExecutableModellingResult getExecutableModellingResult(SlangSource source,
PrecompileStrategy precompileStrategy,
SensitivityLevel sensitivityLevel) {
Validate.notNull(source, "You must supply a source to compile");
Validate.notNull(precompileStrategy, "Pre-compile strategy can not be null");
final String filePath = source.getFilePath();
// handle caching
CacheResult cacheResult = precompileCachePreExecute(source, precompileStrategy, filePath);
if (cacheResult != null && isValidCachedValue(cacheResult)) {
return cacheResult.getExecutableModellingResult();
}
ExecutableModellingResult executableModellingResult = preCompileModel(source, sensitivityLevel);
// handle caching
precompileCachePostExecute(source, precompileStrategy, filePath, executableModellingResult);
return executableModellingResult;
}
@Override
public void invalidateAllInPreCompileCache() {
cachedPrecompileService.invalidateAll();
}
@Override
public List<RuntimeException> validateSlangModelWithDirectDependencies(Executable slangModel,
Set<Executable> directDependenciesModels) {
return scoreCompiler.validateSlangModelWithDirectDependencies(slangModel, directDependenciesModels);
}
@Override
public Set<SystemProperty> loadSystemProperties(SlangSource source) {
SystemPropertyModellingResult systemPropertyModellingResult = loadSystemPropertiesFromSource(source);
if (systemPropertyModellingResult.getErrors().size() > 0) {
throw systemPropertyModellingResult.getErrors().get(0);
}
return systemPropertyModellingResult.getSystemProperties();
}
@Override
public SystemPropertyModellingResult loadSystemPropertiesFromSource(SlangSource source) {
ParseModellingResult parseModellingResult = parseSystemPropertiesFile(source);
return extractProperties(parseModellingResult.getParsedSlang(), source, parseModellingResult.getErrors());
}
private void precompileCachePostExecute(
SlangSource source,
PrecompileStrategy precompileStrategy,
String filePath,
ExecutableModellingResult executableModellingResult) {
switch (precompileStrategy) {
case WITH_CACHE:
cachedPrecompileService.cacheValue(filePath, executableModellingResult, source);
break;
case WITHOUT_CACHE:
break;
default:
throw new NotImplementedException(generatePreCompileTypeErrorMessage(precompileStrategy));
}
}
private CacheResult precompileCachePreExecute(
SlangSource source,
PrecompileStrategy precompileStrategy,
String filePath) {
CacheResult cacheResult = null;
switch (precompileStrategy) {
case WITH_CACHE:
cacheResult = cachedPrecompileService.getValueFromCache(filePath, source);
break;
case WITHOUT_CACHE:
break;
default:
throw new NotImplementedException(generatePreCompileTypeErrorMessage(precompileStrategy));
}
return cacheResult;
}
private String generatePreCompileTypeErrorMessage(PrecompileStrategy precompileStrategy) {
return "Precompile type[" + precompileStrategy + "] not yet implemented";
}
private ExecutableModellingResult preCompileModel(SlangSource source, SensitivityLevel sensitivityLevel) {
//first thing we parse the yaml file into java maps
ParsedSlang parsedSlang = yamlParser.parse(source);
ParseModellingResult parseModellingResult = yamlParser.validate(parsedSlang);
// Then we transform the parsed Slang source to a Slang model
return slangModeller.createModel(parseModellingResult, sensitivityLevel);
}
private boolean isValidCachedValue(CacheResult cacheResult) {
CacheValueState cacheValueState = cacheResult.getState();
switch (cacheValueState) {
case VALID:
return true;
case MISSING:
case OUTDATED:
return false;
default:
throw new NotImplementedException("Cache value state[" + cacheValueState + "] not yet implemented");
}
}
private ParseModellingResult parseSystemPropertiesFile(SlangSource source) {
List<RuntimeException> exceptions = new ArrayList<>();
ParsedSlang parsedSlang = null;
try {
parsedSlang = yamlParser.parse(source);
if (!ParsedSlang.Type.SYSTEM_PROPERTY_FILE.equals(parsedSlang.getType())) {
throw new RuntimeException("Source: " + parsedSlang.getName() + " " +
NOT_A_VALID_SYSTEM_PROPERTY_FILE_ERROR_MESSAGE_SUFFIX);
}
} catch (Throwable ex) {
exceptions.add(getException(source, ex));
}
try {
parsedSlang = yamlParser.validateAndThrowFirstError(parsedSlang);
} catch (RuntimeException ex) {
exceptions.add(getException(source, ex));
}
return new ParseModellingResult(parsedSlang, exceptions);
}
private RuntimeException getException(SlangSource source, Throwable ex) {
return new RuntimeException(
ERROR_LOADING_PROPERTIES_FILE_MESSAGE + source.getName() + "'. Nested exception is: " + ex.getMessage(),
ex);
}
private SystemPropertyModellingResult extractProperties(ParsedSlang parsedSlang, SlangSource source,
List<RuntimeException> exceptions) {
Set<SystemProperty> modelledSystemProperties = new HashSet<>();
Set<String> modelledSystemPropertyKeys = new HashSet<>();
if (parsedSlang != null) {
// parsedSlang is null when properties yaml node is not defined in the property .sl file
List<Map<String, Object>> parsedSystemProperties =
convertRawProperties(parsedSlang.getProperties(), source, exceptions);
for (Map<String, Object> propertyAsMap : parsedSystemProperties) {
Map.Entry<String, Object> propertyAsEntry = propertyAsMap.entrySet().iterator().next();
String propertyKey = getPropertyKey(propertyAsEntry, source, exceptions);
if (SetUtils.containsIgnoreCase(modelledSystemPropertyKeys, propertyKey)) {
exceptions.add(getException(source, new RuntimeException(
DUPLICATE_SYSTEM_PROPERTY_KEY_ERROR_MESSAGE_PREFIX + propertyKey + "'.")));
} else {
modelledSystemPropertyKeys.add(propertyKey);
}
Object propertyValue = propertyAsEntry.getValue();
SystemProperty property =
transformSystemProperty(parsedSlang.getNamespace(), propertyKey, propertyValue);
modelledSystemProperties.add(property);
}
}
return new SystemPropertyModellingResult(modelledSystemProperties, exceptions);
}
private String getPropertyKey(Map.Entry<String, Object> propertyAsEntry, SlangSource source,
List<RuntimeException> exceptions) {
String propertyKey = propertyAsEntry.getKey();
try {
systemPropertyValidator.validateKey(propertyKey);
} catch (RuntimeException ex) {
exceptions.add(getException(source, ex));
}
return propertyKey;
}
// casting and validations
private List<Map<String, Object>> convertRawProperties(Object propertiesAsObject, SlangSource source,
List<RuntimeException> exceptions) {
List<Map<String, Object>> convertedProperties = new ArrayList<>();
if (propertiesAsObject != null) {
if (propertiesAsObject instanceof List) {
List propertiesAsList = (List) propertiesAsObject;
for (Object propertyAsObject : propertiesAsList) {
if (propertyAsObject instanceof Map) {
Map propertyAsMap = (Map) propertyAsObject;
if (propertyAsMap.size() == 1) {
Map.Entry propertyAsEntry = (Map.Entry) propertyAsMap.entrySet().iterator().next();
Object propertyKeyAsObject = propertyAsEntry.getKey();
if (propertyKeyAsObject instanceof String) {
Map<String, Object> convertedProperty = new HashMap<>();
convertedProperty.put((String) propertyKeyAsObject, propertyAsEntry.getValue());
convertedProperties.add(convertedProperty);
} else {
exceptions.add(getException(source, new RuntimeException(
SYSTEM_PROPERTY_KEY_WRONG_TYPE_ERROR_MESSAGE_PREFIX +
propertyKeyAsObject +
"(" + propertyKeyAsObject.getClass().getName() + ")."
)));
}
} else {
exceptions.add(getException(source, new RuntimeException(
SIZE_OF_SYSTEM_PROPERTY_ERROR_MESSAGE_PREFIX +
propertyAsMap + "' size is: " + propertyAsMap.size() + "."
)));
}
} else {
String errorMessageSuffix;
if (propertyAsObject == null) {
errorMessageSuffix = "null.";
} else {
errorMessageSuffix = propertyAsObject.toString() + "(" +
propertyAsObject.getClass().getName() + ").";
}
exceptions.add(getException(source, new RuntimeException(
PROPERTY_LIST_ELEMENT_WRONG_TYPE_ERROR_MESSAGE_PREFIX + errorMessageSuffix
)));
}
}
} else {
exceptions.add(getException(source, new RuntimeException(
"Under '" + SlangTextualKeys.SYSTEM_PROPERTY_KEY +
"' key there should be a list. Found: " + propertiesAsObject.getClass().getName() + "."
)));
}
}
return convertedProperties;
}
private SystemProperty transformSystemProperty(
String rawNamespace,
String key,
Object rawValue) {
String namespace = rawNamespace == null ? "" : rawNamespace;
if (rawValue == null) {
return new SystemProperty(namespace, key, (String) null);
}
if (rawValue instanceof Map) {
Map rawModifiers = (Map) rawValue;
Map<String, Serializable> modifiers = convertRawMap(rawModifiers, key);
List<String> knownModifierKeys = Arrays.asList(SENSITIVE_KEY, VALUE_KEY);
for (String modifierKey : modifiers.keySet()) {
if (!knownModifierKeys.contains(modifierKey)) {
throw new RuntimeException(
"Artifact {" + key + "} has unrecognized tag {" + modifierKey + "}" +
". Please take a look at the supported features per versions link");
}
}
Serializable valueAsSerializable = modifiers.get(VALUE_KEY);
String value = valueAsSerializable == null ? null : valueAsSerializable.toString();
boolean sensitive = modifiers.containsKey(SENSITIVE_KEY) && (boolean) modifiers.get(SENSITIVE_KEY);
if (sensitive) {
return new SystemProperty(namespace, key, ValueFactory.createEncryptedString(value));
} else {
return new SystemProperty(namespace, key, value);
}
} else {
return new SystemProperty(namespace, key, rawValue.toString());
}
}
private Map<String, Serializable> convertRawMap(Map rawMap, String artifact) {
Map<String, Serializable> convertedMap = new HashMap<>();
@SuppressWarnings("unchecked")
Set<Map.Entry> entrySet = rawMap.entrySet();
for (Map.Entry entry : entrySet) {
Object rawKey = entry.getKey();
if (!(rawKey instanceof String)) {
throw new RuntimeException(
"Artifact {" + artifact + "} has invalid tag {" + rawKey + "}:" +
" Value cannot be cast to String"
);
}
Object rawValue = entry.getValue();
if (!(rawValue instanceof Serializable)) {
throw new RuntimeException(
"Artifact {" + artifact + "} has invalid value {" + rawValue + "}:" +
" Value cannot be cast to Serializable"
);
}
convertedMap.put((String) rawKey, (Serializable) rawValue);
}
return convertedMap;
}
public void setYamlParser(YamlParser yamlParser) {
this.yamlParser = yamlParser;
}
public void setSlangModeller(SlangModeller slangModeller) {
this.slangModeller = slangModeller;
}
public void setScoreCompiler(ScoreCompiler scoreCompiler) {
this.scoreCompiler = scoreCompiler;
}
public void setCompileValidator(CompileValidator compileValidator) {
this.compileValidator = compileValidator;
}
public void setSystemPropertyValidator(SystemPropertyValidator systemPropertyValidator) {
this.systemPropertyValidator = systemPropertyValidator;
}
public void setCachedPrecompileService(CachedPrecompileService cachedPrecompileService) {
this.cachedPrecompileService = cachedPrecompileService;
}
}