All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jasperreports.engine.data.JsonDataSource Maven / Gradle / Ivy

There is a newer version: 6.21.3
Show newest version
/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see .
 */
package net.sf.jasperreports.engine.data;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRRewindableDataSource;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.util.JsonUtil;
import net.sf.jasperreports.repo.RepositoryUtil;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;


/**
 * JSON data source implementation
 * 
 * @author Narcis Marcu ([email protected])
 */
public class JsonDataSource extends JRAbstractTextDataSource implements JRRewindableDataSource {

	// the JSON select expression that gives the nodes to iterate
	private String selectExpression;

	private Iterator jsonNodesIterator;

	// the current node
	private JsonNode currentJsonNode;

	private final String PROPERTY_SEPARATOR = ".";//FIXME static?

	private final String ARRAY_LEFT = "[";

	private final String ARRAY_RIGHT = "]";
	
	private final String ATTRIBUTE_LEFT = "(";
	
	private final String ATTRIBUTE_RIGHT = ")";
	
	// the JSON tree as it is obtained from the JSON source
	private JsonNode jsonTree;
	
	private ObjectMapper mapper;
	
	private InputStream jsonStream;
	
	private boolean toClose;
	
	public JsonDataSource(InputStream stream) throws JRException {
		this(stream, null);
	}
	
	public JsonDataSource(InputStream jsonStream, String selectExpression) throws JRException {
		try {
			this.jsonStream = jsonStream;
			this.mapper = new ObjectMapper();
			
			mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
			mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
			mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
			
			this.jsonTree = mapper.readTree(jsonStream);
			this.selectExpression = selectExpression;
			
			moveFirst();
		} catch (JsonProcessingException e) {
			throw new JRException(e);
		} catch (IOException e) {
			throw new JRException(e);
		}
	}


	public JsonDataSource(File file) throws FileNotFoundException, JRException {
		this(file, null);
	}
	

	public JsonDataSource(File file, String selectExpression) throws FileNotFoundException, JRException {
		this(new FileInputStream(file), selectExpression);
		
		toClose = true;
	}

	/**
	 * Creates a data source instance that reads JSON data from a given location
	 * @param jasperReportsContext the JasperReportsContext
	 * @param location a String representing JSON data source
	 * @param selectExpression a String representing the select expression
	 */
	public JsonDataSource(JasperReportsContext jasperReportsContext, String location, String selectExpression) throws JRException 
	{
		this(RepositoryUtil.getInstance(jasperReportsContext).getInputStreamFromLocation(location), selectExpression);
		
		toClose = true;
	}

