package daruma.wfs;

import daruma.wfs.SOAPFaultDocumentBuilder;
import daruma.wfs.filter.FilterHandler;
import daruma.wfs.filter.PropertyPathConverter;
import daruma.wfs.filter.SortByHandler;

import daruma.auth.PermissionManager;
import daruma.auth.AuthenticationInfo;

import daruma.geometry.DrmEnvelope;
import daruma.geometry.CoordinateSystem;
import daruma.geometry.TransformationContext;

import daruma.xml.handler.MispDefaultHandler;
import daruma.xml.handler.XSAXDOMCreateHandler;
import daruma.xml.SAXExceptionObserver;
import daruma.xml.Lexicon;
import daruma.xml.NameSpace;
import daruma.xml.URI;
import daruma.xml.util.XMLFormatConverter;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLParseErrorException;

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;
import daruma.storage_manager.type_definition.ElementName;
import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.storage_manager.type_definition.TypedInstance;
import daruma.storage_manager.type_definition.TypedInstanceSet;
import daruma.storage_manager.type_definition.TypeException;
import daruma.storage_manager.type_definition.NoNeedToConvertTypeException;

import daruma.util.ISO8601DateFormat;
import daruma.sql.TableColumn;

import daruma.util.LogWriter;

import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.Attributes;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.xml.transform.TransformerException;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xpath.XPathAPI;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.lang.OutOfMemoryError;

import daruma.util.Itk;

