/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.processor.xml;
import java.util.Iterator;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import net.sf.saxon.TransformerFactoryImpl;
import org.teiid.common.buffer.FileStore;
import org.teiid.common.buffer.FileStoreInputStreamFactory;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.types.SQLXMLImpl;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.query.QueryPlugin;
import org.teiid.query.mapping.xml.MappingNodeConstants;
import org.xml.sax.SAXException;
/**
* This class is used to build XML document and stream the output as
* chunks. The class holds one chunk of the document in memory at one time.
*/
public class DocumentInProgress {
private TransformerHandler handler;
private Transformer transformer;
private Element currentParent;
private Element currentObject;
private boolean finished;
private boolean isFormatted = MappingNodeConstants.Defaults.DEFAULT_FORMATTED_DOCUMENT.booleanValue();
private SQLXMLImpl xml;
public DocumentInProgress(FileStore store, String encoding) throws TeiidComponentException{
final FileStoreInputStreamFactory fsisf = new FileStoreInputStreamFactory(store, encoding);
this.xml = new SQLXMLImpl(fsisf);
this.xml.setEncoding(encoding);
SAXTransformerFactory factory = new TransformerFactoryImpl();
try {
//SAX2.0 ContentHandler
handler = factory.newTransformerHandler();
handler.setResult(new StreamResult(fsisf.getOuputStream()));
} catch (Exception e) {
throw new TeiidComponentException(QueryPlugin.Event.TEIID30204, e);
}
transformer = handler.getTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
}
public SQLXMLImpl getSQLXML() {
return xml;
}
/**
* @see org.teiid.query.processor.xml.DocumentInProgress#setDocumentFormat(boolean)
*/
public void setDocumentFormat(boolean isFormatted) {
this.isFormatted = isFormatted;
}
/**
* Move to the parent of this element. The parent of this element becomes
* the current object. Need to process the current and child object before moving
* to the parent.
* @throws SAXException
* @see org.teiid.query.processor.xml.DocumentInProgress#moveToParent()
*/
public boolean moveToParent() throws SAXException {
showState( "moveToParent - TOP" ); //$NON-NLS-1$
endElement(currentObject);
//move to parent - if parent is null, then stop processing here
if(currentParent == null){
return false;
}
showState( "moveToParent - before processWorkingElements, second time" ); //$NON-NLS-1$
currentObject = currentParent;
currentParent = currentParent.getParent();
showState( "moveToParent - BOT" ); //$NON-NLS-1$
return true;
}
/**
* @see org.teiid.query.processor.xml.DocumentInProgress#moveToLastChild()
*/
public boolean moveToLastChild() {
showState( "moveToLastChild - TOP" ); //$NON-NLS-1$
currentParent = currentObject;
currentObject = null;
showState( "moveToLastChild - BOT" ); //$NON-NLS-1$
return true;
}
public boolean addElement(NodeDescriptor descriptor, NodeDescriptor nillableDescriptor){
return addElement(descriptor, null, nillableDescriptor);
}
public boolean addElement(NodeDescriptor descriptor, String content){
return addElement(descriptor, content, null);
}
private boolean addElement(NodeDescriptor descriptor, String content, NodeDescriptor nillableDescriptor){
showState( "addElement(2) - TOP" ); //$NON-NLS-1$
try{
if(currentParent == null){
//this is the root element, start document first
showState( "addElement(2) - before StartDocument()" ); //$NON-NLS-1$
startDocument();
}
Element element = makeElement(descriptor);
if (element != null){
if (content != null) {
element.setContent(normalizeText(content, descriptor.getTextNormalizationMode()));
//mark the element and its parents to be mandatory
markAsNonOptional(element);
} else {
element.setNillableDescriptor(nillableDescriptor);
}
showState( "addElement(2) - before markAsNonOptional()" ); //$NON-NLS-1$
endElement(currentObject);
currentObject = element;
return true;
}
} catch (SAXException e) {
LogManager.logError(org.teiid.logging.LogConstants.CTX_XML_PLAN, e, e.getMessage());
return false;
}
showState( "addElement(2) - BOT" ); //$NON-NLS-1$
return false;
}
private void endElement(Element element) throws SAXException {
showState( "endElement(2) - TOP" ); //$NON-NLS-1$
if (element == null) {
return;
}
if (element.isOptional()) {
if (element.getParent() != null) {
element.getParent().getChildren().remove(element);
}
return;
}
NodeDescriptor nillableDescriptor = element.getNillableDescriptor();
if (nillableDescriptor != null) {
addAttribute(nillableDescriptor, nillableDescriptor.getDefaultValue(), element);
}
// Optional parents are in control of when their children are emitted.
if (element.hadOptionalParent()) {
return;
}
startElement(element);
processChildren(element);
element.endElement();
if (element.getParent() != null) {
element.getParent().getChildren().remove(element);
}
showState( "endElement(2) - BOT" ); //$NON-NLS-1$
}
private void startElement(Element element) throws SAXException {
Element parent = element.getParent();
while (parent != null && !parent.isElementStarted()) {
parent.setNillableDescriptor(null);
startElement(parent);
}
element.startElement();
}
private void processChildren(Element element) throws SAXException {
for (Iterator i = element.getChildren().iterator(); i.hasNext();) {
Element child = (Element)i.next();
i.remove();
child.startElement();
processChildren(child);
child.endElement();
}
}
private void markAsNonOptional(Element element) {
while(element != null){
element.setOptional(false);
element = element.getParent();
}
}
public boolean addAttribute(NodeDescriptor descriptor, String attributeValue, Element element){
element.setAttribute(descriptor, normalizeText(attributeValue,descriptor.getTextNormalizationMode()));
if (!descriptor.isOptional()){
//mark the element and its parents to be mandatory
markAsNonOptional(element);
}
return true;
}
/**
* @see org.teiid.query.processor.xml.DocumentInProgress#addAttribute(java.lang.String, java.lang.String, java.lang.String, boolean)
*/
public boolean addAttribute(NodeDescriptor descriptor, String attributeValue){
return addAttribute(descriptor, attributeValue, currentParent);
}
/**
* @throws TeiidComponentException
* @see org.teiid.query.processor.xml.DocumentInProgress#addComment(java.lang.String)
*/
public boolean addComment(String commentText) {
currentParent.setComment(commentText);
return true;
}
/**
* @see org.teiid.query.processor.xml.DocumentInProgress#isFinished()
*/
public boolean isFinished() {
return finished;
}
/**
* @see org.teiid.query.processor.xml.DocumentInProgress#markAsFinished()
*/
public void markAsFinished() throws TeiidComponentException{
try {
endDocument();
} catch (SAXException e) {
throw new TeiidComponentException(QueryPlugin.Event.TEIID30205, e);
}
finished = true;
}
private Element makeElement(NodeDescriptor descripter) {
showState( "makeElement - TOP" ); //$NON-NLS-1$
Element element = new Element(descripter, handler);
element.setParent(currentParent);
if (currentParent != null) {
currentParent.addChild(element);
}
showState( "makeElement - BOT" ); //$NON-NLS-1$
return element;
}
private void startDocument() throws SAXException{
showState( "startDocument - TOP" ); //$NON-NLS-1$
if(isFormatted){
transformer.setOutputProperty(OutputKeys.INDENT, "yes");//$NON-NLS-1$
}
handler.startDocument();
showState( "startDocument - BOT" ); //$NON-NLS-1$
}
private void endDocument() throws SAXException{
//special case: only one root element
endElement(currentObject);
handler.endDocument();
}
/**
* @param content
* @param textNormalizationMode
* preserve No normalization is done, the value is not changed for element content
* replace All occurrences of #x9 (tab), #xA (line feed) and #xD (carriage return) are replaced with #x20 (space)
* collapse After the processing implied by replace, contiguous sequences of #x20's are collapsed to a single #x20, and leading and trailing #x20's are removed.
*
* @return
* @since 4.3
*/
public static String normalizeText(String content, String textNormalizationMode) {
String result = content;
String singleSpace = " "; //$NON-NLS-1$
if(textNormalizationMode.equalsIgnoreCase(MappingNodeConstants.NORMALIZE_TEXT_REPLACE)) {
result = result.replaceAll("\\s", singleSpace); //$NON-NLS-1$
}else if(textNormalizationMode.equalsIgnoreCase(MappingNodeConstants.NORMALIZE_TEXT_COLLAPSE)){
result = result.replaceAll("\\s+", singleSpace); //$NON-NLS-1$
result = result.trim();
}
return result;
}
private void showState( String sOccasion ) {
if (LogManager.isMessageToBeRecorded(org.teiid.logging.LogConstants.CTX_XML_PLAN, MessageLevel.TRACE)) {
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"\n [showState] State Vars at: " + sOccasion} ); //$NON-NLS-1$
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"[showState] currentParent: " + currentParent} ); //$NON-NLS-1$
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"[showState] currentObject: " + currentObject} ); //$NON-NLS-1$
if ( currentObject != null ) {
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"[showState] currentObject.getNillableDescriptor(): " + currentObject.getNillableDescriptor()}); //$NON-NLS-1$
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"[showState] workingElements: " + currentObject.getChildren()}); //$NON-NLS-1$
}
if ( currentParent != null ) {
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"[showState] currentParent.getParent(): " + currentParent.getParent()}); //$NON-NLS-1$
} else {
LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"[showState] currentParent.getParent(): is NULL "}); //$NON-NLS-1$
}
}
}
}