	/**
	 * @see #JsonDataSource(JasperReportsContext, String, String)
	 */
	public JsonDataSource(String location, String selectExpression) throws JRException 
	{
		this(DefaultJasperReportsContext.getInstance(), location, selectExpression);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.jasperreports.engine.JRRewindableDataSource#moveFirst()
	 */
	public void moveFirst() throws JRException {
		if (jsonTree == null || jsonTree.isMissingNode()) {
			throw new JRException("No JSON data to operate on!");
		}

		currentJsonNode = null;
		JsonNode result = getJsonData(jsonTree, selectExpression);
		if (result != null && result.isObject()) {
			final List list = new ArrayList();
			list.add(result);
			jsonNodesIterator = new Iterator() {
				private int count = -1;
				public void remove() {
					list.remove(count);
				}
				
				public JsonNode next() {
					count ++;
					return list.get(count);
				}
				
				public boolean hasNext() {
					return count < list.size()-1;
				}
			};
		} else if (result != null && result.isArray()) {
			jsonNodesIterator = result.elements();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.jasperreports.engine.JRDataSource#next()
	 */
	public boolean next() {
		if(jsonNodesIterator == null || !jsonNodesIterator.hasNext()) {
			return false;
		}
		currentJsonNode = jsonNodesIterator.next();
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.jasperreports.engine.JRDataSource#getFieldValue(net.sf.jasperreports.engine.JRField)
	 */
	public Object getFieldValue(JRField jrField) throws JRException 
	{
		if(currentJsonNode == null) {
			return null;
		}
		String expression = jrField.getDescription();
		if (expression == null || expression.length() == 0)
		{
			expression = jrField.getName();
			if (expression == null || expression.length() == 0)
			{
				return null;
			}
		}
		Object value = null;
		
		Class valueClass = jrField.getValueClass();
		JsonNode selectedObject = getJsonData(currentJsonNode, expression);
		
		if(Object.class != valueClass) 
		{
			boolean hasValue = selectedObject != null 
					&& !selectedObject.isMissingNode() && !selectedObject.isNull();
			if (hasValue) 
			{
				try {
					if (valueClass.equals(String.class)) {
						value = selectedObject.asText();
						
					} else if (valueClass.equals(Boolean.class)) {
						value = selectedObject.booleanValue();
						
					} else if (Number.class.isAssignableFrom(valueClass)) {
						//FIXME if the json node is a number, avoid converting to string and parsing back the value
							value = convertStringValue(selectedObject.asText(), valueClass);
							
					}
					else if (Date.class.isAssignableFrom(valueClass)) {
							value = convertStringValue(selectedObject.asText(), valueClass);
							
					} else {
						throw new JRException("Field '" + jrField.getName() + "' is of class '" + valueClass.getName() + "' and cannot be converted");
					}
				} catch (Exception e) {
					throw new JRException("Unable to get value for field '" + jrField.getName() + "' of class '" + valueClass.getName() + "'", e);
				}
			}
		}
		else
		{
			value = selectedObject;
		}
		
		return value;
	}
	
	/**
	 * Extracts the JSON nodes based on the query expression
	 * 
	 * @param rootNode
	 * @param jsonExpression
	 * @throws JRException
	 */
	protected JsonNode getJsonData(JsonNode rootNode, String jsonExpression) throws JRException {
		if (jsonExpression == null || jsonExpression.length() == 0) {
			return rootNode;
		}
		JsonNode tempNode = rootNode;
		StringTokenizer tokenizer = new StringTokenizer(jsonExpression, PROPERTY_SEPARATOR);
		
		while(tokenizer.hasMoreTokens()) {
			String currentToken = tokenizer.nextToken();
			int currentTokenLength = currentToken.length();
			int indexOfLeftSquareBracket = currentToken.indexOf(ARRAY_LEFT);

			// got Left Square Bracket - LSB
			if (indexOfLeftSquareBracket != -1) {
				// a Right Square Bracket must be the last character in the current token
				if(currentToken.lastIndexOf(ARRAY_RIGHT) != (currentTokenLength-1)) {
					throw new JRException("Invalid expression: " + jsonExpression + "; current token " + currentToken + " not ended properly");
				}
				
				// LSB not first character
				if (indexOfLeftSquareBracket > 0) {
					// extract nodes at property
					String property = currentToken.substring(0, indexOfLeftSquareBracket);
					tempNode = goDownPathWithAttribute(tempNode, property);
					
					String arrayOperators = currentToken.substring(indexOfLeftSquareBracket);
					StringTokenizer arrayOpsTokenizer = new StringTokenizer(arrayOperators,ARRAY_RIGHT);
					while(arrayOpsTokenizer.hasMoreTokens()) {
						if (!tempNode.isMissingNode() && tempNode.isArray()) {
							String currentArrayOperator = arrayOpsTokenizer.nextToken();
							tempNode = tempNode.path(Integer.parseInt(currentArrayOperator.substring(1)));
						}
					}
				} else { // LSB first character
					String arrayOperators = currentToken.substring(indexOfLeftSquareBracket);
					StringTokenizer arrayOpsTokenizer = new StringTokenizer(arrayOperators,ARRAY_RIGHT);
					while(arrayOpsTokenizer.hasMoreTokens()) {
						if (!tempNode.isMissingNode() && tempNode.isArray()) {
							String currentArrayOperator = arrayOpsTokenizer.nextToken();
							tempNode = tempNode.path(Integer.parseInt(currentArrayOperator.substring(1)));
						}
					}
				}
			} else {
				tempNode = goDownPathWithAttribute(tempNode, currentToken);
			}
		}
		
		return tempNode;
	}
	
	
	/**
	 * Extracts the JSON nodes that match the attribute expression
	 * 
	 * @param rootNode
	 * @param pathWithAttributeExpression : e.g. Orders(CustomerId == HILAA)
	 * @throws JRException
	 */
	protected JsonNode goDownPathWithAttribute(JsonNode rootNode, String pathWithAttributeExpression) throws JRException {
		// check if path has attribute selector
		int indexOfLeftRoundBracket = pathWithAttributeExpression.indexOf(ATTRIBUTE_LEFT); 
		if (indexOfLeftRoundBracket != -1) {
			
			// a Right Round Bracket must be the last character in the current pathWithAttribute
			if(pathWithAttributeExpression.indexOf(ATTRIBUTE_RIGHT) != (pathWithAttributeExpression.length() - 1)) {
				throw new JRException("Invalid attribute selection expression: " + pathWithAttributeExpression);
			}
			
			if(rootNode != null && !rootNode.isMissingNode()) {
				
				String path = pathWithAttributeExpression.substring(0, indexOfLeftRoundBracket);
				
				// an expression in a form like: attribute==value
				String attributeExpression = pathWithAttributeExpression.substring(indexOfLeftRoundBracket + 1, pathWithAttributeExpression.length() - 1);
				
				JsonNode result = null;
				if (rootNode.isObject()) {
					// select only those nodes for which the attribute expression applies
					if (!rootNode.path(path).isMissingNode()) {
						if (rootNode.path(path).isObject()) {
							if (isValidExpression(rootNode.path(path), attributeExpression)) {
								result = rootNode.path(path);
							}
						} else if (rootNode.path(path).isArray()) {
							result = mapper.createArrayNode();
							for (JsonNode node: rootNode.path(path)) {
								if (isValidExpression(node, attributeExpression)) {
									((ArrayNode)result).add(node);
								} 
							}
						}
					}
				} else if (rootNode.isArray()) {
					result = mapper.createArrayNode();
					for (JsonNode node: rootNode) {
						JsonNode deeperNode = node.path(path);
						if (!deeperNode.isMissingNode()) {
							if (deeperNode.isArray()) {
								for(JsonNode arrayNode: deeperNode) {
									if (isValidExpression(arrayNode, attributeExpression)) {
										((ArrayNode)result).add(arrayNode);
									}
								}
							} else if (isValidExpression(deeperNode, attributeExpression)){
								((ArrayNode)result).add(deeperNode);
							}
						} 
					}
				}
				return result;
			} 
			
		} else { // path has no attribute selectors
			return goDownPath(rootNode, pathWithAttributeExpression);
		}
		return rootNode;
	}
	
	
	/**
	 * Extracts the JSON nodes under the simple path
	 * 
	 * @param rootNode
	 * @param simplePath - a simple field name, with no selection by attribute
	 */
	protected JsonNode goDownPath(JsonNode rootNode, String simplePath) {
		if(rootNode != null && !rootNode.isMissingNode()) {
			JsonNode result = null;
			if (rootNode.isObject()) {
				result = rootNode.path(simplePath);
			} else if (rootNode.isArray()) {
				result = mapper.createArrayNode();
				for (JsonNode node: rootNode) {
					JsonNode deeperNode = node.path(simplePath);
					if (!deeperNode.isMissingNode()) {
						if (deeperNode.isArray()) {
							for(JsonNode arrayNode: deeperNode) {
								((ArrayNode)result).add(arrayNode);
							}
						} else {
							((ArrayNode)result).add(deeperNode);
						}
					} 
				}
			}
			return result;
		} 
		return rootNode;
	}
	
	
	/**
	 * Validates an attribute expression on a JsonNode
	 * 
	 * @param operand
	 * @param attributeExpression
	 * @throws JRException
	 */
	protected boolean isValidExpression(JsonNode operand, String attributeExpression) throws JRException {
		return JsonUtil.evaluateJsonExpression(operand, attributeExpression);
	}


	/**
	 * Creates a sub data source using the current node as the base for its input stream.
	 * 
	 * @return the JSON sub data source
	 * @throws JRException
	 */
	public JsonDataSource subDataSource() throws JRException {
		return subDataSource(null);
	}


	/**
	 * Creates a sub data source using the current node as the base for its input stream.
	 * An additional expression specifies the select criteria that will be applied to the
	 * JSON tree node. 
	 * 
	 * @param selectExpression
	 * @return the JSON sub data source
	 * @throws JRException
	 */
	public JsonDataSource subDataSource(String selectExpression) throws JRException {
		if(currentJsonNode == null)
		{
			throw new JRException("No node available. Iterate or rewind the data source.");
		}

		try {
			byte[] jsonNodeBytes = currentJsonNode.toString().getBytes("UTF-8");
			JsonDataSource subDataSource = new JsonDataSource(new ByteArrayInputStream(jsonNodeBytes), selectExpression);
			subDataSource.setTextAttributes(this);
			return subDataSource;
		} catch(UnsupportedEncodingException e) {
			throw new JRRuntimeException(e);
		}
	}


	public void close() {
		if (toClose) {
			try	{
				jsonStream.close();
			} catch(Exception e) {
				//nothing to do
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy