OpenSPCoop2MessageSoapStreamReader.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.message.soap.reader;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPHeader;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.OpenSPCoop2MessageFactory;
import org.openspcoop2.message.constants.Costanti;
import org.openspcoop2.message.constants.MessageType;
import org.openspcoop2.message.exception.MessageException;
import org.openspcoop2.message.soap.OpenSPCoop2Message_saaj_11_impl;
import org.openspcoop2.message.soap.OpenSPCoop2Message_saaj_12_impl;
import org.openspcoop2.message.soap.SoapUtils;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.resources.Charset;
import org.openspcoop2.utils.transport.http.ContentTypeUtilities;
import org.openspcoop2.utils.transport.http.HttpConstants;
import org.openspcoop2.utils.xml.TransformerConfig;
import org.openspcoop2.utils.xml.XMLUtils;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

/**
 * OpenSPCoop2MessageSoapParser
 *
 * @author Andrea Poli (poli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class OpenSPCoop2MessageSoapStreamReader {

	public static boolean SOAP_HEADER_OPTIMIZATION_ENABLED = true;
	
	@SuppressWarnings("unused")
	private OpenSPCoop2MessageFactory msgFactory;
	private InputStream is;
	private String contentType;
	private int bufferThresholdKb;
	
	public OpenSPCoop2MessageSoapStreamReader(OpenSPCoop2MessageFactory msgFactory, String contentType, InputStream is, int bufferThresholdKb) {
		this.msgFactory = msgFactory;
		this.contentType = contentType;
		this.is = is;
		this.bufferThresholdKb = bufferThresholdKb;
	}

	private String envelope;
	private String prefixEnvelope = null;
	private String namespace;
	
	private String header;
	private String prefixHeader = null;
	private OpenSPCoop2Message _headerMsgCompleto;
	private boolean soapHeaderModified = false;
	
	private boolean soapHeaderOptimizable = false;
	private long startHeaderOffset;
	private long endHeaderOffset;
	private long startBodyOffset;
	private String isCharset;
	
	private String body;
	private boolean bodyEmpty;
	//private Element rootElement;
	private String rootElementNamespace;
	private String rootElementLocalName;
	private String rootElementPrefix;
	private String fault;
	
	private int bufferSize;
	
	private boolean parsingComplete = false;
	
	private MessageException tBuffered = null;
	
	public void checkException() throws MessageException {
		if(this.tBuffered!=null) {
			throw this.tBuffered;
		}
	}
	private InputStream bufferedInputStream = null;
	public InputStream getBufferedInputStream() {
		return this.bufferedInputStream;
	}
	public void releaseBufferedInputStream() {
		this.bufferedInputStream = null;
	}
	public void read() throws MessageException {

		this.bufferedInputStream = this.is;
				
		if(this.tBuffered!=null) {
			throw this.tBuffered;
		}
		
		if(this.contentType==null || StringUtils.isEmpty(this.contentType) || this.is == null) {
			return;
		}
		boolean multipart = false;
		try {
			multipart = ContentTypeUtilities.isMultipartType(this.contentType);
		}catch(Throwable t) {}
		String charset = null;
		try {
			if(!multipart) {
				charset = ContentTypeUtilities.readCharsetFromContentType(this.contentType);
				this.isCharset = charset; 
				if(Charset.UTF_8.getValue().equalsIgnoreCase(charset)) {
					charset = null;
				}
			}
			else {
				String internalCT = ContentTypeUtilities.getInternalMultipartContentType(this.contentType);
				this.isCharset = ContentTypeUtilities.readCharsetFromContentType(internalCT);
			}
		}catch(Throwable t) {}
		
		try {
			ByteArrayOutputStream bufferReaded = new ByteArrayOutputStream();
			int lettiBuffer = 0;
			
			StringBuilder sbActual = null;
			long sbActualOffsetStart = -1;
			byte[] bufferRaw = new byte[1024];
			long bLetti = 0;
			int letturaPrecedente = 0;
			int kbLetti = 0;
			
			boolean envelopeFound = false; 
			
			StringBuilder headerBuilder = null;
			String headerClosure = null;
			boolean headerCompletato = false;
			
			boolean bodyFound = false;
			String prefixBody = null;
			
			StringBuilder elementAfterBodyBuilder = null;
			
			boolean cdataFound = false;
			boolean commentFound = false;
			
			boolean analisiTerminata = false;
			
//			String check = org.openspcoop2.utils.Utilities.getAsString(this.is, "UTF8");
//			System.out.println("STREAM: "+check);
//			this.is = new java.io.ByteArrayInputStream(check.getBytes());
			
			while ( (lettiBuffer=this.is.read(bufferRaw)) !=-1) {
				
				// bufferizzo (1024 byte = 1kb alla volta)
				bufferReaded.write(bufferRaw, 0, lettiBuffer);
				kbLetti++;
				if(letturaPrecedente>0) {
					bLetti = bLetti + letturaPrecedente;
				}
				letturaPrecedente = lettiBuffer;
			
				// In caso di charset differente, uso una stringa
				String bufferString = null;
				if(charset!=null) {
					ByteArrayOutputStream bufferTmp = new ByteArrayOutputStream();
					bufferTmp.write(bufferRaw, 0, lettiBuffer);
					bufferTmp.flush();
					bufferTmp.close();
					bufferString = new String(bufferTmp.toByteArray(), charset);
					lettiBuffer = bufferString.length();
				}
				
				// analizzo
				for (int i = 0; i < lettiBuffer; i++) {
					char c = bufferString!=null ? bufferString.charAt(i) : (char) bufferRaw[i];
					
					if(headerCompletato && this.endHeaderOffset<=0) {
						this.endHeaderOffset = sbActualOffsetStart;
					}
					
					if( (c == '<' || startsWithLessThan(sbActual)) 
							&& 
							!cdataFound &&
							!commentFound) {
												
						if(sbActual!=null) {
							if(sbActual.length()>0) {
								
								// devo verificare se ho incontrato un cdata o sto continuando a leggere un header
								String sPreClosure = sbActual.toString();
								sbActual.setLength(0);
								sbActualOffsetStart = i+bLetti;
								
								if(sPreClosure.startsWith("<![CDATA[")) {
									//System.out.println("FOND CDATA! in sPreClosure");
									cdataFound=true;
								}
								else if(sPreClosure.startsWith("<!--")) {
									//System.out.println("FOND <!-- COMMENTO in sPreClosure: ["+sPreClosure+"]");
									if(!sPreClosure.endsWith("-->")){
										commentFound=true;
									}
								}
								
								if(headerClosure!=null) {
									//System.out.println("AGGIUNGO AD HEADER TESTO ["+sPreClosure+"]");
									headerBuilder.append(sPreClosure);
								}
								else if(bodyFound) {
									if(!sPreClosure.startsWith("<![CDATA[") && !sPreClosure.startsWith("<!--")) {
										//System.out.println("AGGIUNGO A BODY ["+sPreClosure+"]");
										if(elementAfterBodyBuilder!=null) {
											elementAfterBodyBuilder.append(sPreClosure);
										}
										else {
											elementAfterBodyBuilder = new StringBuilder(sPreClosure);
										}
									}
								}
								
							}
						}
						else {
							sbActual = new StringBuilder();
							sbActualOffsetStart = i+bLetti;
						}
					}
					if(sbActual!=null) {
						sbActual.append(c);
					}
					if(c == '>' || endsWithGreaterThan(sbActual)) {
						
						if(sbActual==null) {
							//NO: gli attachments potrebbero rientrare in questo caso
							if(multipart) {
								continue;
							}
							else {
								throw new Exception("Invalid content; found premature '>' character ("+bufferReaded.toString()+")");
							}
						}
						
						String s = sbActual.toString();
						
						//System.out.println("S ["+s+"]");
						
						if(cdataFound) {
							if(headerClosure!=null) {
								//System.out.println("AGGIUNGO AD HEADER CDATA ["+sbActual.toString()+"]");
								headerBuilder.append(sbActual.toString());
							}
							sbActual.setLength(0);
							sbActualOffsetStart = i+bLetti;
							//System.out.println("continuo perche CDATA... ");
							if(s.endsWith("]]>")) {
								cdataFound=false;
								//System.out.println("FOUND FINE CDATA");
							}
							continue;
						}
						if(s.startsWith("<![CDATA[")) {
							//System.out.println("FOUND CDATA!");
							cdataFound=true;
							continue;
						}
						
						if(commentFound) {
							if(headerClosure!=null) {
								//System.out.println("AGGIUNGO AD HEADER COMMENTO ["+sbActual.toString()+"]");
								headerBuilder.append(sbActual.toString());
							}
							sbActual.setLength(0);
							sbActualOffsetStart = i+bLetti;
							//System.out.println("continuo perche COMMENTO... ");
							if(s.endsWith("-->")) {
								commentFound=false;
								//System.out.println("FOUND FINE COMMENTO");
							}
							continue;
						}
						if(s.startsWith("<!--")) {
							//System.out.println("FOUND <!-- COMMENTO: ["+s+"]");
							if(s.endsWith("-->")){
								if(headerClosure!=null) {
									//System.out.println("AGGIUNGO AD HEADER COMMENTO ["+sbActual.toString()+"]");
									headerBuilder.append(sbActual.toString());
								}
								sbActual.setLength(0);
								sbActualOffsetStart = i+bLetti;
							}
							else {
								commentFound=true;
							}
							continue;
						}
						
						if(s.startsWith("<?")) {
							continue;
						}
						
						if(!envelopeFound && isOpenedElement(s,"<Envelope")) {
							this.envelope = s;
							//System.out.println("INIZIALIZZO ENVELOPE ["+s+"]");
							if(!analizyEnvelopeNamespace(this.envelope)) {
								//System.out.println("TERMINO");
								analisiTerminata=true;
								break;
							}
							envelopeFound = true;
							this.prefixEnvelope = "";
						}
						else if(!envelopeFound && isOpenedElement(s,":Envelope")) {
							String sPrefix = s.substring(0, s.indexOf(":Envelope"));
							if(sPrefix.startsWith("<") && !sPrefix.contains(" ") && sPrefix.length()>1) {
								this.envelope = s;
								//System.out.println("INIZIALIZZO ENVELOPE ["+s+"]");
								if(!analizyEnvelopeNamespace(this.envelope)) {
									//System.out.println("TERMINO");
									analisiTerminata=true;
									break;
								}
								envelopeFound = true;
								this.prefixEnvelope = sPrefix.substring(1)+":";
							}
							else {
								analisiTerminata=true;
								break;
							}
						}
						else if(envelopeFound) {
						
							if(headerClosure!=null) {
								//System.out.println("AGGIUNGO AD HEADER ["+s+"]");
								headerBuilder.append(s);
								sbActual.setLength(0);
								sbActualOffsetStart = i+bLetti;
								if(s.startsWith(headerClosure)) {
									headerClosure=null;	
									headerCompletato = true;
								}
								continue;
							}
							
							if(headerBuilder==null && isOpenedElement(s,"<Header")) {
								//System.out.println("INIZIALIZZO HEADER ["+s+"]");
								//headerBuilder = new StringBuilder(s);
								headerBuilder = new StringBuilder();
								if(!s.endsWith("/>")) {
									headerClosure = "</Header";
								}
								if(bodyFound) {
									analisiTerminata=true;
									break;
								}
								else {
									this.startHeaderOffset = sbActualOffsetStart;
									this.prefixHeader = "";
								}
							}
							else if(headerBuilder==null && isOpenedElement(s,":Header")) {
								String sPrefix = s.substring(0, s.indexOf(":Header"));
								if(sPrefix.startsWith("<") && !sPrefix.contains(" ") && sPrefix.length()>1) {
									//System.out.println("INIZIALIZZO HEADER ["+s+"]");
									//headerBuilder = new StringBuilder(s);
									headerBuilder = new StringBuilder();
									if(!s.endsWith("/>")) {
										headerClosure = "</"+sPrefix.substring(1)+":"+"Header";
									}
									if(bodyFound) {
										analisiTerminata=true;
										break;
									}
									else {
										this.startHeaderOffset = sbActualOffsetStart;
										this.prefixHeader = sPrefix.substring(1)+":";
									}
								}
								else {
									analisiTerminata=true;
									break;
								}
							}
							else if(!bodyFound && isOpenedElement(s,"<Body")) {
								//System.out.println("INIZIALIZZO BODY ["+s+"]");
								this.body = s;
								bodyFound = true;
								prefixBody = "";
								if(this.startHeaderOffset<=0) {
									this.startHeaderOffset = sbActualOffsetStart;
								}
								this.startBodyOffset=sbActualOffsetStart;
								sbActual.setLength(0);
								sbActualOffsetStart = i+bLetti;
							}
							else if(!bodyFound && isOpenedElement(s,":Body")) {
								String sPrefix = s.substring(0, s.indexOf(":Body"));
								if(sPrefix.startsWith("<") && !sPrefix.contains(" ") && sPrefix.length()>1) {
									//System.out.println("INIZIALIZZO BODY ["+s+"]");
									this.body = s;
									bodyFound = true;
									prefixBody = sPrefix.substring(1)+":";
									if(this.startHeaderOffset<=0) {
										this.startHeaderOffset = sbActualOffsetStart;
									}
									this.startBodyOffset=sbActualOffsetStart;
									sbActual.setLength(0);
									sbActualOffsetStart = i+bLetti;
								}
								else {
									analisiTerminata=true;
									break;
								}
							}
							else if(bodyFound) {
								if(this.fault==null && isOpenedElement(s,"<Fault")) {
									//System.out.println("INIZIALIZZO FAULT ["+s+"]");
									this.fault = s;
								}
								else if(this.fault==null && isOpenedElement(s,":Fault")) {
									String sPrefix = s.substring(0, s.indexOf(":Fault"));
									if(sPrefix.startsWith("<") && !sPrefix.contains(" ")) {
										//System.out.println("INIZIALIZZO FAULT ["+s+"]");
										this.fault = s;
									}
								}
								else {
									
									String closeBody = "</"+prefixBody+"Body";
									String closeEnvelope = "</"+this.prefixEnvelope+"Envelope";
									if( isClosedElement(s,closeBody) || isClosedElement(s,closeEnvelope) ){
										this.parsingComplete = true;
									}
									else {
										if(!endsWithGreaterThan(s)){
											if(elementAfterBodyBuilder!=null) {
												//System.out.println("AGGIUNGO A BODY dentro ["+s+"]");
												elementAfterBodyBuilder.append(s);
											}
											else {
												//System.out.println("CREO A BODY ["+s+"]");
												elementAfterBodyBuilder = new StringBuilder(s);
											}
										}
									}
									
									analisiTerminata=true;
									break;
								}
							}
							
						}
					}
					
				}
				if(analisiTerminata) {
					break;
				}
				if(kbLetti==this.bufferThresholdKb) {
					// buffer dimensione massima raggiunta
					break;
				}
			}
			
			// svuoto
			if(sbActual!=null) {
				if(sbActual.length()>0) {
					sbActual.setLength(0);
				}
			}
			
			
			bufferReaded.flush();
			bufferReaded.close();
			if(bufferReaded.size()>0) {
				this.bufferSize = bufferReaded.size();
				ByteArrayInputStream bin = new ByteArrayInputStream(bufferReaded.toByteArray());
				if(lettiBuffer!=-1) {
					// sono uscita prima della chiusura dello stream
					this.bufferedInputStream = new SequenceInputStream(bin,this.is);
				}
				else {
					// letto tutto
					this.bufferedInputStream = bin;
				}
			}
			
			
			boolean throwException = false;
			String elementAfterBody = null;
			//System.out.println("BODY ["+elementAfterBodyBuilder+"]");
			if(elementAfterBodyBuilder!=null && elementAfterBodyBuilder.length()>0) {
				elementAfterBody = elementAfterBodyBuilder.toString();
			}
			if(elementAfterBody!=null || this.fault!=null) {
				throwException=true; // ho terminato di leggere la parte interessante del messaggio
				this.parsingComplete = true;
			}

			if(headerBuilder!=null && headerBuilder.length()>0) {
				if(this.envelope.endsWith("/>")) {
					if(throwException) {
						throw new Exception("Invalid content; found 'Header' after envelope closure '/>': ("+headerBuilder+")");
					}
					else{
						return;
					}
				}
				if(headerCompletato) {
					StringBuilder sbHeaderAnalizer = new StringBuilder();
					sbHeaderAnalizer.append(this.envelope);
					sbHeaderAnalizer.append(headerBuilder.toString());
					sbHeaderAnalizer.append("<").append(this.prefixEnvelope).append("Body/>");
					sbHeaderAnalizer.append("</").append(this.prefixEnvelope).append("Envelope>");
					this.header = sbHeaderAnalizer.toString();
				}
			}
			if(this.body!=null) {
				//System.out.println("BODY ["+this.body+"]");
				if(this.envelope.endsWith("/>")) {
					if(throwException) {
						throw new Exception("Invalid content; found 'Body' after envelope closure '/>': ("+this.body+")");
					}
					else{
						return;
					}
				}
				if(this.body.endsWith("/>")) {
					this.bodyEmpty = true;
				}
			}
			
			if(this.fault!=null) {
				//System.out.println("TROVATO FAULT ["+this.fault+"]");
				if(this.body.endsWith("/>")) {
					if(throwException) {
						throw new Exception("Invalid content; found Fault after body closure '/>': ("+this.fault+")");
					}
					else{
						return;
					}
				}
				StringBuilder sbFaultAnalizer = new StringBuilder();
				sbFaultAnalizer.append(this.envelope);
				sbFaultAnalizer.append(this.body);
				sbFaultAnalizer.append(this.fault);
				if((this.fault.contains("<") && this.fault.contains(">")) && !this.fault.trim().endsWith("/>")) {
					//System.out.println("CALCOLO!! ["+this.fault+"]");
					int i = 0;
					for (; i < this.fault.length(); i++) {
						char c = this.fault.charAt(i);
						if(c=='<') {
							i++;
							break;
						}
					}
					//System.out.println("DOPO GIRO: "+i);
					StringBuilder sb = new StringBuilder();
					for (; i < this.fault.length(); i++) {
						char c = this.fault.charAt(i);
						if(c==' ' || c=='>' || c=='\t' || c=='\r' || c=='\n') {
							break;
						}
						sb.append(c);
					}
					sbFaultAnalizer.append("</").append(sb.toString()).append(">");
					//System.out.println("TROVATO:["+sb.toString()+"]");
				}
				
				sbFaultAnalizer.append("</").append(prefixBody).append("Body>");
				sbFaultAnalizer.append("</").append(this.prefixEnvelope).append("Envelope>");
				//Element e = null;
				String namespaceFaultTrovato = null;
				try {
					OpenSPCoop2Message msg = buildOp2Message(sbFaultAnalizer.toString().getBytes(), this.getMessageType());
					SOAPBody soapBody = msg.castAsSoap().getSOAPBody();
					if(soapBody!=null) {
						if(soapBody.hasFault()) {
							namespaceFaultTrovato = soapBody.getFault().getNamespaceURI();
						}
					}
				}catch(Throwable t) {
					if(throwException) {
						throw new Exception("Invalid content ("+sbFaultAnalizer.toString()+"): "+t.getMessage(),t);
					}
					else{
						return;
					}
				}
				if(!this.namespace.equals(namespaceFaultTrovato)) {
					elementAfterBody = this.fault;
					this.fault = null; // si tratta di un contenuto applicativo con nome Fault
				}
			}
			
			
			if(this.fault==null && elementAfterBody!=null) {
				//System.out.println("elementAfterBody ["+elementAfterBody+"]");

				StringBuilder sbElementAnalizer = new StringBuilder();
				sbElementAnalizer.append(this.envelope);
				sbElementAnalizer.append(this.body);
				sbElementAnalizer.append(elementAfterBody);
				if((elementAfterBody.contains("<") && elementAfterBody.contains(">")) && !elementAfterBody.trim().endsWith("/>")) {
					//System.out.println("CALCOLO!! ["+elementAfterBody+"]");
					int i = 0;
					for (; i < elementAfterBody.length(); i++) {
						char c = elementAfterBody.charAt(i);
						if(c=='<') {
							i++;
							break;
						}
					}
					//System.out.println("DOPO GIRO: "+i);
					StringBuilder sb = new StringBuilder();
					for (; i < elementAfterBody.length(); i++) {
						char c = elementAfterBody.charAt(i);
						if(c==' ' || c=='>' || c=='\t' || c=='\r' || c=='\n') {
							break;
						}
						sb.append(c);
					}
					sbElementAnalizer.append("</").append(sb.toString()).append(">");
					//System.out.println("TROVATO:["+sb.toString()+"]");
				}
				else {
					//System.out.println("RIPULISCO");
					boolean empty = true;
					for (int i = 0; i < elementAfterBody.length(); i++) {
						char c = elementAfterBody.charAt(i);
						if(c!=' ' && c!='\t' && c!='\r' && c!='\n') {
							empty = false;
							break;
						}
					}
					//System.out.println("RIPULISCO EMPTY: "+empty);
					if(empty) {
						this.bodyEmpty=true;
					}
				}
				
				//System.out.println("BODY ["+this.body+"]");
				if(this.body!=null && this.body.endsWith("/>")) {
					if(!this.bodyEmpty) {
						if(throwException) {
							throw new Exception("Invalid content; found element after body closure '/>': ("+elementAfterBody+")");
						}
						else{
							return;
						}
					}
				}
				else {
				
					sbElementAnalizer.append("</").append(prefixBody).append("Body>");
					sbElementAnalizer.append("</").append(this.prefixEnvelope).append("Envelope>");
					//System.out.println("UTILIZZO:["+sbElementAnalizer.toString()+"]");
					//Element e = null;
					SAXParser saxParser = null;
					try {
						//e = XMLUtils.getInstance().newElement(sbElementAnalizer.toString().getBytes());
						
						saxParser = getParser();
						XMLReader xmlReader = saxParser.getXMLReader();
						RootElementSaxContentHandler saxHandler = new RootElementSaxContentHandler(this.namespace);
						xmlReader.setContentHandler(saxHandler);
						//System.out.println("ANALIZZO '"+sbElementAnalizer.toString()+"'");
						try(ByteArrayInputStream bin = new ByteArrayInputStream(sbElementAnalizer.toString().getBytes())){
							InputSource inputSource = new InputSource(bin);
							xmlReader.parse(inputSource);
						}
						this.rootElementLocalName = saxHandler.getLocalName();
						this.rootElementNamespace = saxHandler.getNamespace();	
						this.rootElementPrefix = saxHandler.getPrefix();
					}catch(Throwable t) {
						if(throwException) {
							throw new Exception("Invalid content '"+elementAfterBody+"' ("+sbElementAnalizer.toString()+"): "+t.getMessage(),t);
						}
						else{
							return;
						}
					}finally {
						if(saxParser!=null) {
							returnParser(saxParser);
						}
					}
	//				Node body = SoapUtils.getFirstNotEmptyChildNode(this.msgFactory, e, false);
	//				if(body!=null) {
	//					Node element = SoapUtils.getFirstNotEmptyChildNode(this.msgFactory, body, false);
	//					if(element instanceof Element) {
	//						this.rootElement = (Element) element;
	//					}
	//				}
				}
				
			}
			
			if(this.fault==null && elementAfterBody==null) {
				if(this.parsingComplete) { // siamo arrivato alla chiusura del body o dell'envelope
					this.bodyEmpty=true;
				}
			}
			
			
			//System.out.println("parsingComplete["+this.parsingComplete+"] charset["+charset+"] startHeaderOffset["+this.startHeaderOffset+"] < endHeaderOffset["+this.endHeaderOffset+"] < startBodyOffset["+this.startBodyOffset+"]");
			if(this.parsingComplete) {
				if(charset==null || !charset.startsWith("UTF-16")) {
					if(this.startHeaderOffset>0 && this.startBodyOffset>=this.startHeaderOffset) {
						this.soapHeaderOptimizable = true;
						if(this.endHeaderOffset>0) {
							if(this.endHeaderOffset<this.startHeaderOffset || this.endHeaderOffset>this.startBodyOffset) {
								// inconsistenza
								this.soapHeaderOptimizable = false;
							}
						}
					}
				}
			}
			
		}catch(Throwable t) {
			this.tBuffered = new MessageException(t.getMessage(),t);
			throw this.tBuffered;
		}
		
		return;
		
	}
	private boolean endsWithGreaterThan( String s ) {
		if ( s == null || s.length() < 4 )
			return false;
		int startIx = s.length() - 4;
		return ( s.charAt(startIx) == '&' && s.charAt(startIx + 1) == 'g' && s.charAt(startIx + 2) == 't' && s.charAt(startIx + 3) == ';' );
	}
	private boolean endsWithGreaterThan( StringBuilder buffer ) {
		if ( buffer == null || buffer.length() < 4 )
			return false;
		int startIx = buffer.length() - 4;
		return ( buffer.charAt(startIx) == '&' && buffer.charAt(startIx + 1) == 'g' && buffer.charAt(startIx + 2) == 't' && buffer.charAt(startIx + 3) == ';' );
	}
	private boolean startsWithLessThan( StringBuilder buffer ) {
		if ( buffer == null || buffer.length() < 4 )
			return false;
		return ( buffer.charAt(0) == '&' && buffer.charAt(1) == 'l' && buffer.charAt(2) == 't' && buffer.charAt(3) == ';' );
	}
	
	private boolean isOpenedElement(String s, String prefix) {
		if(prefix.startsWith(":")) {
			return s.contains(prefix+" ") || s.contains(prefix+">") || s.contains(prefix+"/")  || 
					s.contains(prefix+"\n") || s.contains(prefix+"\r") || s.contains(prefix+"\t");
		}
		else {
			return s.startsWith(prefix+" ") || s.startsWith(prefix+">") || s.startsWith(prefix+"/")  || 
					s.startsWith(prefix+"\n") || s.startsWith(prefix+"\r") || s.startsWith(prefix+"\t");
		}
	}
	private boolean isClosedElement(String s, String prefix) {
		return s.startsWith(prefix+" ") || s.startsWith(prefix+">") || 
				s.startsWith(prefix+"\n") || s.startsWith(prefix+"\r") || s.startsWith(prefix+"\t");
	}
	
	private boolean analizyEnvelopeNamespace(String envelope) throws Exception {
		if(envelope!=null) {
			String s = null;
			if(envelope.endsWith("/>")) {
				s = envelope;
			}
			else {
				s = envelope.replace(">", "/>");
			}
			
			//Element e = XMLUtils.getInstance().newElement(s.getBytes());
			//this.namespace = e.getNamespaceURI();
			//System.out.println("ENVELOPE PARSE ["+envelope+"]");
			SAXParser saxParser = null;
			try {
				saxParser = getParser();
				XMLReader xmlReader = saxParser.getXMLReader();
				SoapEnvelopeSaxContentHandler saxHandler = new SoapEnvelopeSaxContentHandler();
				xmlReader.setContentHandler(saxHandler);
				try(ByteArrayInputStream bin = new ByteArrayInputStream(s.getBytes())){
					InputSource inputSource = new InputSource(bin);
					xmlReader.parse(inputSource);
				}
				this.namespace = saxHandler.getNamespace();
				//System.out.println("ENVELOPE PARSE ["+envelope+"], found namespace: "+this.namespace);
			}
			catch(Throwable t) {
				throw new Exception("Invalid content ("+s+"): "+t.getMessage(),t);
			}
			finally {
				if(saxParser!=null) {
					returnParser(saxParser);
				}
			}
			
			
			return Costanti.SOAP_ENVELOPE_NAMESPACE.equals(this.namespace) ||
					Costanti.SOAP12_ENVELOPE_NAMESPACE.equals(this.namespace);
		}
		return false;
	}

	public String getEnvelope() {
		return this.envelope;
	}

	public String getNamespace() {
		return this.namespace;
	}
	
	public MessageType getMessageType() {
		if(Costanti.SOAP_ENVELOPE_NAMESPACE.equals(this.namespace)) {
			return MessageType.SOAP_11;
		}
		else {
			return MessageType.SOAP_12;
		}
	}

	public boolean isParsingComplete() {
		return this.parsingComplete;
	}
	
//	public String getBody() {
//		return this.body;
//	}

	public boolean isFault() {
		return this.fault!=null;
	}
	
	public boolean isEmpty() {
		return this.bodyEmpty;
	}
	
	public int getBufferSize() {
		return this.bufferSize;
	}
	
	public OpenSPCoop2Message getHeader_OpenSPCoop2Message() throws MessageException {
		boolean checkIsEmpty = true;
		SOAPHeader s = _getHeader(checkIsEmpty, false); // perchè la struttura del soap header viene mantenuta anche vuota, per preservare i commenti.
		if(s!=null) {
			return this._headerMsgCompleto;
		}
		return null;
	}
	public SOAPHeader getHeader() throws MessageException {
		return _getHeader(false, false);
	}
	public SOAPHeader getModifiableHeader() throws MessageException {
		this.soapHeaderModified = true;
		return _getHeader(false, false);
	}
	public SOAPHeader addHeader() throws MessageException {
		return _getHeader(false, true);
	}
	public boolean isSoapHeaderModified() {
		return this.soapHeaderModified;
	}
	public void setSoapHeaderModified(boolean soapHeaderModified) {
		this.soapHeaderModified = soapHeaderModified;
	}
	private MessageException parseErrorHeader = null;
	private SOAPHeader _getHeader(boolean checkIsEmpty, boolean buildIfEmpty) throws MessageException {
		
		if(this.parseErrorHeader!=null) {
			throw this.parseErrorHeader;
		}
		
		SOAPHeader soapHeader = null;
		
		if(this._headerMsgCompleto!=null) {
			try{
				soapHeader = this._headerMsgCompleto.castAsSoap().getSOAPHeader();
			}catch(Throwable t) {
				throw SoapUtils.buildMessageException("Error during access header: "+t.getMessage(),t);
			}
		}
		else {
			if(this._headerMsgCompleto==null && this.header!=null) {
				try {
					//System.out.println("COSTRUISCO ["+this.header+"]");
					this._headerMsgCompleto = buildOp2Message(this.header.getBytes(), this.getMessageType());
					soapHeader = this._headerMsgCompleto.castAsSoap().getSOAPHeader();
					//System.out.println("COSTRUITO ["+org.openspcoop2.message.xml.XMLUtils.getInstance(this.msgFactory).toString(soapHeader)+"]");
										
					if(soapHeader!=null) {
						if("".equals(this.prefixEnvelope)) {
							this._headerMsgCompleto.castAsSoap().getSOAPPart().getEnvelope().setPrefix("");
						}
						if("".equals(this.prefixHeader)) {
							soapHeader.setPrefix("");
						}
						//System.out.println("HEADER COSTRUITO DA QUANTO LETTO CON SAAJ PREFIX ["+soapHeader.getPrefix()+"], LETTO: "+this.header);
					}
					
					//this._header = XMLUtils.getInstance().newElement(this.header.getBytes());
					
					this.header = null; // libero memoria
					
				}catch(Throwable t) {
					//System.out.println("ECCEZIONE!");
					this.parseErrorHeader = SoapUtils.buildMessageException("Invalid header ("+this.header+"): "+t.getMessage(),t);
					throw this.parseErrorHeader;
				}
			}
			
			if(this._headerMsgCompleto==null && buildIfEmpty) {
				try {
					String xmlns = "";
					if(this.prefixEnvelope!=null && !"".equals(this.prefixEnvelope)) {
						xmlns=":"+this.prefixEnvelope;
						if(xmlns.endsWith(":") && xmlns.length()>1) {
							xmlns = xmlns.substring(0, xmlns.length()-1);
						}
					}
					String envelope = "<"+this.prefixEnvelope+"Envelope xmlns"+xmlns+"=\""+this.namespace+"\"><"+this.prefixEnvelope+"Header/></"+this.prefixEnvelope+"Envelope>";
					this._headerMsgCompleto = buildOp2Message(envelope.getBytes(), this.getMessageType());
					soapHeader = this._headerMsgCompleto.castAsSoap().getSOAPHeader();
					if("".equals(xmlns)) {
						this._headerMsgCompleto.castAsSoap().getSOAPPart().getEnvelope().setPrefix("");
						soapHeader.setPrefix("");
					}
					//System.out.println("HEADER CREATO CON SAAJ PREFIX ["+soapHeader.getPrefix()+"]");
					this.soapHeaderModified = true;
				}catch(Throwable t) {
					throw SoapUtils.buildMessageException("Build header failed: "+t.getMessage(),t);
				}
			}
		}
		
		if(soapHeader!=null && checkIsEmpty) {
			Node n = SoapUtils.getFirstNotEmptyChildNode(OpenSPCoop2MessageFactory.getDefaultMessageFactory(), soapHeader, false);
			if(n==null) {
				soapHeader = null;
			}
		}
		
		/*if(soapHeader!=null) {
			try {
				TransformerConfig xmlConfig = new TransformerConfig();
				xmlConfig.setOmitXMLDeclaration(true);
				xmlConfig.setCharset(this.isCharset);
				System.out.println("COSTRUITO ["+org.openspcoop2.message.xml.XMLUtils.getInstance(OpenSPCoop2MessageFactory.getDefaultMessageFactory()).toString(soapHeader, xmlConfig)+"]");
				org.w3c.dom.NodeList l = soapHeader.getChildNodes();
				for (int i = 0; i < l.getLength(); i++) {
					System.out.println("BEFORE ["+l.item(i).getClass().getName()+"] ["+l.item(i).getLocalName()+"]");
				}
			}catch(Throwable t) {
				System.out.println("ERRORE");
			}
		}*/
		
		return soapHeader;
	}
	public void clearHeader() {
		this._headerMsgCompleto=null;
		this.soapHeaderOptimizable=false;
	}


