com.bazaarvoice.jolt.shiftr.spec.ShiftrCompositeSpec Maven / Gradle / Ivy
/*
* Copyright 2013 Bazaarvoice, Inc.
*
* 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 com.bazaarvoice.jolt.shiftr.spec;
import com.bazaarvoice.jolt.common.pathelement.*;
import com.bazaarvoice.jolt.exception.SpecException;
import com.bazaarvoice.jolt.common.WalkedPath;
import java.util.*;
/**
* Spec that has children, which it builds and then manages during Transforms.
*/
public class ShiftrCompositeSpec extends ShiftrSpec {
/*
Example of how a Spec gets parsed into Composite and LeafSpec objects :
{ // "implicit" root CompositeSpec, with one specialChild ("@") and one literalChild ("rating")
"@" : [ "payload.original", "payload.secondCopy" ] // LeafSpec with an AtPathElement and outputWriters [ "payload.original", "payload.secondCopy" ]
"rating": { // CompositeSpec with 1 literalChild ("rating") and one computedChild ("*")
"primary": {
"value": "Rating",
"max": "RatingRange"
},
"*": {
"value": "SecondaryRatings.&1.Value", // LeafSpec with a LiteralPathElement and one outputWriter [ "SecondaryRatings.&1.Value" ]
"max": "SecondaryRatings.&1.Range",
"&": "SecondaryRatings.&1.Id" // & with no children : specialKey : Means use the text value of the key as the input
}
}
}
*/
private static final ComputedKeysComparator computedKeysComparator = new ComputedKeysComparator();
// Three different buckets for the children of this CompositeSpec
private final List specialChildren; // children that aren't actually triggered off the input data
private final Map literalChildren; // children that are simple exact matches against the input data
private final List computedChildren; // children that are regex matches against the input data
private final ExecutionStrategy executionStrategy;
public ShiftrCompositeSpec(String rawKey, Map spec ) {
super( rawKey );
ArrayList special = new ArrayList<>();
Map literals = new LinkedHashMap<>();
ArrayList computed = new ArrayList<>();
// self check
if ( pathElement instanceof AtPathElement ) {
throw new SpecException( "@ Shiftr key, can not have children." );
}
if ( pathElement instanceof DollarPathElement ) {
throw new SpecException( "$ Shiftr key, can not have children." );
}
List children = createChildren( spec );
if ( children.isEmpty() ) {
throw new SpecException( "Shift ShiftrSpec format error : ShiftrSpec line with empty {} as value is not valid." );
}
for ( ShiftrSpec child : children ) {
if ( child.pathElement instanceof LiteralPathElement ) {
literals.put( child.pathElement.getRawKey(), child );
}
// special is it is "@" or "$"
else if ( child.pathElement instanceof AtPathElement ||
child.pathElement instanceof HashPathElement ||
child.pathElement instanceof DollarPathElement ||
child.pathElement instanceof TransposePathElement ) {
special.add( child );
}
else { // star || (& with children)
computed.add( child );
}
}
// Only the computed children need to be sorted
Collections.sort(computed, computedKeysComparator);
special.trimToSize();
computed.trimToSize();
specialChildren = Collections.unmodifiableList( special );
literalChildren = Collections.unmodifiableMap( literals );
computedChildren = Collections.unmodifiableList( computed );
executionStrategy = ExecutionStrategy.determineStrategy( literalChildren, computedChildren );
}
/**
* Recursively walk the spec input tree.
*/
private static List createChildren( Map rawSpec ) {
List result = new ArrayList<>();
Set actualKeys = new HashSet<>();
for ( String rawLhsStr : rawSpec.keySet() ) {
Object rawRhs = rawSpec.get( rawLhsStr );
String[] keyStrings = rawLhsStr.split( "\\|" ); // unwrap the syntactic sugar of the OR
for ( String keyString : keyStrings ) {
ShiftrSpec childSpec;
if( rawRhs instanceof Map ) {
childSpec = new ShiftrCompositeSpec(keyString, (Map) rawRhs );
}
else {
childSpec = new ShiftrLeafSpec(keyString, rawRhs );
}
String childCanonicalString = childSpec.pathElement.getCanonicalForm();
if ( actualKeys.contains( childCanonicalString ) ) {
throw new IllegalArgumentException( "Duplicate canonical Shiftr key found : " + childCanonicalString );
}
actualKeys.add( childCanonicalString );
result.add(childSpec);
}
}
return result;
}
// visible for test
List getComputedChildren() {
return computedChildren;
}
/**
* If this Spec matches the inputKey, then perform one step in the Shiftr parallel treewalk.
*
* Step one level down the input "tree" by carefully handling the List/Map nature the input to
* get the "one level down" data.
*
* Step one level down the Spec tree by carefully and efficiently applying our children to the
* "one level down" data.
*
* @return true if this this spec "handles" the inputKey such that no sibling specs need to see it
*/
@Override
public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map output )
{
LiteralPathElement thisLevel = pathElement.match( inputKey, walkedPath );
if ( thisLevel == null ) {
return false;
}
// add ourselves to the path, so that our children can reference us
walkedPath.add( input, thisLevel );
// Handle any special / key based children first, but don't have them block anything
for( ShiftrSpec subSpec : specialChildren ) {
subSpec.apply( inputKey, input, walkedPath, output );
}
// Handle the rest of the children
executionStrategy.process( this, input, walkedPath, output );
// We are done, so remove ourselves from the walkedPath
walkedPath.removeLast();
// we matched so increment the matchCount of our parent
walkedPath.lastElement().getLiteralPathElement().incrementHashCount();
return true;
}
private enum ExecutionStrategy {
/**
* The performance assumption built into this code is that the literal values in the spec, are generally smaller
* than the number of potential keys to check in the input.
*
* More specifically, the assumption here is that the set of literalChildren is smaller than the input "keyset".
*/
LITERALS_ONLY {
@Override
void processMap( ShiftrCompositeSpec spec, Map inputMap, WalkedPath walkedPath, Map output ) {
for( String key : spec.literalChildren.keySet() ) {
Object subInput = inputMap.get( key );
if ( subInput != null ) {
// we know the .get(key) will not return null
spec.literalChildren.get( key ).apply( key, subInput, walkedPath, output );
}
}
}
@Override
void processList( ShiftrCompositeSpec spec, List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy