/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.workbench.common.services.datamodel.backend.server.builder.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.drools.core.util.MVELSafeHelper;
import org.mvel2.MVEL;
import org.mvel2.ParserConfiguration;
import org.mvel2.ParserContext;
/**
* Use MVEL to load up map/list of valid items for fields - used by the Guided rule editor.
*/
public class DataEnumLoader {
private final List<String> errors;
private final Map<String, String[]> data;
/**
* This is the source of the asset, which is an MVEL map (minus the outer "[") of course.
*/
public DataEnumLoader( final String mvelSource ) {
this( mvelSource,
Thread.currentThread().getContextClassLoader() );
}
/**
* This is the source of the asset, which is an MVEL map (minus the outer "[") of course.
*/
public DataEnumLoader( final String mvelSource,
final ClassLoader classLoader ) {
this.errors = new ArrayList<String>();
this.data = loadEnum( mvelSource,
classLoader );
}
private Map<String, String[]> loadEnum( String mvelSource,
ClassLoader classLoader ) {
if ( mvelSource == null || ( mvelSource.trim().equals( "" ) ) ) {
return Collections.emptyMap();
}
if ( mvelSource.startsWith( "=" ) ) {
mvelSource = mvelSource.substring( 1 );
} else {
mvelSource = "[ " + addCommasForNewLines( mvelSource ) + " ]";
}
final Object mvelData;
try {
final ParserConfiguration pconf = new ParserConfiguration();
final ParserContext pctx = new ParserContext( pconf );
pconf.setClassLoader( classLoader );
final Serializable compiled = MVEL.compileExpression( mvelSource,
pctx );
mvelData = MVELSafeHelper.getEvaluator().executeExpression( compiled,
new HashMap<String, Object>() );
} catch ( RuntimeException e ) {
addError( "Unable to load enumeration data." );
addError( e.getMessage() );
addError( "Error type: " + e.getClass().getName() );
return Collections.emptyMap();
}
if ( !( mvelData instanceof Map<?, ?> ) ) {
addError( "The expression is not a map, it is a " + mvelData.getClass().getName() );
return Collections.emptyMap();
}
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) mvelData;
Map<String, String[]> newMap = new HashMap<String, String[]>();
for ( Map.Entry<String, Object> entry : map.entrySet() ) {
String key = makeEnumKey( entry.getKey() );
validateKey( key );
Object list = entry.getValue();
if ( !( list instanceof List<?> || list instanceof String ) ) {
if ( list == null ) {
addError( "The item with " + key + " is null." );
} else {
addError( "The item with " + key + " is not a list or a string, it is a " + list.getClass().getName() );
}
return Collections.emptyMap();
} else if ( list instanceof String ) {
newMap.put( key, new String[]{ (String) list } );
} else {
List<?> items = (List<?>) list;
String[] newItems = new String[ items.size() ];
for ( int i = 0; i < items.size(); i++ ) {
Object listItem = items.get( i );
if ( !( listItem instanceof String ) ) {
newItems[ i ] = listItem.toString();
} else {
newItems[ i ] = (String) listItem;
}
}
newMap.put( key, newItems );
}
}
return newMap;
}
private void validateKey( final String key ) {
final Pattern pattern = Pattern.compile( ".*(\\[.*\\])" );
final Matcher matcher = pattern.matcher( key );
if ( !matcher.matches() ) {
return;
}
if ( matcher.groupCount() > 2 ) {
errors.add( "Invalid dependent definition: Only [..] accepted." );
return;
}
final String dependencySegment = matcher.group( 1 );
if ( dependencySegment.equals( "[]" ) ) {
errors.add( "Invalid dependent definition: Empty [] detected." );
return;
}
if ( dependencySegment.contains( "\"" ) ) {
errors.add( "Invalid dependent definition: Found quote literal." );
return;
}
if ( dependencySegment.contains( "=" ) ) {
validateSimpleEnumKey( dependencySegment );
} else {
validateAdvancedEnumKey( dependencySegment );
}
}
private void validateSimpleEnumKey( final String dependencySegment ) {
if ( dependencySegment.matches( "\\[\\s*=\\s*\\]" ) ) {
errors.add( "Invalid dependent definition: No field or value detected." );
return;
}
if ( dependencySegment.matches( "\\[\\s*=\\S+\\]" ) ) {
errors.add( "Invalid dependent definition: No field detected." );
return;
}
if ( dependencySegment.matches( "\\[\\S+=\\s*\\]" ) ) {
errors.add( "Invalid dependent definition: No value detected." );
return;
}
}
private void validateAdvancedEnumKey( final String dependencySegment ) {
if ( dependencySegment.matches( "\\[\\s*,\\s*\\]" ) ) {
errors.add( "Invalid definition: Field definitions are incomplete." );
return;
}
if ( dependencySegment.matches( "\\[\\s*,\\S+\\]" ) ) {
errors.add( "Invalid definition: Field definitions are incomplete." );
return;
}
if ( dependencySegment.matches( "\\[\\S+,\\s*\\]" ) ) {
errors.add( "Invalid definition: Field definitions are incomplete." );
return;
}
}
private String addCommasForNewLines( String mvelSource ) {
StringTokenizer st = new StringTokenizer( mvelSource, "\r\n" );
StringBuilder buf = new StringBuilder();
while ( st.hasMoreTokens() ) {
String line = st.nextToken().trim();
if ( st.hasMoreTokens() && line.endsWith( "," ) ) {
buf.append( line );
} else {
buf.append( line );
if ( st.hasMoreTokens() ) {
buf.append( "," );
}
}
if ( st.hasMoreTokens() ) {
buf.append( "\n" );
}
}
return buf.toString();
}
private void addError( String string ) {
this.errors.add( string );
}
/**
* Return a list of any errors found.
*/
public List<String> getErrors() {
return this.errors;
}
public boolean hasErrors() {
return this.errors.size() > 0;
}
/**
* Return the map of Fact.field to List (of Strings).
*/
public Map<String, String[]> getData() {
return this.data;
}
private String makeEnumKey( final String userDefinedKey ) {
//Use of "." as a delimiter between Fact and Field leads to problems with fully qualified class names
String systemDefinedKey = userDefinedKey.replace( ".",
"#" );
return systemDefinedKey;
}
}