Package com.esri.gpt.server.csw.provider.local

Source Code of com.esri.gpt.server.csw.provider.local.QueryFilterParser

/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.esri.gpt.server.csw.provider.local;
import com.esri.gpt.catalog.discovery.Discoverable;
import com.esri.gpt.catalog.discovery.DiscoveryClause;
import com.esri.gpt.catalog.discovery.DiscoveryFilter;
import com.esri.gpt.catalog.discovery.DiscoveryQuery;
import com.esri.gpt.catalog.discovery.LogicalClause;
import com.esri.gpt.catalog.discovery.PropertyClause;
import com.esri.gpt.catalog.discovery.PropertyMeaningType;
import com.esri.gpt.catalog.discovery.SpatialClause;
import com.esri.gpt.catalog.discovery.LogicalClause.LogicalAnd;
import com.esri.gpt.framework.geometry.Envelope;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.server.csw.provider.components.CapabilityOptions;
import com.esri.gpt.server.csw.provider.components.CswConstants;
import com.esri.gpt.server.csw.provider.components.CswNamespaces;
import com.esri.gpt.server.csw.provider.components.IFilterParser;
import com.esri.gpt.server.csw.provider.components.OperationContext;
import com.esri.gpt.server.csw.provider.components.OwsException;
import com.esri.gpt.server.csw.provider.components.QueryOptions;

import java.util.ArrayList;
import java.util.logging.Logger;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* Parses the ogc:Filter portion of a CSW query request (GetRecords).
*/
public class QueryFilterParser extends DiscoveryAdapter implements IFilterParser {
 
  /** class variables ========================================================= */
 
  /** The Logger. */
  private static Logger LOGGER = Logger.getLogger(QueryFilterParser.class.getName());
 
  /** instance variables ====================================================== */
  private OperationContext opContext;
 
  /** constructors ============================================================ */
 
  /** Default constructor */
  public QueryFilterParser(OperationContext context) {
    super(context);
    this.opContext = context;
  }
 
  /** methods ================================================================= */
 
  /**
   * Parses the ogc:Filter node for an XML based request.
   * @param context the operation context
   * @param filterNode the ogc:Filter node
   * @param xpath an XPath to enable queries (properly configured with name spaces)
   * @throws OwsException if validation fails
   * @throws XPathExpressionException if an XPath related exception occurs
   */
  public void parseFilter(OperationContext context, Node filterNode, XPath xpath)
    throws OwsException, XPathExpressionException {
   
    // initialize the discovery filter
    QueryOptions qOptions = context.getRequestOptions().getQueryOptions();
    DiscoveryQuery query = this.getDiscoveryContext().getDiscoveryQuery();
    DiscoveryFilter filter = query.getFilter();
    filter.setRootClause(null);
    filter.setStartRecord(qOptions.getStartRecord());
    if (qOptions.getResultType().equals(CswConstants.ResultType_Hits)) {
      filter.setMaxRecords(0);
    } else {
      filter.setMaxRecords(qOptions.getMaxRecords());
    }
       
    // parse the ogc:Filter
    if (filterNode != null) {
      LOGGER.finer("Parsing ogc:Filter....");
      filter.setRootClause(new LogicalAnd());
      LogicalClause rootClause = filter.getRootClause();
      this.parseLogicalClause(filterNode,xpath,rootClause);
      if (rootClause.getClauses().size() == 1) {
        DiscoveryClause onlySubClause = rootClause.getClauses().get(0);
        if (onlySubClause instanceof LogicalClause) {
          LogicalClause onlyLogicalSubClause = (LogicalClause) onlySubClause;
          filter.setRootClause(onlyLogicalSubClause);
        }
      }
    }
   
  }
 
