/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* 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
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.binding.impl.jaxb.json;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
/**
* The JSON FilterInputStream check the input stream if the root XML element is present
* in the JSON payload. If not, it will add the root XML element at the begining and the
* the corresponding end before return EOS of the underlying stream.
*
* @author asahni
*
*/
public class JSONFilterInputStream extends FilterInputStream {
private QName m_rootXMLName;
private Charset m_charset;
private static final int MAX_BUFFER_SIZE = 8192;
// byte array
private byte[] m_bites = new byte[INITIAL_BUFFER_SIZE*2];
// the index from which the bytes are returned from the byte array
private int m_readIndex = 0;
// number of bytes in buffer that are present in the byte and ready to be read
private int m_numBites = 0;
// Whether the root has been looked for or not
// Checking for the root is a one time activity, invoked at the very first
// of reads
private boolean m_lookedForRoot = false;
// Signifies whether the root XML element is already present in the input stream or not
private boolean m_rootElementAdded = false;
// If the root XML ending was added, whether it has been sent to the caller or not
private boolean m_sentRootElementSuffix = false;
private String m_rootElementName = null;
private boolean m_endCurlyBraceNeeded = false;
/**
* Constructor
*
* @param in - the underlying input stream carrying the JSON payload
* @param rootXMLName - the root XML element to look for
* @param charset - character encoding
*/
public JSONFilterInputStream(InputStream in, QName rootXMLName, Charset charset) {
super(in);
this.m_rootXMLName = rootXMLName;
this.m_charset = charset;
m_rootElementName = "\"" +
(
(m_rootXMLName.getPrefix() == null || m_rootXMLName.getPrefix().equals(""))
?
""
:
(m_rootXMLName.getPrefix() + ".")
) +
m_rootXMLName.getLocalPart() + "\"" + ":";
}
/**
* Ensures that there are atleast the specified number of 'len' bytes available
* in the byte array.
*
* @param len
*/
private void ensureCapacity(int len) {
if( m_numBites + len > m_bites.length ) {
byte[] newBites = new byte[(m_bites.length + len)*2];
System.arraycopy(m_bites, 0, newBites, 0, m_numBites);
m_bites = newBites;
}
}
/**
* This method first checks whether the root XML element has been looked for.
* If not, the method to look for the root XML element is called, else
* super.read() is call and the returned values is stored in the byte array and
* returned from it.
*/
public int read() throws IOException {
// Check if the root has been looked for
if( ! m_lookedForRoot ) {
lookForRootUsingRegex();
}
// Read from either the buffer or from stream
if( m_readIndex < m_numBites ) {
// the buffer has some unread chars
return m_bites[m_readIndex++];
} else {
// either the buffer is empty or end of stream has been reached
int retval = super.read();
if( retval < 0 ) {
// check if corresponding end of XML root element needs to be added
if( m_rootElementAdded && m_endCurlyBraceNeeded && ! m_sentRootElementSuffix ) {
m_sentRootElementSuffix = true;
return '}';
} else {
return retval;
}
} else {
return retval;
}
}
}
/**
* This method first looks for the root XML element. If the root XML element
* has been not been looked for, it calls the method to look for the root XML
* element, else super.read
*/
public int read(byte[] b, int off, int len) throws IOException {
if( len == 0 ) {
return 0;
}
// look for presence or absence of root XML element
if( ! m_lookedForRoot ) {
lookForRootUsingRegex();
}
// check if unread data is present in the byte array buffer
if( m_readIndex < m_numBites ) {
// unread data in the buffer - read from it
int xferCount = Math.min(len, (m_numBites-m_readIndex));
System.arraycopy(m_bites, m_readIndex, b, off, xferCount);
// System.out.println("ashish debug: " + new String(b, off, xferCount));
m_readIndex += xferCount;
return xferCount;
}
// buffer is empty. go ahead and read from stream
int numRead2 = super.read(b, off, len);
if( numRead2 < 0 ) {
// check if corresponding end of XML root element needs to be added
if( m_rootElementAdded && m_endCurlyBraceNeeded && ! m_sentRootElementSuffix ) {
m_sentRootElementSuffix = true;
b[off] = '}';
//System.out.println("ashish debug: " + new String(b, off, 1));
return 1;
} else {
// EOS
return numRead2;
}
} else {
//System.out.println("ashish debug: " + new String(b, off, numRead2));
// return the number of bytes read
return numRead2;
}
}
/**
* If the root XML element has already been looked for, 0 is returned,
* else inputstream is read and the number of bytes read from the input stream are returned
*
* This method needs to be called once only, for the first time when read() or read(byte[], int, int)
* is called. Its look for the first two curly braces ('{'). If the char sequence between the first two
* curly braces matches the root XML element name, nothing special is done, else the root XML element
* name is prefixed to the byte array.
*
* @return total number of byte added to the byte array during this call
* @throws IOException
*/
// TODO - For the logic here pushback inputstream might work better
// read till the second curly brace
// if( rootXMLelement NOT present ) {
// add rootXMLelement to byte array buffer
// }
// pushback all the bytes read from the input stream
private int lookForRoot() throws IOException {
if( m_lookedForRoot ) {
return 0;
}
m_lookedForRoot = true;
int totalBitesRead = 0;
int firstIndex = 0;
int secondIndex = 0;
int firstOpenParanIndex = -1;
int firstCloseParanIndex = -1;
boolean noBraces = false;
// look for the first '{'
while(true) {
ensureCapacity(1);
int biteRead = super.read();
if( biteRead < 0 ) {
return totalBitesRead;
}
m_bites[m_numBites++] = (byte)biteRead;
totalBitesRead++;
if( biteRead == '{' ) {
firstOpenParanIndex = totalBitesRead - 1;
}
if(biteRead == '"' ) {
break;
}
firstIndex++;
if( totalBitesRead > MAX_BUFFER_SIZE ) {
throw new IllegalStateException("Maximum buffer size exceeded");
}
}
// signifies whether to break from the while loop of keep looking for '{ or '}'
boolean loopForBraceOnly = false;
secondIndex = firstIndex + 1;
// look for the second '{' or ':'
while(true) {
ensureCapacity(1);
int biteRead = super.read();
if( biteRead < 0 ) {
return totalBitesRead;
}
m_bites[m_numBites++] = (byte)biteRead;
totalBitesRead++;
if(biteRead == '"' ) {
loopForBraceOnly = true;
}
if(biteRead == '{' || biteRead == ':' ) {
break;
}
// its a single level like {"this is my message"}
// the output should look
// something like {"XMLRootElementName":"this is my message"}
// and not {"XMLRootElementName":{"this is my message"}}
if( biteRead == '}' ) {
noBraces = true;
firstCloseParanIndex = totalBitesRead - 1;
break;
}
if( loopForBraceOnly ) {
continue;
}
secondIndex++;
if( totalBitesRead > MAX_BUFFER_SIZE ) {
throw new IllegalStateException("Maximum buffer size exceeded");
}
}
// Look if the root element if already present between the first and the second parantheses
//String s = new String(m_bites, firstParanIndex, (secondParanIndex-firstParanIndex), m_charset.name());
String s = new String(m_bites, firstIndex+1, (secondIndex-firstIndex-1), m_charset.name());
boolean addRootElement = false;
if(s.equals(m_rootXMLName.getLocalPart())) {
// Yes, root element is present in the payload
addRootElement = false;
} else {
// Check is the root element is prefix qualified
if(s.indexOf('.') != -1) {
// Yes, prefix qualified. Check local part only
String s1 = s.substring(s.indexOf('.')+1);
if(s1.equals(m_rootXMLName.getLocalPart())) {
// Local part matches root element
addRootElement = false;
} else {
// No root element present, must be added
addRootElement = true;
}
} else {
// no, not prefix qualified and therefore add root element
addRootElement = true;
}
}
if( addRootElement ) {
if( noBraces ) {
// get rid of the '{' '}' in the byte array read so far
// create a tmp byte array
byte[] tmpBites = new byte[m_bites.length];
// copy from 0 to firstParanIndex(exclusive)
System.arraycopy(m_bites, 0, tmpBites, 0, firstOpenParanIndex);
// copy from firstParanIndex+1, to secondParanIndex(exclusive)
System.arraycopy(m_bites, firstOpenParanIndex+1,
tmpBites, firstOpenParanIndex, (firstCloseParanIndex-firstOpenParanIndex-1));
// reduce the length by 2
m_numBites -= 2;
// reassign the new to old
m_bites = tmpBites;
}
// root XML element is not present - add it now to the buffer bytes
String rootPrefix = "{" + "\"" +
(
(m_rootXMLName.getPrefix() == null || m_rootXMLName.getPrefix().equals(""))
?
""
:
(m_rootXMLName.getPrefix() + ".")
) +
// (m_rootXMLName.getPrefix() == "" ?
// (m_rootXMLName.getLocalPart().equals("MyMessage") ? "ns2." : "") : m_rootXMLName.getPrefix()) +
m_rootXMLName.getLocalPart() + "\"" + ":";
ensureCapacity(rootPrefix.length());
byte[] tmpBites = this.m_bites.clone();
System.arraycopy(rootPrefix.getBytes(), 0, m_bites, 0, rootPrefix.length());
System.arraycopy(tmpBites, 0, m_bites, rootPrefix.length(), totalBitesRead);
m_numBites += rootPrefix.length();
m_rootElementAdded = true;
return totalBitesRead + rootPrefix.length();
} else {
m_rootElementAdded = false;
return totalBitesRead;
}
}
/**
* "{\"jsonns.xsi\":\"http://www.w3.org/2001/XMLSchema-instance\",\"jsonns.xs\":\"http://www.w3.org/2001/XMLSchema\",\"jsonns.ms\":\"http://www.ebayopensource.org/turmeric/common/v1/types\",\"jsonns.ns2\":\"http://www.ebayopensource.org/test\",\"ms.getName\":[{\"ms.in\":[{\"ns2.name\":[\"whatever\"]}]}]}";
* {(("jsonns.[a-z]+":"[^"]*")?(,"jsonns.[a-z]+":"[^"]*")*("rootElementName":)?[?{?.*}
* JSON-PAYLOAD:
*/
private static final Pattern JSON_NS_PATTERN = Pattern.compile(
"\\{\\s*(\\s*\"jsonns\\.[a-zA-Z0-9]+\"\\s*:\\s*\"[^\"]+\"\\s*,)*\\s*(\"[a-zA-Z0-9@.]+\"\\s*:\\s*)?(\\[?\\{?.*)", Pattern.MULTILINE | Pattern.DOTALL);
private static final int INITIAL_BUFFER_SIZE = 1024;
private void lookForRootUsingRegex() throws IOException {
if( m_lookedForRoot ) {
return; //return 0;
}
m_lookedForRoot = true;
int bitesRead = 0;
while((bitesRead = super.read(this.m_bites, m_numBites, INITIAL_BUFFER_SIZE-m_numBites)) >= 0) {
m_numBites += bitesRead;
if(m_numBites == INITIAL_BUFFER_SIZE) {
break;
}
}
String sRead = new String(m_bites, 0, m_numBites, m_charset.name());
//System.out.println("pattern: " + JSON_NS_PATTERN);
Matcher matcher = JSON_NS_PATTERN.matcher(sRead);
// System.out.println("matcher matches: " + matcher.matches());
// System.out.println("group count: " + matcher.groupCount());
if( matcher.matches() ) {
// for(int i=0; i<=matcher.groupCount(); i++) {
// System.out.println(" " + i + " : " + matcher.group(i));
// }
if( matcher.group(3) == null ) {
return; // ?
}
String currentRootElement = matcher.group(2);
if( currentRootElement == null ) {
// its of the form { "abc" } to be transformed into { "root-element-name": "abc" }
int startIndex = matcher.start(3);
// System.out.print("part1=\"" + matcher.group(1) + "\" part2=\"" + matcher.group(2) + "\" part3=\"" + matcher.group(3) + "\"");
byte[] tmpBites = m_bites.clone();
System.arraycopy(m_bites, 0, tmpBites, 0, startIndex);
int rootElementLen = m_rootElementName.length();
System.arraycopy(m_rootElementName.getBytes(), 0, tmpBites, startIndex, rootElementLen);
System.arraycopy(m_bites, startIndex, tmpBites, startIndex + rootElementLen, m_numBites-startIndex);
m_bites = tmpBites;
m_numBites += rootElementLen;
this.m_rootElementAdded = true;
this.m_endCurlyBraceNeeded = true;
} else {
currentRootElement = currentRootElement.substring(1, currentRootElement.length()-2);
boolean addRootElement = false;
if(currentRootElement.equals(m_rootXMLName.getLocalPart())) {
// Yes, root element is present in the payload
addRootElement = false;
} else {
// Check is the root element is prefix qualified
if(currentRootElement.indexOf('.') != -1) {
// Yes, prefix qualified. Check local part only
String s1 = currentRootElement.substring(currentRootElement.indexOf('.')+1);
if(s1.equals(m_rootXMLName.getLocalPart())) {
// Local part matches root element
addRootElement = false;
} else {
// No root element present, must be added
addRootElement = true;
}
} else {
// no, not prefix qualified and therefore add root element
addRootElement = true;
}
}
if( addRootElement ) {
String newRoot = m_rootElementName + "{";
// its of the form { "abc" } to be transformed into { "root-element-name": "abc" }
int startIndex = matcher.start(2);
byte[] tmpBites = m_bites.clone();
System.arraycopy(m_bites, 0, tmpBites, 0, startIndex);
System.arraycopy(newRoot.getBytes(), 0, tmpBites, startIndex, newRoot.length());
System.arraycopy(m_bites, startIndex, tmpBites, startIndex + newRoot.length(), m_numBites-startIndex);
m_bites = tmpBites;
m_numBites += newRoot.length();
this.m_rootElementAdded = true;
this.m_endCurlyBraceNeeded = true;
}
}
}
}
}