PatternExtractor.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.pdd.core.dynamic;

import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.OpenSPCoop2MessageFactory;
import org.openspcoop2.message.OpenSPCoop2RestJsonMessage;
import org.openspcoop2.message.constants.MessageRole;
import org.openspcoop2.message.constants.MessageType;
import org.openspcoop2.message.xml.MessageXMLUtils;
import org.openspcoop2.protocol.sdk.Context;
import org.openspcoop2.utils.json.JsonPathNotFoundException;
import org.openspcoop2.utils.json.JsonPathNotValidException;
import org.openspcoop2.utils.transport.http.HttpConstants;
import org.openspcoop2.utils.xml.AbstractXPathExpressionEngine;
import org.openspcoop2.utils.xml.DynamicNamespaceContext;
import org.openspcoop2.utils.xml.XPathNotFoundException;
import org.openspcoop2.utils.xml.XPathNotValidException;
import org.openspcoop2.utils.xml.XPathReturnType;
import org.openspcoop2.utils.xml2json.JsonXmlPathExpressionEngine;
import org.slf4j.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * GestorePathEstrazione
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class PatternExtractor {

	public static PatternExtractor getJsonPatternExtractor(String json, Logger log, boolean bufferMessage_readOnly, Context context) throws DynamicException {
		try {
			OpenSPCoop2MessageFactory messageFactory = OpenSPCoop2MessageFactory.getDefaultMessageFactory();
			OpenSPCoop2Message jsonMessageVerifica = messageFactory.createMessage(MessageType.JSON, MessageRole.NONE, 
					HttpConstants.CONTENT_TYPE_JSON, json.getBytes()).getMessage();
			MessageContent messageContent = new MessageContent(jsonMessageVerifica.castAsRestJson(), bufferMessage_readOnly, context);
			return new PatternExtractor(messageFactory, messageContent, log);
		}catch(Exception e) {
			throw new DynamicException(e.getMessage(),e);
		}
	}
	public static PatternExtractor getXmlPatternExtractor(Element element, Logger log, boolean bufferMessage_readOnly, Context context) throws DynamicException {
		try {
			OpenSPCoop2MessageFactory messageFactory = OpenSPCoop2MessageFactory.getDefaultMessageFactory();
			OpenSPCoop2Message jsonMessageVerifica = messageFactory.createMessage(MessageType.XML, MessageRole.NONE, 
					HttpConstants.CONTENT_TYPE_XML, MessageXMLUtils.getInstance(messageFactory).toByteArray(element, true)).getMessage();
			MessageContent messageContent = new MessageContent(jsonMessageVerifica.castAsRestXml(), bufferMessage_readOnly, context);
			return new PatternExtractor(messageFactory, messageContent, log);
		}catch(Exception e) {
			throw new DynamicException(e.getMessage(),e);
		}
	}
	
	private Logger log;
	
	private OpenSPCoop2MessageFactory messageFactory;
	private Element element = null;
	private Boolean refresh = null;
	private DynamicNamespaceContext dnc;
	
	private String elementJson = null;
	
	private MessageContent messageContent = null;
	private boolean messageContent_initialized = false;
	
	public PatternExtractor(OpenSPCoop2MessageFactory messageFactory, MessageContent messageContent, Logger log) {
		this.messageFactory = messageFactory;
		this.messageContent = messageContent;
		this.log = log;
	}
	
	// I seguenti costruttori li lascio per backward compatibility se usati dentro delle trasformazioni
	@Deprecated
	public PatternExtractor(OpenSPCoop2MessageFactory messageFactory, Element element, Logger log) {
		try {
			PatternExtractor pe = getXmlPatternExtractor(element, log, false, null);
			this.messageFactory = pe.messageFactory;
			this.messageContent = pe.messageContent;
			this.log = pe.log;
		}catch(Exception e) {
			throw new RuntimeException(e.getMessage(),e);
		}
	}
	@Deprecated
	public PatternExtractor(String elementJson, Logger log) {
		try {
			PatternExtractor pe = getJsonPatternExtractor(elementJson, log, false, null);
			this.messageFactory = pe.messageFactory;
			this.messageContent = pe.messageContent;
			this.log = pe.log;
		}catch(Exception e) {
			throw new RuntimeException(e.getMessage(),e);
		}
	}
	
	
	private void init() throws DynamicException {
		if(!this.messageContent_initialized) {
			_init();
		}
	}
	private org.openspcoop2.utils.Semaphore semaphore = new org.openspcoop2.utils.Semaphore("PatternExtractor");
	private void _init() throws DynamicException {
		this.semaphore.acquireThrowRuntime("init");
		try {
			if(!this.messageContent_initialized) {
				if(this.messageContent!=null) {
					try {
						//this.messageFactory = this.messageContent.getMessageFactory();
						if(this.messageContent.isJson()) {
							this.elementJson = this.messageContent.getElementJson();
						}
						else if(this.messageContent.isXml()) {
							this.element = this.messageContent.getElement();
							this.dnc = new DynamicNamespaceContext();
							this.dnc.findPrefixNamespace(this.element);
						}
						else if(this.messageContent.isRestMultipart()) {
							this.elementJson = this.messageContent.getElementJson();
							if(this.elementJson==null) {
								this.element = this.messageContent.getElement();
								if(this.element!=null) {
									this.dnc = new DynamicNamespaceContext();
									this.dnc.findPrefixNamespace(this.element);
								}
								else {
									throw new Exception("Invalid multipart content");
								}
							}
						}
					}catch(Exception e) {
						throw new DynamicException(e.getMessage(),e);
					}
				}
				this.messageContent_initialized = true;
			}
		}finally {
			this.semaphore.release("init");
		}
	}
	
	
	public void refreshContent() {
		if(this.element!=null && this.refresh==null) {
			this._refreshContent();
		}
	}
	private void _refreshContent() {
		this.semaphore.acquireThrowRuntime("refreshContent");
		// effettuo il refresh, altrimenti le regole xpath applicate sulla richiesta, nel flusso di risposta (es. header http della risposta) non funzionano.
		try {
			this.refresh = true;
			if(this.element!=null) {
				MessageXMLUtils xmlUtils = MessageXMLUtils.getInstance(this.messageFactory);
				this.element = xmlUtils.newElement(xmlUtils.toByteArray(this.element));
			}
		}catch(Exception e){
			this.log.error("Refresh fallito: "+e.getMessage(),e);
		}finally {
			this.semaphore.release("refreshContent");
		}
	}
	
	public boolean match(String pattern) throws DynamicException {
		init();
		
		String v = read(pattern);
		return v!=null && !"".equals(v);
	}
	
	public String read(String pattern) throws DynamicException {
		init();
		
		String operazione = "Read (pattern: "+pattern+")";
		
		String valore = null;
		try {
			if(this.element!=null) {
				AbstractXPathExpressionEngine xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(this.messageFactory);
				valore = AbstractXPathExpressionEngine.extractAndConvertResultAsString(this.element, this.dnc, xPathEngine, pattern, this.log);
			}
			else {
				valore = JsonXmlPathExpressionEngine.extractAndConvertResultAsString(this.elementJson, pattern, this.log);
			}
		}
		catch(XPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(XPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(JsonPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(JsonPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(org.openspcoop2.utils.UtilsMultiException e) {
			int index = 0;
			boolean notFound = true;
			boolean notValid = true;
			for (Throwable t : e.getExceptions()) {
				if(t instanceof XPathNotFoundException || t instanceof JsonPathNotFoundException) {
					this.log.debug("["+index+"] "+operazione+" failed: "+t.getMessage(),t);
				}
				else {
					notFound = false;
				}
				
				if(!(t instanceof XPathNotValidException) && !(t instanceof JsonPathNotValidException)) {
					notValid = false;
				}
				
				index++;
			}
			if(!notFound) {
				if(notValid) {
					throw new DynamicException(e.getMessage(),e);
				}
				else {
					throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
				}
			}
		}
		catch(Exception e){
			throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
		}
		return valore;
	}
	
	public List<String> readList(String pattern) throws DynamicException {
		init();
		
		String operazione = "ReadList (pattern: "+pattern+")";
		
		List<String> valore = null;
		try {
			if(this.element!=null) {
				AbstractXPathExpressionEngine xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(this.messageFactory);
				xPathEngine.getMatchPattern(this.element, this.dnc, pattern, XPathReturnType.BOOLEAN);
				valore = AbstractXPathExpressionEngine.extractAndConvertResultAsList(this.element, this.dnc, xPathEngine, pattern, this.log);
			}
			else {
				valore = JsonXmlPathExpressionEngine.extractAndConvertResultAsList(this.elementJson, pattern, this.log);
			}
		}
		catch(XPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(XPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(JsonPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(JsonPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(org.openspcoop2.utils.UtilsMultiException e) {
			int index = 0;
			boolean notFound = true;
			boolean notValid = true;
			for (Throwable t : e.getExceptions()) {
				if(t instanceof XPathNotFoundException || t instanceof JsonPathNotFoundException) {
					this.log.debug("["+index+"] "+operazione+" failed: "+t.getMessage(),t);
				}
				else {
					notFound = false;
				}
				
				if(!(t instanceof XPathNotValidException) && !(t instanceof JsonPathNotValidException)) {
					notValid = false;
				}
				
				index++;
			}
			if(!notFound) {
				if(notValid) {
					throw new DynamicException(e.getMessage(),e);
				}
				else {
					throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
				}
			}
		}
		catch(Exception e){
			throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
		}
		return valore;
	}
	
	// Lascio per utilizzi in vecchie trasformazioni
	@Deprecated
	public void remove(String pattern) throws DynamicException {
		this._remove(pattern, null);
	}
	public void removeByXPath(String pattern) throws DynamicException {
		if(pattern==null || StringUtils.isEmpty(pattern)) {
			throw new DynamicException("XPath pattern undefined");
		}
		this._remove(pattern, null);
	}
	public void removeJsonElement(String name) throws DynamicException {
		if(name==null || StringUtils.isEmpty(name)) {
			throw new DynamicException("JsonElement name undefined");
		}
		this._remove(null, name);
	}
	public void removeByJsonPath(String pattern, String name) throws DynamicException {
		if(pattern==null || StringUtils.isEmpty(pattern)) {
			throw new DynamicException("JsonPath pattern undefined");
		}
		if(name==null || StringUtils.isEmpty(name)) {
			throw new DynamicException("JsonElement name undefined");
		}
		this._remove(pattern, name);
	}
	private void _remove(String pattern, String name) throws DynamicException {
		init();
		
		String opName = "";
		if(name!=null) {
			opName="name:"+name+"";
		}
		String opPattern = "";
		if(pattern!=null) {
			opPattern="pattern:"+pattern+"";
			if(name!=null) {
				opPattern+=" ";
			}
		}
		String operazione = "Remove ("+opPattern+""+opName+")";
		
		try {
			if(this.messageContent!=null) {
				this.messageContent.setUpdatable();
			}
			
			if(this.element!=null) {
				AbstractXPathExpressionEngine xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(this.messageFactory);
				Node n = (Node) xPathEngine.getMatchPattern(this.element, this.dnc, pattern, XPathReturnType.NODE);
				if(n.getParentNode()!=null) {
					n.getParentNode().removeChild(n);	
				}
				else {
					this.element.removeChild(n);
				}
			}
			else {
				if(this.messageContent!=null && this.messageContent.isJson() && this.messageContent.getJsonMessage()!=null) {
					OpenSPCoop2RestJsonMessage json = this.messageContent.getJsonMessage();
					if(pattern!=null) {
						json.removeElement(pattern, name);
					}
					else {
						json.removeElement(name);
					}
				}
				else {
					// retrocompatibilita'
					String s = JsonXmlPathExpressionEngine.extractAndConvertResultAsString(this.elementJson, pattern, this.log);
					this.elementJson = this.elementJson.replace(s, "");
				}
			}
		}
		catch(XPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(XPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(JsonPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(JsonPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(org.openspcoop2.utils.UtilsMultiException e) {
			int index = 0;
			boolean notFound = true;
			boolean notValid = true;
			for (Throwable t : e.getExceptions()) {
				if(t instanceof XPathNotFoundException || t instanceof JsonPathNotFoundException) {
					this.log.debug("["+index+"] "+operazione+" failed: "+t.getMessage(),t);
				}
				else {
					notFound = false;
				}
				
				if(!(t instanceof XPathNotValidException) && !(t instanceof JsonPathNotValidException)) {
					notValid = false;
				}
				
				index++;
			}
			if(!notFound) {
				if(notValid) {
					throw new DynamicException(e.getMessage(),e);
				}
				else {
					throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
				}
			}
		}
		catch(Exception e){
			throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
		}
	}
	
	// Lascio per utilizzi in vecchie trasformazioni
	@Deprecated
	public void removeList(String pattern) throws DynamicException {
		this._removeList(pattern);
	}
	public void removeListByXPath(String pattern) throws DynamicException {
		if(pattern==null || StringUtils.isEmpty(pattern)) {
			throw new DynamicException("XPath pattern undefined");
		}
		this._remove(pattern, null);
	}
	private void _removeList(String pattern) throws DynamicException {
		init();
		
		String operazione = "RemoveList (pattern: "+pattern+")";
		
		try {
			this.messageContent.setUpdatable();
			
			if(this.element!=null) {
				AbstractXPathExpressionEngine xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(this.messageFactory);
				NodeList nList = (NodeList) xPathEngine.getMatchPattern(this.element, this.dnc, pattern, XPathReturnType.NODESET);
				if(nList!=null && nList.getLength()>0) {
					for (int i = 0; i < nList.getLength(); i++) {
						Node n = nList.item(i);
						if(n.getParentNode()!=null) {
							n.getParentNode().removeChild(n);	
						}
						else {
							this.element.removeChild(n);
						}			
					}
				}
			}
			else {
				// Soluzione deprecata: usare removeByJsonPath
				List<String> valori = JsonXmlPathExpressionEngine.extractAndConvertResultAsList(this.elementJson, pattern, this.log);
				if(valori!=null && !valori.isEmpty()) {
					for (String valore : valori) {
						this.elementJson = this.elementJson.replace(valore, "");			
					}
				}
			}
		}
		catch(XPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(XPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(JsonPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(JsonPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(org.openspcoop2.utils.UtilsMultiException e) {
			int index = 0;
			boolean notFound = true;
			boolean notValid = true;
			for (Throwable t : e.getExceptions()) {
				if(t instanceof XPathNotFoundException || t instanceof JsonPathNotFoundException) {
					this.log.debug("["+index+"] "+operazione+" failed: "+t.getMessage(),t);
				}
				else {
					notFound = false;
				}
				
				if(!(t instanceof XPathNotValidException) && !(t instanceof JsonPathNotValidException)) {
					notValid = false;
				}
				
				index++;
			}
			if(!notFound) {
				if(notValid) {
					throw new DynamicException(e.getMessage(),e);
				}
				else {
					throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
				}
			}
		}
		catch(Exception e){
			throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
		}
	}
	
	
	
	public void replaceValueByXPath(String pattern, String newValue) throws DynamicException {
		if(pattern==null || StringUtils.isEmpty(pattern)) {
			throw new DynamicException("XPath pattern undefined");
		}
		if(newValue==null) {
			throw new DynamicException("New value undefined");
		}
		this._replaceValue(pattern, null, newValue);
	}
	public void replaceJsonElementValue(String name, Object newValue) throws DynamicException {
		if(name==null || StringUtils.isEmpty(name)) {
			throw new DynamicException("JsonElement name undefined");
		}
		if(newValue==null) {
			throw new DynamicException("New value undefined");
		}
		this._replaceValue(null, name, newValue);
	}
	public void replaceValueByJsonPath(String pattern, String name, Object newValue) throws DynamicException {
		if(pattern==null || StringUtils.isEmpty(pattern)) {
			throw new DynamicException("JsonPath pattern undefined");
		}
		if(name==null || StringUtils.isEmpty(name)) {
			throw new DynamicException("JsonElement name undefined");
		}
		if(newValue==null) {
			throw new DynamicException("New value undefined");
		}
		this._replaceValue(pattern, name, newValue);
	}
	private void _replaceValue(String pattern, String name, Object value) throws DynamicException {
		init();
		
		String opName = "";
		if(name!=null) {
			opName=" name:"+name+"";
		}
		String operazione = "Replace value (pattern: "+pattern+""+opName+")";
		
		try {
			if(this.messageContent!=null) {
				this.messageContent.setUpdatable();
			}
			
			if(this.element!=null) {
				AbstractXPathExpressionEngine xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(this.messageFactory);
				Node n = (Node) xPathEngine.getMatchPattern(this.element, this.dnc, pattern, XPathReturnType.NODE);
				n.setTextContent(value.toString());
			}
			else {
				if(this.messageContent!=null && this.messageContent.isJson() && this.messageContent.getJsonMessage()!=null) {
					OpenSPCoop2RestJsonMessage json = this.messageContent.getJsonMessage();
					if(pattern!=null) {
						json.replaceValue(pattern, name, value);
					}
					else {
						json.replaceValue(name, value);
					}
				}
			}
		}
		catch(XPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(XPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(Exception e){
			throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
		}
	}
	
	
	public void replaceValuesByXPath(String pattern, String newValue) throws DynamicException {
		if(pattern==null || StringUtils.isEmpty(pattern)) {
			throw new DynamicException("XPath pattern undefined");
		}
		if(newValue==null) {
			throw new DynamicException("New value undefined");
		}
		this._replaceValues(pattern, null, newValue);
	}
	private void _replaceValues(String pattern, String name, String value) throws DynamicException {
		init();
		
		String opName = "";
		if(name!=null) {
			opName=" name:"+name+"";
		}
		String operazione = "Replace values (pattern: "+pattern+""+opName+")";
		
		try {
			this.messageContent.setUpdatable();
			
			if(this.element!=null) {
				AbstractXPathExpressionEngine xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(this.messageFactory);
				NodeList nList = (NodeList) xPathEngine.getMatchPattern(this.element, this.dnc, pattern, XPathReturnType.NODESET);
				if(nList!=null && nList.getLength()>0) {
					for (int i = 0; i < nList.getLength(); i++) {
						Node n = nList.item(i);
						n.setTextContent(value);		
					}
				}
			}
			else {
				throw new Exception("Unsupported in json structure");
			}
		}
		catch(XPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(XPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(JsonPathNotFoundException e){
			this.log.debug(operazione+" failed, results not found: "+e.getMessage(),e);
		}
		catch(JsonPathNotValidException e){
			throw new DynamicException(e.getMessage(),e);
		}
		catch(org.openspcoop2.utils.UtilsMultiException e) {
			int index = 0;
			boolean notFound = true;
			boolean notValid = true;
			for (Throwable t : e.getExceptions()) {
				if(t instanceof XPathNotFoundException || t instanceof JsonPathNotFoundException) {
					this.log.debug("["+index+"] "+operazione+" failed: "+t.getMessage(),t);
				}
				else {
					notFound = false;
				}
				
				if(!(t instanceof XPathNotValidException) && !(t instanceof JsonPathNotValidException)) {
					notValid = false;
				}
				
				index++;
			}
			if(!notFound) {
				if(notValid) {
					throw new DynamicException(e.getMessage(),e);
				}
				else {
					throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
				}
			}
		}
		catch(Exception e){
			throw new DynamicException(operazione+" failed: "+e.getMessage(),e);
		}
	}
}