blob: 87bbe51505702cb2da9e307a56c913936a903fe6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.knox.gateway.filter.rewrite.impl.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterApplyDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterBufferDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterContentDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterDetectDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterGroupDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterPathDescriptor;
import org.apache.knox.gateway.filter.rewrite.i18n.UrlRewriteMessages;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.util.JsonPath;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
class JsonFilterReader extends Reader {
private static final UrlRewriteMessages LOG = MessagesFactory.get( UrlRewriteMessages.class );
private static final UrlRewriteFilterPathDescriptor.Compiler<JsonPath.Expression> JPATH_COMPILER = new JsonPathCompiler();
private static final UrlRewriteFilterPathDescriptor.Compiler<Pattern> REGEX_COMPILER = new RegexCompiler();
private JsonFactory factory;
private JsonParser parser;
private JsonGenerator generator;
private ObjectMapper mapper;
private Reader reader;
private int offset;
private StringWriter writer;
private StringBuffer buffer;
private Stack<Level> stack;
private Level bufferingLevel;
private UrlRewriteFilterBufferDescriptor bufferingConfig;
private UrlRewriteFilterGroupDescriptor config;
JsonFilterReader( Reader reader, UrlRewriteFilterContentDescriptor config ) throws IOException {
this.reader = reader;
factory = new JsonFactory();
mapper = new ObjectMapper();
parser = factory.createParser( reader );
writer = new StringWriter();
buffer = writer.getBuffer();
offset = 0;
generator = factory.createGenerator( writer );
stack = new Stack<>();
bufferingLevel = null;
bufferingConfig = null;
this.config = config;
}
@Override
public int read( char[] destBuffer, int destOffset, int destCount ) throws IOException {
int count = 0;
int available = buffer.length() - offset;
if( available == 0 ) {
JsonToken token = parser.nextToken();
if( token == null ) {
count = -1;
} else {
processCurrentToken();
available = buffer.length() - offset;
}
}
if( available > 0 ) {
count = Math.min( destCount, available );
buffer.getChars( offset, offset+count, destBuffer, destOffset );
offset += count;
if( offset == buffer.length() ) {
offset = 0;
buffer.setLength( 0 );
}
}
return count;
}
private void processCurrentToken() throws IOException {
switch( parser.getCurrentToken() ) {
case START_OBJECT:
processStartObject();
break;
case END_OBJECT:
processEndObject();
break;
case START_ARRAY:
processStartArray();
break;
case END_ARRAY:
processEndArray();
break;
case FIELD_NAME:
processFieldName(); // Could be the name of an object, array or value.
break;
case VALUE_STRING:
processValueString();
break;
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
processValueNumber();
break;
case VALUE_TRUE:
case VALUE_FALSE:
processValueBoolean();
break;
case VALUE_NULL:
processValueNull();
break;
case NOT_AVAILABLE:
// Ignore it.
break;
}
generator.flush();
}
private Level pushLevel( String field, JsonNode node, JsonNode scopeNode, UrlRewriteFilterGroupDescriptor scopeConfig ) {
if( !stack.isEmpty() ) {
Level top = stack.peek();
if( scopeNode == null ) {
scopeNode = top.scopeNode;
scopeConfig = top.scopeConfig;
}
}
Level level = new Level( field, node, scopeNode, scopeConfig );
stack.push( level );
return level;
}
private void processStartObject() throws IOException {
JsonNode node;
Level child;
Level parent;
if( stack.isEmpty() ) {
node = mapper.createObjectNode();
child = pushLevel( null, node, node, config );
} else {
child = stack.peek();
if( child.node == null ) {
child.node = mapper.createObjectNode();
parent = stack.get( stack.size()-2 );
switch( parent.node.asToken() ) {
case START_ARRAY:
((ArrayNode)parent.node ).add( child.node );
break;
case START_OBJECT:
((ObjectNode)parent.node ).set( child.field, child.node );
break;
default:
throw new IllegalStateException();
}
} else if( child.isArray() ) {
parent = child;
node = mapper.createObjectNode();
child = pushLevel( null, node, null, null );
((ArrayNode)parent.node ).add( child.node );
} else {
throw new IllegalStateException();
}
}
if( bufferingLevel == null && !startBuffering( child ) ) {
generator.writeStartObject();
}
}
private void processEndObject() throws IOException {
Level child;
Level parent;
child = stack.pop();
if( child.equals(bufferingLevel) ) {
filterBufferedNode( child );
mapper.writeTree( generator, child.node );
bufferingLevel = null;
bufferingConfig = null;
} else if( bufferingLevel == null ) {
generator.writeEndObject();
if( !stack.isEmpty() ) {
parent = stack.peek();
switch( parent.node.asToken() ) {
case START_ARRAY:
((ArrayNode)parent.node ).removeAll();
break;
case START_OBJECT:
((ObjectNode)parent.node ).removeAll();
break;
default:
throw new IllegalStateException();
}
}
}
}
private void processStartArray() throws IOException {
JsonNode node;
Level child;
Level parent;
if( stack.isEmpty() ) {
node = mapper.createArrayNode();
child = pushLevel( null, node, node, config );
} else {
child = stack.peek();
if( child.node == null ) {
child.node = mapper.createArrayNode();
parent = stack.get( stack.size() - 2 );
switch( parent.node.asToken() ) {
case START_ARRAY:
((ArrayNode)parent.node ).add( child.node );
break;
case START_OBJECT:
((ObjectNode)parent.node ).set( child.field, child.node );
break;
default:
throw new IllegalStateException();
}
} else if( child.isArray() ) {
parent = child;
child = pushLevel( null, mapper.createArrayNode(), null, null );
((ArrayNode)parent.node ).add( child.node );
} else {
throw new IllegalStateException();
}
}
if( bufferingLevel == null && !startBuffering( child ) ) {
generator.writeStartArray();
}
}
private void processEndArray() throws IOException {
Level child;
Level parent;
child = stack.pop();
if( child.equals(bufferingLevel) ) {
filterBufferedNode( child );
mapper.writeTree( generator, child.node );
bufferingLevel = null;
bufferingConfig = null;
} else if( bufferingLevel == null ) {
generator.writeEndArray();
if( !stack.isEmpty() ) {
parent = stack.peek();
switch( parent.node.asToken() ) {
case START_ARRAY:
((ArrayNode)parent.node ).removeAll();
break;
case START_OBJECT:
((ObjectNode)parent.node ).removeAll();
break;
default:
throw new IllegalStateException();
}
}
}
}
private void processFieldName() throws IOException {
Level child = pushLevel( parser.getCurrentName(), null, null, null );
try {
child.field = filterFieldName( child.field );
} catch( Exception e ) {
LOG.failedToFilterFieldName( child.field, e );
// Write original name.
}
if( bufferingLevel == null ) {
generator.writeFieldName( child.field );
}
}
private void processValueString() throws IOException {
Level child;
Level parent;
String value = null;
if(stack.isEmpty()) {
generator.writeString( parser.getText() );
return;
}
parent = stack.peek();
if( parent.isArray() ) {
ArrayNode array = (ArrayNode)parent.node;
array.add( parser.getText() );
if( bufferingLevel == null ) {
value = filterStreamValue( parent );
array.set( array.size()-1, new TextNode( value ) );
} else {
array.removeAll();
}
} else {
child = stack.pop();
parent = stack.peek();
((ObjectNode)parent.node ).put( child.field, parser.getText() );
if( bufferingLevel == null ) {
child.node = parent.node; // Populate the JsonNode of the child for filtering.
value = filterStreamValue( child );
}
}
if( bufferingLevel == null ) {
if( parent.node.isArray() ) {
((ArrayNode)parent.node).removeAll();
} else {
((ObjectNode)parent.node).removeAll();
}
generator.writeString( value );
}
}
private void processValueNumber() throws IOException {
Level child;
Level parent;
if(stack.isEmpty()) {
processedUnbufferedValueNumber();
return;
}
parent = stack.peek();
if( parent.isArray() ) {
if( bufferingLevel != null ) {
ArrayNode array = (ArrayNode)parent.node;
processBufferedArrayValueNumber( array );
}
} else {
child = stack.pop();
if( bufferingLevel != null ) {
parent = stack.peek();
ObjectNode object = (ObjectNode)parent.node;
processBufferedFieldValueNumber( child, object );
}
}
if( bufferingLevel == null ) {
processedUnbufferedValueNumber();
}
}
private void processedUnbufferedValueNumber() throws IOException {
switch( parser.getNumberType() ) {
case INT:
generator.writeNumber( parser.getIntValue() );
break;
case LONG:
generator.writeNumber( parser.getLongValue() );
break;
case BIG_INTEGER:
generator.writeNumber( parser.getBigIntegerValue() );
break;
case FLOAT:
generator.writeNumber( parser.getFloatValue() );
break;
case DOUBLE:
generator.writeNumber( parser.getDoubleValue() );
break;
case BIG_DECIMAL:
generator.writeNumber( parser.getDecimalValue() );
break;
}
}
private void processBufferedFieldValueNumber( Level child, ObjectNode object ) throws IOException {
//object.put( child.field, parser.getDecimalValue() );
switch( parser.getNumberType() ) {
case INT:
object.put( child.field, parser.getIntValue() );
break;
case LONG:
object.put( child.field, parser.getLongValue() );
break;
case BIG_INTEGER:
object.put( child.field, parser.getDecimalValue() );
break;
case FLOAT:
object.put( child.field, parser.getFloatValue() );
break;
case DOUBLE:
object.put( child.field, parser.getDoubleValue() );
break;
case BIG_DECIMAL:
object.put( child.field, parser.getDecimalValue() );
break;
}
}
private void processBufferedArrayValueNumber( ArrayNode array ) throws IOException {
//array.add( parser.getDecimalValue() );
switch( parser.getNumberType() ) {
case INT:
array.add( parser.getIntValue() );
break;
case LONG:
array.add( parser.getLongValue() );
break;
case BIG_INTEGER:
array.add( parser.getDecimalValue() );
break;
case FLOAT:
array.add( parser.getFloatValue() );
break;
case DOUBLE:
array.add( parser.getDoubleValue() );
break;
case BIG_DECIMAL:
array.add( parser.getDecimalValue() );
break;
}
}
private void processValueBoolean() throws IOException {
Level child;
Level parent;
if(stack.isEmpty()) {
generator.writeBoolean(parser.getBooleanValue());
return;
}
parent = stack.peek();
if( parent.isArray() ) {
((ArrayNode)parent.node ).add( parser.getBooleanValue() );
//dump();
if( bufferingLevel == null ) {
((ArrayNode)parent.node ).removeAll();
}
} else {
child = stack.pop();
parent = stack.peek();
((ObjectNode)parent.node ).put( child.field, parser.getBooleanValue() );
//dump();
if( bufferingLevel == null ) {
((ObjectNode)parent.node ).remove( child.field );
}
}
if( bufferingLevel == null ) {
generator.writeBoolean( parser.getBooleanValue() );
}
}
private void processValueNull() throws IOException {
Level child;
if(stack.isEmpty()) {
generator.writeNull();
return;
}
Level parent = stack.peek();
if( parent.isArray() ) {
((ArrayNode)parent.node ).addNull();
//dump();
if( bufferingLevel == null ) {
((ArrayNode)parent.node ).removeAll();
}
} else {
child = stack.pop();
parent = stack.peek();
((ObjectNode)parent.node ).putNull( child.field );
//dump();
if( bufferingLevel == null ) {
((ObjectNode)parent.node ).remove( child.field );
}
}
if( bufferingLevel == null ) {
generator.writeNull();
}
}
protected boolean startBuffering( Level node ) {
boolean buffered = false;
UrlRewriteFilterGroupDescriptor scope = node.scopeConfig;
if( scope != null ) {
for( UrlRewriteFilterPathDescriptor selector : scope.getSelectors() ) {
JsonPath.Expression path = (JsonPath.Expression)selector.compiledPath( JPATH_COMPILER );
List<JsonPath.Match> matches = path.evaluate( node.scopeNode );
if( matches != null && !matches.isEmpty() ) {
if( selector instanceof UrlRewriteFilterBufferDescriptor ) {
bufferingLevel = node;
bufferingConfig = (UrlRewriteFilterBufferDescriptor)selector;
buffered = true;
}
break;
}
}
}
return buffered;
}
protected String filterStreamValue( Level node ) {
String value;
if( node.isArray() ) {
value = node.node.get( 0 ).asText();
} else {
value = node.node.get( node.field ).asText();
}
String rule = null;
UrlRewriteFilterGroupDescriptor scope = node.scopeConfig;
//TODO: Scan the top level apply rules for the first match.
if( scope != null ) {
for( UrlRewriteFilterPathDescriptor selector : scope.getSelectors() ) {
JsonPath.Expression path = (JsonPath.Expression)selector.compiledPath( JPATH_COMPILER );
List<JsonPath.Match> matches = path.evaluate( node.scopeNode );
if( matches != null && !matches.isEmpty() ) {
JsonPath.Match match = matches.get( 0 );
if( match.getNode().isTextual() && selector instanceof UrlRewriteFilterApplyDescriptor ) {
UrlRewriteFilterApplyDescriptor apply = (UrlRewriteFilterApplyDescriptor)selector;
rule = apply.rule();
break;
}
}
}
}
try {
value = filterValueString( node.field, value, rule );
if( node.isArray() ) {
((ArrayNode)node.node).set( 0, new TextNode( value ) );
} else {
((ObjectNode)node.node).put( node.field, value );
}
} catch( Exception e ) {
LOG.failedToFilterValue( value, rule, e );
}
return value;
}
private void filterBufferedNode( Level node ) {
for( UrlRewriteFilterPathDescriptor selector : bufferingConfig.getSelectors() ) {
JsonPath.Expression path = (JsonPath.Expression)selector.compiledPath( JPATH_COMPILER );
List<JsonPath.Match> matches = path.evaluate( node.node );
for( JsonPath.Match match : matches ) {
if( selector instanceof UrlRewriteFilterApplyDescriptor ) {
if( match.getNode().isTextual() ) {
filterBufferedValue( match, (UrlRewriteFilterApplyDescriptor)selector );
}
} else if( selector instanceof UrlRewriteFilterDetectDescriptor ) {
UrlRewriteFilterDetectDescriptor detectConfig = (UrlRewriteFilterDetectDescriptor)selector;
JsonPath.Expression detectPath = (JsonPath.Expression)detectConfig.compiledPath( JPATH_COMPILER );
List<JsonPath.Match> detectMatches = detectPath.evaluate( node.node );
for( JsonPath.Match detectMatch : detectMatches ) {
if( detectMatch.getNode().isTextual() ) {
String detectValue = detectMatch.getNode().asText();
Pattern detectPattern = detectConfig.compiledValue( REGEX_COMPILER );
if( detectPattern.matcher( detectValue ).matches() ) {
filterBufferedValues( node, detectConfig.getSelectors() );
}
}
}
}
}
}
}
private void filterBufferedValues( Level node, List<UrlRewriteFilterPathDescriptor> selectors ) {
for( UrlRewriteFilterPathDescriptor selector : selectors ) {
JsonPath.Expression path = (JsonPath.Expression)selector.compiledPath( JPATH_COMPILER );
List<JsonPath.Match> matches = path.evaluate( node.node );
for( JsonPath.Match match : matches ) {
if( match.getNode().isTextual() && selector instanceof UrlRewriteFilterApplyDescriptor ) {
filterBufferedValue( match, (UrlRewriteFilterApplyDescriptor)selector );
}
}
}
}
private void filterBufferedValue( JsonPath.Match match, UrlRewriteFilterApplyDescriptor apply ) {
String field = match.getField();
String value = match.getNode().asText();
try {
value = filterValueString( field, value, apply.rule() );
((ObjectNode)match.getParent().getNode()).put( field, value );
} catch( Exception e ) {
LOG.failedToFilterValue( value, apply.rule(), e );
}
}
protected String filterFieldName( String field ) {
return field;
}
protected String filterValueString( String name, String value, String rule ) {
return value;
}
@Override
public void close() throws IOException {
generator.close();
writer.close();
parser.close();
reader.close();
}
private static class Level {
String field;
JsonNode node;
JsonNode scopeNode;
UrlRewriteFilterGroupDescriptor scopeConfig;
Level( String field, JsonNode node, JsonNode scopeNode, UrlRewriteFilterGroupDescriptor scopeConfig ) {
this.field = field;
this.node = node;
this.scopeNode = scopeNode;
this.scopeConfig = scopeConfig;
}
public boolean isArray() {
return node != null && node.isArray();
}
}
private static class JsonPathCompiler implements UrlRewriteFilterPathDescriptor.Compiler<JsonPath.Expression> {
@Override
public JsonPath.Expression compile( String expression, JsonPath.Expression compiled ) {
return JsonPath.compile( expression );
}
}
private static class RegexCompiler implements UrlRewriteFilterPathDescriptor.Compiler<Pattern> {
@Override
public Pattern compile( String expression, Pattern compiled ) {
if( compiled != null ) {
return compiled;
} else {
return Pattern.compile( expression );
}
}
}
}