//	public Element getRootElement() {
//		return this.rootElement;
//	}
	public String getRootElementNamespace() {
		return this.rootElementNamespace;
	}
	public String getRootElementLocalName() {
		return this.rootElementLocalName;
	}
	public String getRootElementPrefix() {
		return this.rootElementPrefix;
	}
	
    private static ThreadLocal<SAXParser> saxParserThreadLocal = 
	            new ThreadLocal<SAXParser>() {
	        @Override
			protected SAXParser initialValue() {
	        	try{
	    			SAXParserFactory saxFactory = XMLUtils.getInstance().getSAXParserFactory();
//	    	        try {
//	    	        	saxFactory.setFeature("jdk.xml.resetSymbolTable", true);
//	    	        } catch(Throwable e) {
//	    	        }
	    	        saxFactory.setNamespaceAware(true);
	    	        SAXParser parser = saxFactory.newSAXParser();
	    	        return parser;
				}catch(Throwable t){
					throw new RuntimeException("Inizializzazione SAXParser fallita: "+t.getMessage(),t);
				}
	        }
	};
	private static SAXParser getParser() {
		return saxParserThreadLocal.get();
	}
	public static void removeParser() {
		saxParserThreadLocal.remove();
	}
    		
    private static void returnParser(SAXParser saxParser) {
        saxParser.reset();
    }
    
    private static OpenSPCoop2Message buildOp2Message(byte[] bytes, MessageType messageType) throws Exception {
		MimeHeaders mhs = new MimeHeaders();
		mhs.addHeader(HttpConstants.CONTENT_TYPE, MessageType.SOAP_11.equals(messageType) ? HttpConstants.CONTENT_TYPE_SOAP_1_1 : HttpConstants.CONTENT_TYPE_SOAP_1_2);
		OpenSPCoop2Message msg = null;
		try(ByteArrayInputStream bin = new ByteArrayInputStream(bytes)){
			if(MessageType.SOAP_11.equals(messageType)){
				msg = new OpenSPCoop2Message_saaj_11_impl(OpenSPCoop2MessageFactory.getDefaultMessageFactory(), mhs, bin);
			}
			else {
				msg = new OpenSPCoop2Message_saaj_12_impl(OpenSPCoop2MessageFactory.getDefaultMessageFactory(), mhs, bin);
			}
		}
		return msg;
    }
    
	public boolean isSoapHeaderOptimizable() {
		return SOAP_HEADER_OPTIMIZATION_ENABLED && this.soapHeaderOptimizable;
	}
	
	public void writeOptimizedHeaderTo(InputStream is, OutputStream osParam, boolean consumeHeader) throws Exception {
		if(!this.soapHeaderOptimizable) {
			throw new Exception("SOAPHeader not optimizable");
		}
		
		if(this._headerMsgCompleto==null) {
			throw new Exception("SOAPMessage Optimized undefined");
		}
		SOAPHeader soapHeader = this._headerMsgCompleto.castAsSoap().getSOAPHeader();
		if(soapHeader==null) {
			throw new Exception("SOAPHeader undefined");
		}
		
		TransformerConfig xmlConfig = new TransformerConfig();
		xmlConfig.setOmitXMLDeclaration(true);
		xmlConfig.setCharset(this.isCharset);
				
		long indexBody = this.startBodyOffset;
		if(this.endHeaderOffset>0) {
			indexBody = this.endHeaderOffset+1;
		}
		
		// first
		byte[] buffer = new byte[Utilities.DIMENSIONE_BUFFER];
		int letti = 0;
		long index = 0;
		boolean writeHeader = false;
		
		boolean debug = false;
		OutputStream os = null;
		if(debug) {
			os = new ByteArrayOutputStream();
		}
		else {
			os = osParam;
		}
		while( (letti=is.read(buffer, 0, Utilities.DIMENSIONE_BUFFER)) != -1 ){
			
			if(index<this.startHeaderOffset) {
				for (int i=0; i < letti; i++) {
					if(!writeHeader || index>=indexBody) {
						os.write(buffer[i]);
					}
					index++;
					
					if(index==this.startHeaderOffset) {
						if(debug) {
							os.flush();
							//System.out.println("SCRITTO FINO A HEADER ["+os.toString()+"]");
						}
						
						//os.write(OpenSPCoop2MessageFactory.getAsByte(OpenSPCoop2MessageFactory.getDefaultMessageFactory(), this._header, true));
						os.write(org.openspcoop2.message.xml.MessageXMLUtils.getInstance(OpenSPCoop2MessageFactory.getDefaultMessageFactory()).toByteArray(soapHeader, xmlConfig));
						writeHeader = true;
						if(debug) {
							os.flush();
							//System.out.println("SCRITTO HEADER ["+os.toString()+"]");
						}
					}
				}
			}
			else {
				if(!writeHeader) {
					//os.write(OpenSPCoop2MessageFactory.getAsByte(OpenSPCoop2MessageFactory.getDefaultMessageFactory(), this._header, true));
					os.write(org.openspcoop2.message.xml.MessageXMLUtils.getInstance(OpenSPCoop2MessageFactory.getDefaultMessageFactory()).toByteArray(soapHeader, xmlConfig));
					writeHeader = true;
					if(debug) {
						os.flush();
						//System.out.println("SCRITTO HEADER GIRO SUCCESSIVO ["+os.toString()+"]");
					}
				}
				if(index>=indexBody) {
					os.write(buffer, 0, letti);
					index = index+letti;
				}
				else {
					for (int i=0; i < letti; i++) {
						if(index>=indexBody) {
							os.write(buffer[i]);
						}
						index++;
					}
				}
				if(debug) {
					os.flush();
					//System.out.println("SCRITTO PEZZO ["+os.toString()+"]");
				}
			}
			
		}
				
		if(debug) {
			os.flush();
			//System.out.println("SCRITTO TUTTO ["+os.toString()+"]");
		}
		
		
		// flush
		os.flush();
		
		// ** rilascio memoria **
		if(consumeHeader) {
			this._headerMsgCompleto = null;
			this.soapHeaderOptimizable=false;
			this.bufferedInputStream = null;
		}
	}
}