public class GetFeatureHandler
    extends XSAXDOMCreateHandler
    //extends MispDefaultHandler
    implements SAXExceptionObserver
{
	private static final String DEBUG_LEVEL = "INFO";
//	private static final String DEBUG_LEVEL = "DEBUG";

	private static final int OUTPUT_BUFFER_SIZE = 16 * 1024;

	private	StorageManager	storage;
	private	PermissionManager permissionManager;
	private AuthenticationInfo authenticationInfo;

	private	Date		startTime;
	private	Date		endTime;
	private	ElementName	specifiedElement;
	private	TypeDefinition	specifiedElementType;
	private TransformationContext	transformationContext;

	private	boolean		filterTagFound;
	private	boolean		queryTagFound;
	private	boolean		sortByTagFound;
	private	long		tagCount;

	private String		queryMode;
	private String		propertyColName;
	private long		maxFeatures;
	private long		startPosition;

	private	FilterHandler	filterHandler = null;
	private	SortByHandler	sortByHandler = null;

	public	GetFeatureHandler( OutputStream  out ,
				   XMLReader  parser ,
				   boolean  isTopLevelHandler ,
				   StorageManager  storage,
				   PermissionManager permissionManager,
				   AuthenticationInfo authenticationInfo )
	{
		super( out , parser , isTopLevelHandler );

		this.storage               = storage;
		this.permissionManager     = permissionManager;
		this.authenticationInfo    = authenticationInfo;

		this.startTime             = null;
		this.endTime               = null;
		this.specifiedElement      = null;
		this.specifiedElementType  = null;
		this.transformationContext = null;

		this.queryTagFound         = false;
		this.filterTagFound        = false;
		this.sortByTagFound        = false;
		this.tagCount              = 0;

		this.queryMode =
		    Lexicon.MispAttrQueryMode_Features.localname ;
		this.propertyColName = null ;
		this.maxFeatures = -1 ;  // 負の数は limit 無し
		this.startPosition = 1;
	}



    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * DOM を作成するためのインターフェース。
     * handler が delegate されるとちゃんとした DOM を構成できない
     * 問題を回避するためのメソッド。
     * 本来なら XSAXDOMCreateHanlder あたりで解決されるべき。
     */
    public void xStartElementWrapper( String uri ,
				      String localName ,
				      String qName ,
				      Attributes  attrs ) throws SAXException {
	super.xStartElement(uri,localName,qName,attrs) ;
    }

    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * DOM を作成するためのインターフェース。
     * handler が delegate されるとちゃんとした DOM を構成できない
     * 問題を回避するためのメソッド。
     * DOMCreateHandler では protected なので、それを回避するための
     * quick hack。
     */
    public void  xCharacters( char[]  str , int  offset ,  int  length )
	throws SAXException
    {
	super.xCharacters(str,offset,length) ;
    }



    //------------------------------------------------------------
	public	void	xStartElement( String uri ,
				       String localName ,
				       String qName ,
				       Attributes  attrs ) throws SAXException
	{
	       /*
		* DOM を作るため。
		*/
		this.xStartElementWrapper(uri,localName,qName,attrs) ;

		assert super.getCurrentLevel() == 1
			|| super.getCurrentLevel() == 2
			|| super.getCurrentLevel() == 3;

		this.tagCount ++;

		// <GetFeature>
		if ( super.getCurrentLevel() == 1 )
		{
		    try
		    {
			Lexicon.MispGetFeature
			    .matchesOrSaxException( this.getCurrentNode(),
						    super.getLocator(),
						    "GetFeature" );
		    }
		    catch( SAXParseException ex)
		    {
			this.throwError(ex) ;
		    }

		    try
		    {
			this.startTime = this.storage.getCurrentTime();
		    }
		    catch( StorageException  e )
		    {
			this.throwError( new SAXParseException
					 ( e.getMessage() ,
					   super.getLocator() ,
					   e ) );
		    }


		    //
		    // maxFeatures attribute
		    //
		    String maxFeaturesStr
			    = attrs.getValue( Lexicon.MispAttrMaxFeatures
					      .localname );

		    if( maxFeaturesStr != null)
		    {
			try
			{
			    this.maxFeatures = Long.valueOf( maxFeaturesStr );

			    if ( this.maxFeatures <= 0 )
			    {
				throw new NumberFormatException();
			    }
			}
			catch(NumberFormatException ex)
			{
			    this.throwError
				 ( new SAXParseException
				   ( "Illegal "
				     + Lexicon.MispAttrMaxFeatures.localname
				     + " value [" + maxFeaturesStr + "]."
				     + " This value should be"
				     + " a positive integer.",
				     super.getLocator() ) );
			}
		    }

		    //
		    // startPosition attribute
		    //
		    String startPositionStr
			    = attrs.getValue( Lexicon.MispAttrStartPosition
					      .localname );

		    if( startPositionStr != null)
		    {
			try
			{
			    this.startPosition
				  = Long.valueOf( startPositionStr );

			    if ( this.startPosition <= 0 )
			    {
				throw new NumberFormatException();
			    }
			}
			catch( NumberFormatException ex )
			{
			    this.throwError
				 ( new SAXParseException
				   ( "Illegal "
				     + Lexicon.MispAttrStartPosition.localname
				     + " value [" + startPositionStr + "]."
				     + " This value should be"
				     + " a positive integer.",
				     super.getLocator() ) );
			}
		    }

		    return;
		}
		// <GetFeature>/<Query>
		else if ( super.getCurrentLevel() == 2 )
		{
		    try
		    {
			Lexicon.MispQuery
			    .matchesOrSaxException(this.getCurrentNode(),
						   super.getLocator(),
						   "GetFeature/Query") ;
		    }
		    catch( SAXParseException ex)
		    {
			this.throwError(ex) ;
		    }

		    this.queryTagFound = true;

		    // XXX: should unify codes with Transaction*.java
		    String typeNameString
			    = attrs.getValue
			      ( "" , Lexicon.MispAttrTypeName.localname );

		    if ( typeNameString == null ) {
			this.throwError( new SAXParseException
					 ( "typeName attribute"
					   + " not found in "
					   + localName
					   + " tag." ,
					   super.getLocator() ) );
		    }


		    //
		    // coordinate system
		    //
		    this.transformationContext
		      = new TransformationContext
			( TransformationContext.TransformationType.Conv ,
			  new CoordinateSystem
			  ( attrs.getValue( "" , "srsName" ) ) );

		    String transformType
			= attrs.getValue( "" , "transformType" );

		    if ( transformType == null
		      || transformType.equals( "conv" ) )
		    {
			this.transformationContext
			    .setTransformationType( TransformationContext
						    .TransformationType
						    .Conv );
		    }
		    else if ( transformType.equals( "selective" ) )
		    {
			this.transformationContext
			    .setTransformationType( TransformationContext
						    .TransformationType
						    .Selective );
		    }
		    else if ( transformType.equals( "noconv" ) )
		    {
			this.transformationContext
			    .setTransformationType( TransformationContext
						    .TransformationType
						    .NoConv );
		    }
		    else
		    {
			this.throwError( new SAXParseException
					 ( "invalid transformType,"
					   + " expected was one of"
					   + " [selective|conv|noconv]" ,
					   super.getLocator() ) );
		    }

		    try
		    {
			this.transformationContext.setCoordTransDictionary
			     ( this.storage.getCoordTransDictionary() );
		    }
		    catch( StorageException  e )
		    {
			this.throwError( new SAXParseException
					 ( e.getMessage() ,
					   super.getLocator() ) );
		    }


		    //
		    // element
		    //
		    try
		    {
			this.specifiedElement
				= new ElementName
				      ( super.convertQNameStringToUniversalName
					( typeNameString ) );
		    }
		    catch( SAXException  e )
		    {
			this.throwError( new SAXParseException
					 ( e.getMessage() ,
					   super.getLocator() ) );
		    }



		    //
		    // check permission to read
		    //
		    if ( ! this.permissionManager.canRead
			   ( this.authenticationInfo.getAuthenticatedAccount(),
			     this.specifiedElement ) )
		    {
			String mes;
			if ( this.authenticationInfo.getAuthenticatedAccount()
			     .getName() == null )
			{
			    mes = "reading " + this.specifiedElement
				  + " is not permitted without authorization.";
			}
			else
			{
			    mes = "reading " + this.specifiedElement
				  + " is not permitted to account ["
				  + this.authenticationInfo
					.getAuthenticatedAccount().getName()
				  + "].";
			}

			this.throwError( new SAXParseException
					 ( mes, super.getLocator() ) );
		    }


		    //
		    // mode 情報の取得
		    //
		    String modeStr = attrs.getValue(Lexicon
						    .MispAttrQueryMode
						    .localname) ;
		    if(modeStr == null)
		    {
			// do nothing
		    }
		    else if (modeStr.equals(Lexicon
					    .MispAttrQueryMode_Features
					    .localname))
		    {
			this.queryMode = modeStr ;
		    }
		    else if (modeStr.equals(Lexicon
					    .MispAttrQueryMode_Count
					    .localname))
		    {
			this.queryMode = modeStr ;
		    }
		    else if (modeStr.equals(Lexicon
					    .MispAttrQueryMode_BoundedBy
					    .localname))
		    {
			this.queryMode = modeStr ;
			this.propertyColName =
			    getPropertyColNameForBoundedByMode(attrs) ;
		    }
		    else
		    {
			this.throwError
			    ( new SAXParseException
			      (("unknown mode for GetFeature Query:"
				+ modeStr),
			       super.getLocator())) ;
		    }

		    //
		    // limit 情報の取得
		    //
		    String limitStr = attrs.getValue(Lexicon
						     .MispAttrQueryLimit
						     .localname) ;
		    if( limitStr != null
			&& maxFeatures == -1 )
		    {
			try
			{
			    this.maxFeatures = Long.valueOf(limitStr) ;

			    if ( this.maxFeatures <= 0 )
			    {
				throw new NumberFormatException();
			    }
			}
			catch(NumberFormatException ex)
			{
			    this.throwError
				 ( new SAXParseException
				   ( "Illegal "
				     + Lexicon.MispAttrQueryLimit.localname
				     + " value [" + limitStr + "]."
				     + " This value should be"
				     + " a positive integer.",
				     super.getLocator() ) );
			}
		    }

		    TypeName	elementTypeName = null;

		    try
		    {
			elementTypeName
			    = this.storage.getElementTypeName
			    ( specifiedElement );
		    }
		    catch( StorageException  e )
		    {
			this.throwError( new SAXParseException
					 ( e.getMessage() ,
					   super.getLocator() ,
					   e ) );
		    }

		    if ( elementTypeName == null )
		    {
			this.throwError
			    ( new SAXParseException
			      ( "element "
				+ specifiedElement.getLocalName()
				+ " in namespace "
				+ specifiedElement.getNamespace()
				+ " not registered" ,
				super.getLocator() ) );
		    }

		    try
		    {
			this.specifiedElementType
			    = this.storage.getTypeDefinition
					    ( elementTypeName );

			if ( this.specifiedElementType == null )
			{
			    throw new StorageException( "" );
			}
		    }
		    catch( StorageException  e )
		    {
			this.throwError
			    ( new SAXParseException
			      ( "type of element "
				+ specifiedElement.getLocalName()
				+ " in namespace "
				+ specifiedElement.getNamespace()
				+ " not registered" ,
				super.getLocator(),
				e ) );
		    }

		    return;
		}
		// <GetFeature>/<Query>/<Filter>
		else if ( super.getCurrentLevel() == 3 )
		{
		    if ( Lexicon.MispFilter.matches( this.getCurrentNode() ) )
		    {
			if ( this.filterTagFound )
			{
			    this.throwError( new SAXParseException
					     ( "Filter multiply defined.",
					       super.getLocator() ) );
			}

			this.filterTagFound = true;

			this.filterHandler = new FilterHandler
						 ( super.getOutputStream() ,
						   super.getParser() ,
						   false ,
						   this.specifiedElement ,
						   this.specifiedElementType ,
						   this.transformationContext ,
						   this.storage ,
						   this );

			super.setContentHandlerDelegator
			      ( this.filterHandler ,
				uri , localName , qName , attrs );

			return;
		    }
		    else if ( Lexicon.MispSortBy
			      .matches( this.getCurrentNode() ) )
		    {
			if ( ! this.filterTagFound )
			{
			    this.throwError( new SAXParseException
					     ( "SortBy element should be "
					       + "appeared after "
					       + "Filter element." ,
					       super.getLocator() ) );
			}

			this.sortByTagFound = true;
			this.sortByHandler = new SortByHandler
						 ( super.getOutputStream(),
						   super.getParser() ,
						   false,
						   this.specifiedElementType,
						   this.storage,
						   this );

			super.setContentHandlerDelegator
			      ( this.sortByHandler ,
				uri , localName , qName , attrs );

		    }
		    else
		    {
			this.throwError( new SAXParseException
					 ( "unexpected element "
					   + localName
					   + " in namespace " + uri + ", "
					   + "expected was "
					   + "Filter or SortBy in namespace "
					   + URI.MISP + "." ,
					   super.getLocator() ) );
		    }
		}
	}


	public	void  xEndDocument() throws SAXException
	{
		if ( ! this.filterTagFound )
		{
			this.throwError( new SAXParseException
					 ( "Filter tag not found" ,
					   super.getLocator() ) );
		}

		if ( ! this.queryTagFound )
		{
			this.throwError( new SAXParseException
					 ( "Query tag not found" ,
					   super.getLocator() ) );
		}

		if ( this.specifiedElement == null )
		{
			this.throwError( new SAXParseException
					 ( "element not specified" ,
					   super.getLocator() ) );
		}

		int expectedTagCount = 3 + (this.sortByTagFound ? 1 : 0);

		if ( this.tagCount != expectedTagCount )
		{
			this.throwError( new SAXParseException
					 ( "too "
					   + (this.tagCount
					      < expectedTagCount ?
					      "few" : "many") + " tags found" ,
					   super.getLocator() ) );
		}


		try
		{
			this.endTime = this.storage.getCurrentTime();
		}
		catch( StorageException  e )
		{
			this.throwError( new SAXParseException
					 ( e.getMessage() ,
					   super.getLocator() ,
					   e ) );
		}


		//
		// main process of this class
		//
		this.outputResult();
	}

    /*
    //------------------------------------------------------------
    /**
     * BoundedBy mode の場合に、Query 中の属性名を、カラム名に
     * 直す。
     */
    private String getPropertyColNameForBoundedByMode(Attributes attrs)
	throws SAXException
    {
	String propName = attrs.getValue(Lexicon
					 .MispAttrQueryProp
					 .localname) ;

	if(propName == null)
	{
	    this.throwError(new SAXParseException(("a property name for "
						   + this.queryMode
						   + " should be specified."),
						  super.getLocator())) ;
	}

	String colName = null;
	try
	{
	    colName =
		PropertyPathConverter.convertPropertyPathToShortXPath
		(propName, this.getCurrentNode(), this.storage) ;

	}
	catch(XMLParseErrorException ex)
	{
	    this.throwError(new SAXParseException(("a property name for "
						   + this.queryMode
						   + " should be specified."),
						  super.getLocator(),
						  ex)) ;
	}
	return colName ;
    }

    /*
    //------------------------------------------------------------
    /**
     * outputResult* の Wrapper
     */
    public	void	outputResult() throws SAXException
    {
	if        (this.queryMode.equals(Lexicon
					 .MispAttrQueryMode_Features
					 .localname)) {
	    outputResultFeatures() ;
	} else if (this.queryMode.equals(Lexicon
					 .MispAttrQueryMode_Count
					 .localname)) {
	    constructResultCount() ;
	} else if (this.queryMode.equals(Lexicon
					 .MispAttrQueryMode_BoundedBy
					 .localname)) {
	    constructResultBoundedBy() ;
	} else {
	    throw new SAXException("unknown query mode:"
				   + this.queryMode) ;
	}
    }

    private class LongHolder
    {
	private long value;

	LongHolder( long value )
	{
	    this.value = value;
	}

	public void setValue( long value )
	{
	    this.value = value;
	}

	public long getValue()
	{
	    return value;
	}
    }

    public	void	outputResultFeatures() throws SAXException
    {
	PrintWriter out = super.getPrintWriter();

	//
	// print header
	//
	constructResponseInfo(true) ;

	if(Itk.useDOMResponse)
	{
	    this.getResponse().outputHeaderPart(out) ;
	}
	else
	{
	    out.println( "<misp:GetFeatureResponse"
			 + " xmlns:misp=\"" + NameSpace.MISP.uri + "\""
			 + " xmlns:gml=\"" + NameSpace.GML.uri + "\">" );

	    out.println( "  <misp:ElapsedTime>" );
	    out.println( "    <gml:beginPosition>"
			 + new ISO8601DateFormat()
			 .format(this.startTime)
			 + "</gml:beginPosition>" );
	    out.println( "    <gml:endPosition>"
			 + new ISO8601DateFormat()
			 .format(this.endTime)
			 + "</gml:endPosition>" );

	    out.println( "  </misp:ElapsedTime>" );

	    out.println( "  <misp:FeatureCollection>" );
	}

	try
	{
	    out.flush();
	    this.getOutputStream().flush();
	}
	catch( IOException  e )
	{
	    throw new SAXException( e );
	}


	//
	// print data
	//
	ByteArrayOutputStream buf = new ByteArrayOutputStream();

	this.getObjectAndPrint( buf ,
				this.startPosition - 1, this.maxFeatures,
				-1,
				new LongHolder(-1), 0 );

	//
	// print footer
	//
	if(! Itk.useDOMResponse)
	{
	    try
	    {
		super.getOutputStream()
		    .write( "  </misp:FeatureCollection>\n".getBytes() );
		super.getOutputStream()
		    .write( "</misp:GetFeatureResponse>\n".getBytes() );
	    }
	    catch ( IOException e )
	    {
		throw new SAXException( e );
	    }
	}
    }

    void getObjectAndPrint( ByteArrayOutputStream buf,
			    long offset,
			    long nObjects,
			    long wholeObjectCount,
			    LongHolder failedSize,
			    long level ) throws SAXException
    {
	if ( level != 0
	     || DEBUG_LEVEL.equals( "DEBUG" ) )
	{
	    LogWriter.qwrite( DEBUG_LEVEL,
			      "object retrieve: level = ", level, ", ",
			      "offset = ", offset, ", ",
			      "nObjects = ", nObjects );
	}

	TypedInstanceSet objectSet = null;

	try
	{
	    try
	    {
		// XXX: quick hack
		// XXX: a simple loop is better than recursive call
		if ( failedSize.getValue() != -1
		     && nObjects != -1
		     && nObjects > failedSize.getValue() )
		{
		    LogWriter.qwrite( DEBUG_LEVEL,
				      "nObjects is grater than ",
				      "last faild value ",
				      failedSize.getValue(),
				      ", assume failed" );
		
		    throw new OutOfMemoryError();
		}

		objectSet = storage.getTypedInstanceSet
			    ( this.specifiedElement ,
			      this.filterHandler.getSQLExpression(),
			      (this.sortByHandler != null ?
			       this.sortByHandler.getSQLExpression() : null),
			      this.transformationContext,
			      offset,
			      nObjects );

		try
		{
		    this.printObjectSet( objectSet, buf );
		}
		catch( OutOfMemoryError e )
		{
		    e.printStackTrace();

		    SAXParseException se = new SAXParseException
					       ( "Can't retrieve objects: "
						 + e.getMessage() ,
						 super.getLocator() );
		    se.setStackTrace( e.getStackTrace() );
		    this.throwError( se );
		}
	    }
	    catch( StorageException  e )
	    {
		e.printStackTrace();

		SAXParseException se = new SAXParseException
					   ( "Can't retrieve objects: "
					     + e.getMessage() ,
					     super.getLocator() ,
					     e );
		se.setStackTrace( e.getStackTrace() );

		this.throwError( se );
	    }
	    catch( OutOfMemoryError e )
	    {
		try
		{
		    // reset first, before debug print
		    // to inhibit more OutOfMemoryError
		    this.storage.reset();

		    LogWriter.qwrite( DEBUG_LEVEL,
				      "OutOfMemory occured, ",
				      "trying divided fetch, ",
				      "offset = ", offset, ", ",
				      "nObjects = ", nObjects );


		    long totalSize = wholeObjectCount;

		    if ( totalSize == -1 )
		    {
			if ( maxFeatures == -1 )
			{
			    LogWriter.qwrite( DEBUG_LEVEL,
					      "fetching number of",
					      " objects ..." );

			    totalSize = this.storage.getCountOfTypedInstanceSet
					      ( this.specifiedElement ,
						this.filterHandler
						    .getSQLExpression(),
						this.maxFeatures );

			    LogWriter.qwrite( DEBUG_LEVEL,
					      "number of objects: ",
					      totalSize );
			}
			else
			{
			    totalSize = maxFeatures;

			    LogWriter.qwrite( DEBUG_LEVEL,
					      "number of objects",
					      " to retrieve: ",
					      totalSize );
			}

		    }

		    //
		    // we already received OutOfMemory error,
		    // so if totalSize is 1 or 0, we can't retrieve data.
		    //
		    if ( totalSize <= 1 )
		    {
			SAXParseException se = new SAXParseException
					       ( "Can't retrieve objects: "
						 + e.getMessage() ,
						 super.getLocator() );
			se.setStackTrace( e.getStackTrace() );

			this.throwError( se );
		    }


		    long n = totalSize;
		    if ( nObjects == -1 )
		    {
			nObjects = n;
		    }

		    if ( n > offset + nObjects )
		    {
			n = nObjects;
		    }
		    else
		    {
			n -= offset;
		    }

		    if ( failedSize.getValue() == -1
			 || failedSize.getValue() > n )
		    {
			failedSize.setValue( n );
		    }

		    LogWriter.qwrite( DEBUG_LEVEL,
				      "faild size = ", failedSize.getValue() );

		    long firstN = (n - n / 2);
		    long secondN = n - firstN;

		    LogWriter.qwrite( DEBUG_LEVEL,
				      "fetch first ", firstN,
				      " and second ", secondN, " objects" );

		    this.getObjectAndPrint( buf,
					    offset,
					    firstN,
					    totalSize,
					    failedSize,
					    level + 1 );

		    this.getObjectAndPrint( buf,
					    offset + firstN,
					    secondN,
					    totalSize,
					    failedSize,
					    level + 1 );

		    /*
		    // old code, get one by one
		    for ( long i = offset ; i < n ; ++ i )
		    {
			this.getObjectAndPrint( buf,
						i,
						1,
						totalSize,
						failedSize,
						level + 1 );
		    }
		    */
		}
		catch( StorageException storageException )
		{
		    SAXParseException se = new SAXParseException
					   ( "Can't retrieve objects: "
					     + storageException.getMessage() ,
					     super.getLocator() );
		    se.setStackTrace( storageException.getStackTrace() );

		    this.throwError( se );
		}
	    }
	}
	finally
	{
	    try
	    {
		buf.writeTo( this.getOutputStream() );
		buf.close();
		this.getOutputStream().flush();
	    }
	    catch ( IOException e )
	    {
		throw new SAXException( e );
	    }

	    try
	    {
		if ( objectSet != null )
		{
		    objectSet.close();
		}
	    }
	    catch( TypeException  e )
	    {
		this.throwError( new SAXParseException
				 ( e.getMessage() ,
				   super.getLocator() ,
				   e ) );
	    }
	}
}

    void printObjectSet( TypedInstanceSet objectSet,
			 ByteArrayOutputStream buf ) throws SAXException
    {
	try
	{
	    TypedInstance  obj = null;
	    while ( (obj = objectSet.getNextTypedInstance()) != null )
	    {
		Node objElement;

		try
		{
			objElement = obj.convertToXMLElement
					 ( this.specifiedElement ,
					   this.storage ,
					   this.transformationContext );
		}
		catch( NoNeedToConvertTypeException e )
		{
		    LogWriter.qwrite("DEBUG", "skip: " , e.getMessage() );

		    continue;
		}

		/*
		 * 精密チェック
		 */
		if(Itk.useDetailedCheck)
		{
		    try
		    {
			boolean check = this.filterHandler
					    .detailedCheck(objElement) ;

			// 詳細 check に通らなければスキップ
			if(! check)
			{
			    LogWriter.qwrite("DEBUG", "skip " , objElement ,
						      "by detailed check");
			    continue ;
			}
		    }
		    catch (XMLParseErrorException ex)
		    {
			SAXParseException se =
			    new SAXParseException(ex.getMessage(),
						  super.getLocator(), ex) ;
			se.setStackTrace(ex.getStackTrace()) ;
			this.throwError(se) ;
		    }
		}

		buf.write( "    <gml:featureMember>\n".getBytes() );

		try
		{
		    XMLFormatConverter.printElement( objElement, buf );
		}
		catch( TransformerException  te )
		{
		    this.throwError(new SAXParseException
				    ( te.getMessage() ,
				      super.getLocator() , te ) );
		}

		buf.write( "    </gml:featureMember>\n".getBytes() );

		if ( buf.size() >= OUTPUT_BUFFER_SIZE )
		{
		    // send all buffer data
		    buf.writeTo( this.getOutputStream() );
		    buf.reset();

		    /*
		    // send partial buffer data
		    // slower than sending all data

		    byte[] data = buf.toByteArray();

		    this.getOutputStream().write( data ,
						  0 , OUTPUT_BUFFER_SIZE );
		    ByteArrayOutputStream newBuf = new ByteArrayOutputStream();
		    newBuf.write( data ,
				  buf.size() - OUTPUT_BUFFER_SIZE ,
				  OUTPUT_BUFFER_SIZE );
		    buf.close();
		    buf = newBuf;
		    */

		    this.getOutputStream().flush();
		}
	    }
	}
	catch( TypeException  e )
	{
	    this.throwError( new SAXParseException
			     ( e.getMessage() ,
			       super.getLocator() ,
			       e ) );
	}
	catch( java.io.IOException  e )
	{
	    throw new SAXException( e );
	}
    }


    /* !!! [06/08/22 18:56 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * count mode の際のoutput
     */
    public void constructResultCount() throws SAXException
    {
	try
	{
	    constructResponseInfo(false) ;

	    long count = storage.getCountOfTypedInstanceSet
			 ( this.specifiedElement ,
			   this.filterHandler.getSQLExpression(),
			   this.maxFeatures );

	    ResponseInfo responseInfo = this.getResponse() ;

	    ElementUtil.genElementSimple
		(Lexicon.MispFeatureCount,
		 Long.toString(count),
		 responseInfo.document, responseInfo.leaf, true) ;

	}
	catch(StorageException ex)
	{
	    this.throwError( new SAXParseException("Can't retrieve objects "
						   + ex.getMessage(),
						   super.getLocator(),
						   ex) ) ;
	}
    }

    /* !!! [06/08/22 18:56 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * boundedby mode の際のoutput
     */
    public void constructResultBoundedBy() throws SAXException
    {
	try
	{
	    constructResponseInfo(false) ;

	    DrmEnvelope env =
		storage.getEnvelopeOfTypedInstanceSet
		(this.specifiedElement,
		 this.propertyColName,
		 this.filterHandler.getSQLExpression(),
		 this.maxFeatures) ;

	    ResponseInfo responseInfo = this.getResponse() ;

	    Element boundedBy =
		ElementUtil
		.genElementSimple(Lexicon.MispBoundedBy,
				  null,
				  responseInfo.document, responseInfo.leaf,
				  true) ;
	    if(env == null)
	    {
		ElementUtil.genElementSimple
		    (Lexicon.GmlNull,
		     Lexicon.GmlNull_Inapplicable.localname,
		     responseInfo.document, boundedBy, true) ;
	    }
	    else
	    {
		Element envElm =
		    ElementUtil
		    .genElementSimple(Lexicon.GmlEnvelope,
				      null,
				      responseInfo.document, boundedBy, true) ;
		String coord =
		    Double.toString(env.getPointNE().getX()) + ","
		    + Double.toString(env.getPointNE().getY()) + " "
		    + Double.toString(env.getPointSW().getX()) + ","
		    + Double.toString(env.getPointSW().getY()) ;
		ElementUtil
		    .genElementSimple(Lexicon.GmlCoordinates,
				      coord,
				      responseInfo.document, envElm, true) ;
	    }
	}
	catch(StorageException ex)
	{
	    this.throwError( new SAXParseException("Can't retrieve objects "
						   + ex.getMessage(),
						   super.getLocator(),
						   ex) ) ;
	}
    }


    //------------------------------------------------------------
    /* !!! [06/08/19 03:27 I.Noda] !!!
     * construct ResponseInfo
    */
    private void constructResponseInfo(boolean addCollectionP)
	throws SAXException
    {
	try
	{
	    ResponseInfo responseInfo = this.getResponse() ;
	    Element response =
		ElementUtil.genElementSimple
		(Lexicon.MispGetFeatureResponse,
		 null, responseInfo.document, responseInfo.leaf, true) ;
	    responseInfo.setResponseToLeaf(response) ;
	    NameSpace.GML.addNsDeclToIfNeeded(response) ;

	    responseInfo.addResponseStatusToLeaf
		(storage.getMostRecentTransactionURI(),
		 this.startTime, this.endTime) ;

	    if(addCollectionP)
	    {
		Element collection =
		    ElementUtil.genElementSimple
		    (Lexicon.MispFeatureCollection, null,
		     responseInfo.document, response, true) ;
		responseInfo.setResponseToLeaf(collection) ;
	    }
	}
	catch (StorageException ex)
	{
	    throw new SAXException(ex) ;
	}
    }


    //------------------------------------------------------------
	public	void	notifyError( SAXParseException  e ) throws SAXException
	{
		this.throwError( e );
	}

	public	void	throwError( SAXParseException  e ) throws SAXException
	{
		Document	doc;

		try
		{
			XMLFormatConverter
				.print( new SOAPFaultDocumentBuilder(e)
							      .newDocument() ,
					this.getOutputStream() );
		}
		catch( ParserConfigurationException  pe )
		{
			throw new SAXException( pe );
		}
		catch( TransformerException  te )
		{
			throw new SAXException( te );
		}

		throw e;
	}
}