  /**
   * Parses a parent node for logical, property comparison and spatial sub-clauses.
   * <br/>Any logical clauses encountered will be recursively parsed.
   * @param parent the parent node from which sub-clauses will read
   * @param xpath an XPath to enable queries (properly configured with name spaces)
   * @param logicalClause the active logical clause to which sub-clauses will be added
   * @throws OwsException if validation fails
   * @throws XPathExpressionException if an XPath related exception occurs
   */
  protected void parseLogicalClause(Node parent, XPath xpath, LogicalClause logicalClause)
    throws OwsException, XPathExpressionException {
    NodeList children = parent.getChildNodes();
    if (children == null) return;

    int n = children.getLength();
    for (int i=0; i<n; i++) {
      Node subNode = children.item(i);
      String uri = Val.chkStr(subNode.getNamespaceURI());
      if (uri.length() > 0) {
        String localName = Val.chkStr(subNode.getLocalName());
        LOGGER.finer("Parsing node ("+uri+")"+localName);
        if (uri.equals(CswNamespaces.URI_OGC)) {

          // logical clauses - add then recurse
          if (localName.equals("And")) {
            LogicalClause logical = new LogicalClause.LogicalAnd();
            logicalClause.getClauses().add(logical);
            this.parseLogicalClause(subNode,xpath,logical);

          } else if (localName.equals("Or")) {
            LogicalClause logical = new LogicalClause.LogicalOr();
            logicalClause.getClauses().add(logical);
            this.parseLogicalClause(subNode,xpath,logical);

          } else if (localName.equals("Not")) {
            LogicalClause logical = new LogicalClause.LogicalNot();
            logicalClause.getClauses().add(logical);
            this.parseLogicalClause(subNode,xpath,logical);

          // property comparison clauses
          } else if (localName.equals("PropertyIsBetween")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
               new PropertyClause.PropertyIsBetween());

          } else if (localName.equals("PropertyIsEqualTo")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsEqualTo());

          } else if (localName.equals("PropertyIsGreaterThan")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsGreaterThan());

          } else if (localName.equals("PropertyIsGreaterThanOrEqualTo")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsGreaterThanOrEqualTo());

          } else if (localName.equals("PropertyIsLessThan")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsLessThan());

          } else if (localName.equals("PropertyIsLessThanOrEqualTo")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsLessThanOrEqualTo());

          } else if (localName.equals("PropertyIsLike")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsLike());

          } else if (localName.equals("PropertyIsNotEqualTo")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsNotEqualTo());

          } else if (localName.equals("PropertyIsNull")) {
            this.parsePropertyClause(subNode,xpath,logicalClause,
                new PropertyClause.PropertyIsNull());

            // spatial clauses
          } else if (localName.equals("BBOX")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryBBOXIntersects());

          } else if (localName.equals("Contains")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryContains());

          } else if (localName.equals("Disjoint")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryIsDisjointTo());

          } else if (localName.equals("Equals")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryIsEqualTo());

          } else if (localName.equals("Intersects")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryIntersects());

          } else if (localName.equals("Overlaps")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryOverlaps());

          } else if (localName.equals("Within")) {
            this.parseSpatialClause(subNode,xpath,logicalClause,
                new SpatialClause.GeometryIsWithin());

          } else if (localName.equals("Beyond"|| localName.equals("Crosses") ||
                     localName.equals("DWithin") || localName.equals("Touches")) {
            String locator = subNode.getLocalName();
            String msg = "Spatial operator "+subNode.getNodeName()+" is not supported.";
            throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,locator,msg);
           
          } else {
           
            String locator = subNode.getLocalName();
            String msg = "Operator "+subNode.getNodeName()+" is not supported.";
            throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,locator,msg);
          }
        }
      }
    }
  }
 
  /**
   * Parses the property and literal elements underlying a comparison operator.
   * <br/>If the resulting property clause is valid, it will be added to the
   * clause collection of the supplied logical clause.
   * @param parent the parent node (the node of the property operator)
   * @param xpath an XPath to enable queries (properly configured with name spaces)
   * @param logicalClause the logical clause which will contain the comparison clause
   * @param propertyClause the populate
   * @throws OwsException OwsException if validation fails
   * @throws XPathExpressionException if an XPath related exception occurs
   */
  protected void parsePropertyClause(Node parent,
                                     XPath xpath,
                                     LogicalClause logicalClause,
                                     PropertyClause propertyClause)
    throws OwsException, XPathExpressionException {

    // TODO what if this is a Geometry property, also need to validate literals
   
    // initialize
    LOGGER.finer("Parsing property clause for "+parent.getNodeName());
    String sErr = parent.getNodeName();
    Discoverable discoverable = this.parsePropertyName(parent,xpath);
    propertyClause.setTarget(discoverable);
   
    // anytext queries are only supported for PropertyIsLike
    if (discoverable.getMeaning().getMeaningType().equals(PropertyMeaningType.ANYTEXT)) {
      if (!(propertyClause instanceof PropertyClause.PropertyIsLike)) {
        String sPropName = "AnyText";
        Node ndPropName = (Node)xpath.evaluate("ogc:PropertyName",parent,XPathConstants.NODE);
        if (ndPropName != null) {
          sPropName = Val.chkStr(ndPropName.getTextContent());
        }
        String msg = sErr+" - PropertyIsLike is the only supported operand for PropertyName: "+sPropName;
        throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,"PropertyName",msg);
      }
    }

    // between comparison - set lower and upper boundaries
    if (propertyClause instanceof PropertyClause.PropertyIsBetween) {
      PropertyClause.PropertyIsBetween between;
      between = (PropertyClause.PropertyIsBetween)propertyClause;
      Node ndLower = (Node)xpath.evaluate("ogc:LowerBoundary",parent,XPathConstants.NODE);
      Node ndUpper = (Node)xpath.evaluate("ogc:UpperBoundary",parent,XPathConstants.NODE);
      String sLower = "";
      String sUpper = "";
      if ((ndLower == null) && (ndUpper == null)) {
        String msg = sErr+" - a LowerBoundary or UpperBoundary was not found.";
        throw new OwsException(OwsException.OWSCODE_MissingParameterValue,"PropertyIsBetween",msg);
      }
      if (ndLower != null) {
        sLower = ndLower.getTextContent();
        between.setLowerBoundary(sLower);
        // TODO validate content
      }
      if (ndUpper != null) {
        sUpper = ndUpper.getTextContent();
        between.setUpperBoundary(sUpper);
        // TODO validate content
      }
      if ((sLower == null) || (sLower.length() == 0)) {
        if ((sUpper == null) || (sUpper.length() == 0)) {
          String msg = sErr+" - the LowerBoundary and UpperBoundary are empty.";
          throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,"PropertyIsBetween",msg);
        }
      }

      // null check - no literal required
    } else if (propertyClause instanceof PropertyClause.PropertyIsNull) {

      // non-range clauses
    } else {
      Node ndLiteral = (Node)xpath.evaluate("ogc:Literal", parent,XPathConstants.NODE);
      if (ndLiteral == null) {
        String msg = sErr+" - an ogc:Literal was not found.";
        throw new OwsException(OwsException.OWSCODE_MissingParameterValue,"Literal",msg);
      }
      String sLiteral = ndLiteral.getTextContent();
      propertyClause.setLiteral(sLiteral);
      // TODO validate content
      if ((sLiteral == null) || (sLiteral.length() == 0)) {
        String msg = sErr+".ogc:Literal - the supplied literal was empty.";
        throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,"Literal",msg);
      }

      // set like comparison attributes
      if (propertyClause instanceof PropertyClause.PropertyIsLike) {
        PropertyClause.PropertyIsLike like;
        like = (PropertyClause.PropertyIsLike) propertyClause;
        like.setEscapeChar(xpath.evaluate("@escapeChar", parent));
        like.setSingleChar(xpath.evaluate("@singleChar", parent));
        like.setWildCard(xpath.evaluate("@wildCard", parent));
      }
     
      // initialize the language code, (INSPIRE requirement but generally applicable)
      // INSPIRE requirement, specify the language for exceptions in this manner
      // doesn't seem to be a good approach
      if ((this.opContext != null) && (propertyClause instanceof PropertyClause.PropertyIsEqualTo)) {
        if (discoverable.getMeaning().getName().equals("apiso.Language")) {
          if ((sLiteral != null) && (sLiteral.length() > 0)) {
            CapabilityOptions cOptions = this.opContext.getRequestOptions().getCapabilityOptions();
            if (cOptions.getLanguageCode() == null) {
              cOptions.setLanguageCode(sLiteral);
            }
          }
        }
      }
     
    }

    // add the clause
    logicalClause.getClauses().add(propertyClause);
  }
   
  /**
   * Parses the spatial operand underlying a spatial operator.
   * <br/>The spatial operand is used to populate the bounding envelope
   * associated with the supplied spatial clause.
   * <br/>If the envelope is valid, the spatial clause will be added to the
   * clause collection of the supplied logical clause.
   * @param parent the parent node (specifies the spatial operator)
   * @param xpath an XPath to enable queries (properly configured with namespaces)
   * @param logicalClause the logical clause which will contain the spatial clause
   * @param spatialClause the spatial clause to populate
   * @throws OwsException OwsException if validation fails
   * @throws XPathExpressionException if an XPath related exception occurs
   */
  protected void parseSpatialClause(Node parent,
                                    XPath xpath,
                                    LogicalClause logicalClause,
                                    SpatialClause spatialClause)
    throws OwsException, XPathExpressionException {

    // initialize
    LOGGER.finer("Parsing spatial clause for "+parent.getNodeName());
    String sErr = parent.getNodeName();
    Envelope envelope = spatialClause.getBoundingEnvelope();
    // TODO ensure that the discoverable is a geometry
    Discoverable discoverable = this.parsePropertyName(parent,xpath);
    spatialClause.setTarget(discoverable);

    /*
     *
     * gml:Envelope <attribute name="gid" type="ID" use="optional"/> <attribute
     * name="srsName" type="anyURI" use="optional"/>
     *
     * <element name="lowerCorner" type="gml:DirectPositionType"/> <attribute
     * name="gid" type="ID" use="optional"/> <attribute name="srsName"
     * type="anyURI" use="optional"/> space separated x y <element
     * name="upperCorner" type="gml:DirectPositionType"/> <attribute name="gid"
     * type="ID" use="optional"/> <attribute name="srsName" type="anyURI"
     * use="optional"/> space separated x y
     *
     * <element ref="gml:pos" minOccurs="2" maxOccurs="2"> both are
     * type="gml:DirectPositionType" (deprecated)
     *
     * <element ref="gml:coord" minOccurs="2" maxOccurs="2"/> <element X> <element
     * Y>
     *
     * <element ref="gml:coordinates"/> <attribute name="decimal" type="string"
     * use="optional" default="."/> <attribute name="cs" type="string"
     * use="optional" default=","/> <attribute name="ts" type="string"
     * use="optional" default=" "/>
     *
     * gml:Box <attribute name="gid" type="ID" use="optional"/> <attribute
     * name="srsName" type="anyURI" use="optional"/> <element ref="gml:coord"
     * minOccurs="2" maxOccurs="2"/> <element X> <element Y> <element
     * ref="gml:coordinates"/> <attribute name="decimal" type="string"
     * use="optional" default="."/> <attribute name="cs" type="string"
     * use="optional" default=","/> <attribute name="ts" type="string"
     * use="optional" default=" "/>
     *
     * gml:Point <attribute name="gid" type="ID" use="optional"/> <attribute
     * name="srsName" type="anyURI" use="optional"/> <element ref="gml:coord"/>
     * <element X> <element Y> <element ref="gml:coordinates"/> <attribute
     * name="decimal" type="string" use="optional" default="."/> <attribute
     * name="cs" type="string" use="optional" default=","/> <attribute name="ts"
     * type="string" use="optional" default=" "/>
     *
     * gml:LineString <attribute name="gid" type="ID" use="optional"/> <attribute
     * name="srsName" type="anyURI" use="optional"/> <element ref="gml:coord"
     * minOccurs="2" maxOccurs="unbounded"/> <element X> <element Y> <element
     * ref="gml:coordinates"/> <attribute name="decimal" type="string"
     * use="optional" default="."/> <attribute name="cs" type="string"
     * use="optional" default=","/> <attribute name="ts" type="string"
     * use="optional" default=" "/>
     */

    // determine the envelope
    Node ndSpatial = (Node)xpath.evaluate("gml:Envelope",parent,XPathConstants.NODE);
    if (ndSpatial == null) {
      ndSpatial = (Node)xpath.evaluate("gml:Box",parent,XPathConstants.NODE);
      if (ndSpatial == null) {
        ndSpatial = (Node)xpath.evaluate("gml:Point",parent,XPathConstants.NODE);
      }
    }
    if (ndSpatial != null) {
      LOGGER.finest("Parsing "+ndSpatial.getNodeName()+"...");
      sErr += "."+ndSpatial.getNodeName();
      spatialClause.setSrsName(xpath.evaluate("@srsName",ndSpatial));
      Node ndLower = (Node)xpath.evaluate("gml:lowerCorner",ndSpatial,XPathConstants.NODE);
      Node ndUpper = (Node)xpath.evaluate("gml:upperCorner",ndSpatial,XPathConstants.NODE);
      Node ndCoords = (Node)xpath.evaluate("gml:coordinates",ndSpatial,XPathConstants.NODE);
      NodeList nlCoord = (NodeList)xpath.evaluate("gml:coord",ndSpatial,XPathConstants.NODESET);

      // handle a lower and upper boundary
      if ((ndLower != null) && (ndUpper != null)) {
        String sLower = Val.chkStr(ndLower.getTextContent());
        String sUpper = Val.chkStr(ndUpper.getTextContent());
        LOGGER.finest("Parsing gml:lowerCorner=\""+sLower+"\" gml:upperCorner=\""+sUpper+"\"");
        String[] xyLower = Val.tokenize(sLower," ");
        String[] xyUpper = Val.tokenize(sUpper," ");
        if ((xyLower.length == 2) && (xyUpper.length == 2)) {
          envelope.put(xyLower[0],xyLower[1],xyUpper[0],xyUpper[1]);
        }

        // handle a delimited list of coordinates
      } else if (ndCoords != null) {

        // separators: decimal, ts (between coordinate pairs), cs (between x/y values)
        char cDecSep = '.';
        String decSep = xpath.evaluate("@decimal",ndCoords);
        if ((decSep != null) && (decSep.length() == 1)) cDecSep = decSep.charAt(0);
        String tsSep = xpath.evaluate("@ts",ndCoords);
        if ((tsSep == null) || (tsSep.length() != 1)) tsSep = " ";
        String csSep = xpath.evaluate("@cs", ndCoords);
        if ((csSep == null) || (csSep.length() != 1)) csSep = ",";
        String sepMsg = "decimal=\""+cDecSep+"\" ts=\""+tsSep+"\" cs=\""+csSep+"\"";

        // collect individual string values
        String sCordinates = Val.chkStr(ndCoords.getTextContent());
        LOGGER.finest("Parsing gml:coordinates "+sepMsg+" coordinates=\""+sCordinates+"\"");
        ArrayList<String> values = new ArrayList<String>();
        String[] tsValues = Val.tokenize(sCordinates,tsSep);
        for (String tsValue : tsValues) {
          String[] csValues = Val.tokenize(tsValue,csSep);
          for (String csValue: csValues) {
            LOGGER.finer("Adding coordinate value: "+csValue);
            values.add(csValue.replace(cDecSep,'.'));
          }
        }

        // determine the minimum and maximum envelope values from the coordinate list
        for (int i=0; i<values.size(); i=i+2) {
          try {
            LOGGER.finest("Handling coordinate: "+values.get(i)+" "+values.get(i + 1));
            double x = Double.parseDouble(values.get(i));
            double y = Double.parseDouble(values.get(i+1));
            envelope.merge(new Envelope(x,y,x,y));
          } catch (NumberFormatException e) {
            LOGGER.warning(e.getMessage());
          }
        }

      // handle a collection of coordinate elements
      } else if (nlCoord.getLength() > 0) {
        LOGGER.finest("Parsing gml:coord elements...");
        for (int i=0; i< nlCoord.getLength(); i++) {
          Node ndX = (Node)xpath.evaluate("gml:X",nlCoord.item(i),XPathConstants.NODE);
          Node ndY = (Node)xpath.evaluate("gml:Y",nlCoord.item(i),XPathConstants.NODE);
          if ((ndX != null) && (ndY != null)) {
            try {
              LOGGER.finest("Handling coordinate: "+ndX.getTextContent()+" "+ndY.getTextContent());
              double x = Double.parseDouble(ndX.getTextContent());
              double y = Double.parseDouble(ndY.getTextContent());
              envelope.merge(new Envelope(x,y,x,y));
            } catch (NumberFormatException e) {
              LOGGER.warning(e.getMessage());
            }
          }
        }
      }

    }

    // add the clause if the envelope is not empty
    if (!envelope.isEmpty()) {
      logicalClause.getClauses().add(spatialClause);
    } else {
      String msg = sErr+" - the geometry of the spatial operand was not valid.";
      throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,parent.getLocalName(),msg);
    }
  }
}
TOP

Related Classes of com.esri.gpt.server.csw.provider.local.QueryFilterParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.