private void addRelationallyMappedMultiProperty(
     ParsedPropertyReplacement replacement,
     Mapping mapping,
     FeatureTypeMapping ftMapping,
     List<ResourceId> list)
     throws FilterEvaluationException, FeatureStoreException, SQLException {
   UpdateAction action = replacement.getUpdateAction();
   if (action == null) {
     action = UpdateAction.INSERT_AFTER;
   }
   switch (action) {
     case INSERT_BEFORE:
     case REMOVE:
     case REPLACE:
       LOG.warn(
           "Updating of multi properties is currently only supported for 'insertAfter' update action. Omitting.");
       break;
     case INSERT_AFTER:
       break;
     default:
       break;
   }
   InsertRowManager mgr = new InsertRowManager(fs, conn, null);
   List<Property> props = Collections.singletonList(replacement.getNewValue());
   for (ResourceId id : list) {
     IdAnalysis analysis = schema.analyzeId(id.getRid());
     FeatureType featureType = schema.getFeatureType(ftMapping.getFeatureType());
     Feature f = featureType.newFeature(id.getRid(), props, null);
     mgr.updateFeature(f, ftMapping, analysis.getIdKernels(), mapping, replacement);
   }
 }
 private void appendFtInfo(FeatureType ft, FeatureStore store, StringBuffer sb, String indent)
     throws IOException {
   if (ft instanceof FeatureCollectionType) {
     return;
   }
   if (ft.isAbstract()) {
     sb.append(
         indent
             + "- <i>"
             + ft.getName().getPrefix()
             + ":"
             + ft.getName().getLocalPart()
             + " (abstract)</i><br/>");
   } else {
     if (store.isMapped(ft.getName())) {
       Query query = new Query(ft.getName(), null, 0, -1, -1);
       int numInstances = -1;
       try {
         numInstances = store.queryHits(query);
       } catch (Exception e) {
         e.printStackTrace();
       }
       sb.append(
           indent
               + "- "
               + ft.getName().getPrefix()
               + ":"
               + ft.getName().getLocalPart()
               + " ("
               + numInstances
               + " instances)<br/>");
     } else {
       sb.append(
           indent
               + "- "
               + ft.getName().getPrefix()
               + ":"
               + ft.getName().getLocalPart()
               + " (not mapped)<br/>");
     }
   }
   FeatureType[] fts = ft.getSchema().getDirectSubtypes(ft);
   Arrays.sort(
       fts,
       new Comparator<FeatureType>() {
         public int compare(FeatureType a, FeatureType b) {
           int order = a.getName().getNamespaceURI().compareTo(b.getName().getNamespaceURI());
           if (order == 0) {
             order = a.getName().getLocalPart().compareTo(b.getName().getLocalPart());
           }
           return order;
         }
       });
   for (FeatureType childType : fts) {
     appendFtInfo(childType, store, sb, indent + "&nbsp;&nbsp;");
   }
 }
 public List<NamespaceBinding> getNamespaces() {
   Set<NamespaceBinding> namespaces = new TreeSet<NamespaceBinding>();
   FeatureStore fs = getWorkspace().getResource(FeatureStoreProvider.class, getId());
   if (fs == null) {
     return Collections.emptyList();
   }
   AppSchema schema = fs.getSchema();
   for (FeatureType ft : schema.getFeatureTypes()) {
     String prefix = ft.getName().getPrefix();
     String ns = ft.getName().getNamespaceURI();
     namespaces.add(new NamespaceBinding(prefix, ns));
   }
   return new ArrayList<NamespaceBinding>(namespaces);
 }
  @Test
  public void testCollectAndSortFeatureTypesToExport_AllFeatureTypes() {
    List<FeatureType> featureTypes = featureTypes();
    StoredQueryHandler storedQueryHandler =
        new StoredQueryHandler(mockWFS(featureTypes), new ArrayList<URL>());

    List<QName> configuredFeatureTypeNames = Collections.emptyList();
    List<QName> featureTypeNamesToExport =
        storedQueryHandler.collectAndSortFeatureTypesToExport(configuredFeatureTypeNames);

    assertThat(featureTypeNamesToExport.size(), is(featureTypes.size()));
    for (FeatureType featureType : featureTypes) {
      assertThat(featureTypeNamesToExport, hasItems(featureType.getName()));
    }
  }
  @Override
  public int performDelete(IdFilter filter, Lock lock) throws FeatureStoreException {
    int deleted = 0;
    if (blobMapping != null) {
      deleted = performDeleteBlob(filter, lock);
    } else {
      deleted = performDeleteRelational(filter, lock);
    }

    // TODO improve this
    for (FeatureType ft : schema.getFeatureTypes(null, false, false)) {
      bboxTracker.delete(ft.getName());
    }

    return deleted;
  }
  @Test
  public void testParsingCite110SF1()
      throws ClassCastException, ClassNotFoundException, InstantiationException,
          IllegalAccessException {

    String schemaURL =
        this.getClass().getResource("../testdata/schema/cite/cite-gmlsf1.xsd").toString();
    ApplicationSchemaXSDDecoder adapter =
        new ApplicationSchemaXSDDecoder(GMLVersion.GML_31, null, schemaURL);
    FeatureType[] fts = adapter.extractFeatureTypeSchema().getFeatureTypes();
    Assert.assertEquals(4, fts.length);
    for (FeatureType ft : fts) {
      System.out.println("\nFt: " + ft.getName());
      for (PropertyType pt : ft.getPropertyDeclarations()) {
        System.out.println(pt);
      }
    }
  }
Exemple #7
0
  @Override
  public Envelope getBbox() {
    if (super.getBbox() != null) {
      return super.getBbox();
    }

    if (datastore == null || !datastore.isAvailable()) {
      return null;
    }

    // always use up-to-date envelope
    Envelope bbox = null;
    AppSchema schema = datastore.getSchema();

    List<Style> styles = service.registry.getAll(getName());
    List<QName> ftNames = new LinkedList<QName>();
    for (Style s : styles) {
      if (s.getFeatureType() != null) {
        ftNames.add(s.getFeatureType());
      }
    }
    if (ftNames.isEmpty()) {
      for (FeatureType t : schema.getFeatureTypes(null, false, false)) {
        ftNames.add(t.getName());
      }
    }

    for (QName t : ftNames) {
      Envelope thisBox = null;
      try {
        thisBox = datastore.getEnvelope(t);
      } catch (FeatureStoreException e) {
        LOG.error("Error retrieving envelope from FeatureStore: " + e.getMessage(), e);
      }
      if (bbox == null) {
        bbox = thisBox;
      } else {
        if (thisBox != null) {
          bbox = bbox.merge(thisBox);
        }
      }
    }
    return bbox;
  }
  @Test
  public void testParsingCityGML()
      throws ClassCastException, ClassNotFoundException, InstantiationException,
          IllegalAccessException {

    String schemaURL = "http://schemas.opengis.net/citygml/profiles/base/1.0/CityGML.xsd";
    ApplicationSchemaXSDDecoder adapter =
        new ApplicationSchemaXSDDecoder(GMLVersion.GML_31, null, schemaURL);
    ApplicationSchema schema = adapter.extractFeatureTypeSchema();
    FeatureType[] fts = schema.getFeatureTypes();
    Assert.assertEquals(54, fts.length);

    FeatureType buildingFt =
        schema.getFeatureType(
            QName.valueOf("{http://www.opengis.net/citygml/building/1.0}Building"));
    PropertyType<?> pt =
        buildingFt.getPropertyDeclaration(
            QName.valueOf(
                "{http://www.opengis.net/citygml/1.0}_GenericApplicationPropertyOfCityObject"));
    Assert.assertEquals(8, pt.getSubstitutions().length);
  }
 private void appendFcInfo(FeatureType ft, StringBuffer sb, String indent) throws IOException {
   if (ft instanceof FeatureCollectionType) {
     if (ft.isAbstract()) {
       sb.append(
           indent
               + "- <i>"
               + ft.getName().getPrefix()
               + ":"
               + ft.getName().getLocalPart()
               + " (abstract)</i><br/>");
     } else {
       sb.append(
           indent + "- " + ft.getName().getPrefix() + ":" + ft.getName().getLocalPart() + "<br/>");
     }
   }
   FeatureType[] fts = ft.getSchema().getDirectSubtypes(ft);
   Arrays.sort(
       fts,
       new Comparator<FeatureType>() {
         public int compare(FeatureType a, FeatureType b) {
           int order = a.getName().getNamespaceURI().compareTo(b.getName().getNamespaceURI());
           if (order == 0) {
             order = a.getName().getLocalPart().compareTo(b.getName().getLocalPart());
           }
           return order;
         }
       });
   for (FeatureType childType : fts) {
     appendFcInfo(childType, sb, indent + "&nbsp;&nbsp;");
   }
 }
  /**
   * Prints out an SQL create script for the relational schema.
   *
   * @param dbSchema optional db schema, can be null
   * @param writer
   */
  public void writeCreateScript(String dbSchema, PrintWriter writer) {

    if (dbSchema == null) {
      dbSchema = "";
    }

    if (dbSchema.length() != 0) {
      writer.println("/* --- BEGIN schema setup --- */");
      writer.println();
      writer.println("CREATE SCHEMA " + dbSchema + ";");
      writer.println("SET search_path TO " + dbSchema + ",public;");
      writer.println();
      writer.println("/* --- END schema setup --- */");
      writer.println();
    }

    FeatureType[] fts = appSchema.getFeatureTypes();
    Arrays.sort(
        fts,
        new Comparator<FeatureType>() {
          @Override
          public int compare(FeatureType o1, FeatureType o2) {
            return o1.getName().toString().compareTo(o2.getName().toString());
          }
        });

    if (getGlobalHints().isUseObjectLookupTable()) {
      writeCreateGeneral(fts, writer, dbSchema);
    }

    for (FeatureType ft : fts) {
      if (!ft.isAbstract()) {
        writer.println();
        writeCreateFeatureType(ft, writer, dbSchema);
      }
    }

    writer.flush();
  }
Exemple #11
0
  static OperatorFilter buildFilter(
      Operator operator, FeatureType ft, Envelope clickBox, ValueReference geomProp) {
    if (ft == null) {
      if (operator == null) {
        return null;
      }
      return new OperatorFilter(operator);
    }
    LinkedList<Operator> list = new LinkedList<Operator>();

    if (geomProp != null) {
      list.add(new And(new BBOX(geomProp, clickBox), new Intersects(geomProp, clickBox)));
    } else {
      for (PropertyType pt : ft.getPropertyDeclarations()) {
        if (pt instanceof GeometryPropertyType
            && (((GeometryPropertyType) pt).getCoordinateDimension() == DIM_2
                || ((GeometryPropertyType) pt).getCoordinateDimension() == DIM_2_OR_3)) {
          list.add(
              new And(
                  new BBOX(new ValueReference(pt.getName()), clickBox),
                  new Intersects(new ValueReference(pt.getName()), clickBox)));
        }
      }
    }
    if (list.size() > 1) {
      Or or = new Or(list.toArray(new Operator[list.size()]));
      if (operator == null) {
        return new OperatorFilter(or);
      }
      return new OperatorFilter(new And(or, operator));
    }
    if (list.isEmpty()) {
      // obnoxious case where feature has no geometry properties (but features may have extra
      // geometry props)
      list.add(new And(new BBOX(null, clickBox), new Intersects(null, clickBox)));
    }
    if (operator == null) {
      return new OperatorFilter(list.get(0));
    }
    return new OperatorFilter(new And(list.get(0), operator));
  }
  /**
   * Returns the object representation for the given property element.
   *
   * @param xmlStream cursor must point at the <code>START_ELEMENT</code> event of the property,
   *     afterwards points at the next event after the <code>END_ELEMENT</code> of the property
   * @param propDecl property declaration
   * @param crs default SRS for all a descendant geometry properties
   * @param occurence
   * @return object representation for the given property element.
   * @throws XMLParsingException
   * @throws XMLStreamException
   * @throws UnknownCRSException
   */
  public Property<?> parseProperty(
      XMLStreamReaderWrapper xmlStream, PropertyType propDecl, CRS crs, int occurence)
      throws XMLParsingException, XMLStreamException, UnknownCRSException {

    Property<?> property = null;
    QName propName = xmlStream.getName();
    LOG.debug("- parsing property (begin): " + xmlStream.getCurrentEventInfo());
    LOG.debug("- property declaration: " + propDecl);

    CustomPropertyReader<?> parser = ptToParser.get(propDecl);

    if (parser == null) {
      if (propDecl instanceof SimplePropertyType) {
        property =
            createSimpleProperty(
                xmlStream, (SimplePropertyType) propDecl, xmlStream.getElementText().trim());
      } else if (propDecl instanceof GeometryPropertyType) {
        String href = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        if (href != null) {
          // TODO respect geometry type information (Point, Surface, etc.)
          GeometryReference<Geometry> refGeometry = null;
          if (specialResolver != null) {
            refGeometry =
                new GeometryReference<Geometry>(specialResolver, href, xmlStream.getSystemId());
          } else {
            refGeometry = new GeometryReference<Geometry>(idContext, href, xmlStream.getSystemId());
          }
          idContext.addReference(refGeometry);
          property = new GenericProperty<Geometry>(propDecl, propName, refGeometry);
          xmlStream.nextTag();
        } else {
          xmlStream.nextTag();
          Geometry geometry = null;
          geometry = geomReader.parse(xmlStream, crs);
          property = new GenericProperty<Geometry>(propDecl, propName, geometry);
          xmlStream.nextTag();
        }
      } else if (propDecl instanceof FeaturePropertyType) {
        String uri = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        if (uri != null) {
          FeatureReference refFeature = null;
          if (specialResolver != null) {
            refFeature = new FeatureReference(specialResolver, uri, xmlStream.getSystemId());
          } else {
            refFeature = new FeatureReference(idContext, uri, xmlStream.getSystemId());
          }
          idContext.addReference(refFeature);
          property = new GenericProperty<Feature>(propDecl, propName, refFeature);
          xmlStream.nextTag();
        } else {
          // inline feature
          if (xmlStream.nextTag() != START_ELEMENT) {
            String msg = Messages.getMessage("ERROR_INVALID_FEATURE_PROPERTY", propName);
            throw new XMLParsingException(xmlStream, msg);
          }
          // TODO make this check (no constraints on contained feature type) better
          if (((FeaturePropertyType) propDecl).getFTName() != null) {
            FeatureType expectedFt = ((FeaturePropertyType) propDecl).getValueFt();
            FeatureType presentFt = lookupFeatureType(xmlStream, xmlStream.getName());
            if (!schema.isSubType(expectedFt, presentFt)) {
              String msg =
                  Messages.getMessage(
                      "ERROR_PROPERTY_WRONG_FEATURE_TYPE",
                      expectedFt.getName(),
                      propName,
                      presentFt.getName());
              throw new XMLParsingException(xmlStream, msg);
            }
          }
          Feature subFeature = parseFeature(xmlStream, crs);
          property = new GenericProperty<Feature>(propDecl, propName, subFeature);
          xmlStream.skipElement();
        }
      } else if (propDecl instanceof CustomPropertyType) {
        Object value = new GenericCustomPropertyReader().parse(xmlStream);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof EnvelopePropertyType) {
        Envelope env = null;
        xmlStream.nextTag();
        if (xmlStream.getName().equals(new QName(gmlNs, "Null"))) {
          // TODO
          StAXParsingHelper.skipElement(xmlStream);
        } else {
          env = geomReader.parseEnvelope(xmlStream, crs);
          property = new GenericProperty<Object>(propDecl, propName, env);
        }
        xmlStream.nextTag();
      } else if (propDecl instanceof CodePropertyType) {
        String codeSpace = xmlStream.getAttributeValue(null, "codeSpace");
        String code = xmlStream.getElementText().trim();
        Object value = new CodeType(code, codeSpace);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof MeasurePropertyType) {
        String uom = xmlStream.getAttributeValue(null, "uom");
        Object value = new Measure(xmlStream.getElementText(), uom);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof StringOrRefPropertyType) {
        String ref = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        String string = xmlStream.getElementText().trim();
        property = new GenericProperty<Object>(propDecl, propName, new StringOrRef(string, ref));
      }
    } else {
      LOG.trace("************ Parsing property using custom parser.");
      Object value = parser.parse(xmlStream);
      property = new GenericProperty<Object>(propDecl, propName, value);
    }

    LOG.debug(" - parsing property (end): " + xmlStream.getCurrentEventInfo());
    return property;
  }
  /**
   * Returns the object representation for the feature (or feature collection) element event that
   * the cursor of the given <code>XMLStreamReader</code> points at.
   *
   * @param xmlStream cursor must point at the <code>START_ELEMENT</code> event of the feature
   *     element, afterwards points at the next event after the <code>END_ELEMENT</code> event of
   *     the feature element
   * @param crs default CRS for all descendant geometry properties
   * @return object representation for the given feature element
   * @throws XMLStreamException
   * @throws UnknownCRSException
   * @throws XMLParsingException
   */
  public Feature parseFeature(XMLStreamReaderWrapper xmlStream, CRS crs)
      throws XMLStreamException, XMLParsingException, UnknownCRSException {

    if (schema == null) {
      schema = buildApplicationSchema(xmlStream);
    }

    Feature feature = null;
    String fid = parseFeatureId(xmlStream);

    QName featureName = xmlStream.getName();
    FeatureType ft = lookupFeatureType(xmlStream, featureName);

    LOG.debug("- parsing feature, gml:id=" + fid + " (begin): " + xmlStream.getCurrentEventInfo());

    // parse properties
    Iterator<PropertyType<?>> declIter = ft.getPropertyDeclarations(version).iterator();

    PropertyType activeDecl = declIter.next();
    int propOccurences = 0;

    CRS activeCRS = crs;
    List<Property<?>> propertyList = new ArrayList<Property<?>>();

    xmlStream.nextTag();

    while (xmlStream.getEventType() == START_ELEMENT) {

      QName propName = xmlStream.getName();

      LOG.debug("- property '" + propName + "'");

      if (findConcretePropertyType(propName, activeDecl) != null) {
        // current property element is equal to active declaration
        if (activeDecl.getMaxOccurs() != -1 && propOccurences > activeDecl.getMaxOccurs()) {
          String msg =
              Messages.getMessage(
                  "ERROR_PROPERTY_TOO_MANY_OCCURENCES",
                  propName,
                  activeDecl.getMaxOccurs(),
                  ft.getName());
          throw new XMLParsingException(xmlStream, msg);
        }
      } else {
        // current property element is not equal to active declaration
        while (declIter.hasNext() && findConcretePropertyType(propName, activeDecl) == null) {
          if (propOccurences < activeDecl.getMinOccurs()) {
            String msg = null;
            if (activeDecl.getMinOccurs() == 1) {
              msg =
                  Messages.getMessage(
                      "ERROR_PROPERTY_MANDATORY", activeDecl.getName(), ft.getName());
            } else {
              msg =
                  Messages.getMessage(
                      "ERROR_PROPERTY_TOO_FEW_OCCURENCES",
                      activeDecl.getName(),
                      activeDecl.getMinOccurs(),
                      ft.getName());
            }
            throw new XMLParsingException(xmlStream, msg);
          }
          activeDecl = declIter.next();
          propOccurences = 0;
        }
        if (findConcretePropertyType(propName, activeDecl) == null) {
          String msg = Messages.getMessage("ERROR_PROPERTY_UNEXPECTED", propName, ft.getName());
          throw new XMLParsingException(xmlStream, msg);
        }
      }

      Property<?> property =
          parseProperty(
              xmlStream, findConcretePropertyType(propName, activeDecl), activeCRS, propOccurences);
      if (property != null) {
        // if this is the "gml:boundedBy" property, override active CRS (see GML spec. (where???))
        if (StandardGMLFeatureProps.PT_BOUNDED_BY_GML31.getName().equals(activeDecl.getName())) {
          Envelope bbox = (Envelope) property.getValue();
          if (bbox.getCoordinateSystem() != null) {
            activeCRS = bbox.getCoordinateSystem();
            LOG.debug("- crs (from boundedBy): '" + activeCRS + "'");
          }
        }

        propertyList.add(property);
      }
      propOccurences++;
      xmlStream.nextTag();
    }
    LOG.debug(" - parsing feature (end): " + xmlStream.getCurrentEventInfo());

    feature = ft.newFeature(fid, propertyList, version);

    if (fid != null && !"".equals(fid)) {
      if (idContext.getObject(fid) != null) {
        String msg = Messages.getMessage("ERROR_FEATURE_ID_NOT_UNIQUE", fid);
        throw new XMLParsingException(xmlStream, msg);
      }
      idContext.addObject(feature);
    }
    return feature;
  }
  private void writeCreateFeatureType(FeatureType ft, PrintWriter writer, String dbSchema) {

    List<String> additionalCreates = new ArrayList<String>();
    List<Pair<String, String>> comments = new ArrayList<Pair<String, String>>();
    FeatureTypeMapping ftMapping = getFtMapping(ft.getName());
    String tableName = ftMapping.getFeatureTypeHints().getDBTable().toLowerCase();
    int i = 1;

    writer.println("/* --- BEGIN Feature type '" + ft.getName() + "' --- */");
    writer.println();
    writer.println("CREATE TABLE " + tableName + " (");
    writer.print("    id integer PRIMARY KEY REFERENCES GML_OBJECTS");
    comments.add(new Pair<String, String>("id", "Internal id"));
    for (PropertyType<?> pt : ft.getPropertyDeclarations()) {
      PropertyMappingType propMapping = ftMapping.getPropertyHints(pt.getName());
      if (propMapping instanceof SimplePropertyMappingType) {
        SimplePropertyMappingType simplePropMapping = (SimplePropertyMappingType) propMapping;
        if (simplePropMapping.getDBColumn() != null) {
          DBColumn dbColumn = simplePropMapping.getDBColumn();
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase(),
                  "Property " + pt.getName().getLocalPart() + " (simple)"));
          writer.print(",\n    " + dbColumn.getName().toLowerCase() + " " + dbColumn.getSqlType());
          if (pt.getMinOccurs() > 0) {
            writer.print(" NOT NULL");
          }
        } else {
          additionalCreates.add(
              createPropertyTable(tableName, simplePropMapping.getPropertyTable()));
          String comment =
              "COMMENT ON TABLE "
                  + simplePropMapping.getPropertyTable().getTable()
                  + " IS '"
                  + ft.getName().getLocalPart()
                  + ", property "
                  + pt.getName().getLocalPart()
                  + " (simple)';\n";
          additionalCreates.add(comment);
        }
      } else if (propMapping instanceof GeometryPropertyMappingType) {
        GeometryPropertyMappingType geometryPropMapping = (GeometryPropertyMappingType) propMapping;
        if (geometryPropMapping.getGeometryDBColumn() != null) {
          GeometryDBColumn dbColumn = geometryPropMapping.getGeometryDBColumn();
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase(),
                  "Property " + pt.getName().getLocalPart() + " (geometry)"));
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase() + "_ID",
                  "Property " + pt.getName().getLocalPart() + " (geometry id)"));
          writer.print(",\n    " + dbColumn.getName() + "_ID integer REFERENCES gml_objects");
          String create =
              "SELECT ADDGEOMETRYCOLUMN('"
                  + dbSchema
                  + "','"
                  + tableName
                  + "','"
                  + dbColumn.getName().toLowerCase()
                  + "','"
                  + dbColumn.getSrid()
                  + "','"
                  + dbColumn.getSqlType()
                  + "',"
                  + dbColumn.getDimension()
                  + ");\n";
          create +=
              "ALTER TABLE "
                  + tableName
                  + " ADD CONSTRAINT "
                  + tableName
                  + "_geochk"
                  + i
                  + " CHECK (isvalid("
                  + dbColumn.getName().toLowerCase()
                  + "));\n";
          create +=
              "CREATE INDEX "
                  + tableName
                  + "_sidx"
                  + i
                  + " ON "
                  + tableName
                  + " USING GIST ( "
                  + dbColumn.getName().toLowerCase()
                  + " GIST_GEOMETRY_OPS );\n";
          additionalCreates.add(create);
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase() + "_xlink",
                  "Property " + pt.getName().getLocalPart() + " (geometry URI)"));
          writer.print(",\n    " + dbColumn.getName().toLowerCase() + "_xlink text");
          i++;
        } else {
          additionalCreates.add(
              createPropertyTable(
                  tableName, geometryPropMapping.getGeometryPropertyTable(), dbSchema));
          String comment =
              "COMMENT ON TABLE "
                  + geometryPropMapping.getGeometryPropertyTable().getTable()
                  + " IS '"
                  + ft.getName().getLocalPart()
                  + ", property "
                  + pt.getName().getLocalPart()
                  + " (geometry)';\n";
          additionalCreates.add(comment);
        }
      } else if (propMapping instanceof FeaturePropertyMappingType) {
        FeaturePropertyMappingType featurePropMapping = (FeaturePropertyMappingType) propMapping;

        FeatureType targetFt = ((FeaturePropertyType) pt).getValueFt();
        // TODO also non-abstract types may have derived types
        boolean isTargetTypeUnique = targetFt == null ? false : !targetFt.isAbstract();

        if (featurePropMapping.getDBColumn() != null) {
          DBColumn dbColumn = featurePropMapping.getDBColumn();
          String valueFeatureTypeName =
              targetFt == null ? "Any feature" : targetFt.getName().getLocalPart();
          if (!isTargetTypeUnique) {
            valueFeatureTypeName += " (abstract)";
          }
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase(),
                  "Property "
                      + pt.getName().getLocalPart()
                      + " (feature), value feature type: "
                      + valueFeatureTypeName));
          writer.print(
              ",\n    " + dbColumn.getName().toLowerCase() + " integer REFERENCES gml_objects");
          if (!isTargetTypeUnique) {
            writer.print(
                ",\n    "
                    + dbColumn.getName().toLowerCase()
                    + "_ft smallint REFERENCES feature_types");
            comments.add(
                new Pair<String, String>(
                    dbColumn.getName().toLowerCase() + "_ft",
                    "Property " + pt.getName().getLocalPart() + " (feature type id)"));
          }

          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase() + "_xlink",
                  "Property " + pt.getName().getLocalPart() + " (feature URI)"));
          writer.print(",\n    " + dbColumn.getName().toLowerCase() + "_xlink text");
        } else {
          additionalCreates.add(
              createPropertyTable(
                  tableName, featurePropMapping.getFeatureJoinTable(), isTargetTypeUnique));
          String comment =
              "COMMENT ON TABLE "
                  + featurePropMapping.getFeatureJoinTable().getTable()
                  + " IS '"
                  + ft.getName().getLocalPart()
                  + ", property "
                  + pt.getName().getLocalPart()
                  + " (feature)';\n";
          additionalCreates.add(comment);
        }
      } else if (propMapping instanceof MeasurePropertyMappingType) {
        MeasurePropertyMappingType measurePropMapping = (MeasurePropertyMappingType) propMapping;
        if (measurePropMapping.getDBColumn() != null) {
          DBColumn dbColumn = measurePropMapping.getDBColumn();
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase(),
                  "Property " + pt.getName().getLocalPart() + "(measure, text)"));
          writer.print(",\n    " + dbColumn.getName().toLowerCase() + " double precision");
          if (pt.getMinOccurs() > 0) {
            writer.print(" NOT NULL");
          }
          comments.add(
              new Pair<String, String>(
                  dbColumn.getName().toLowerCase(),
                  "Property " + pt.getName().getLocalPart() + "_uom (measure, uom attribute)"));
          writer.print(",\n    " + dbColumn.getName().toLowerCase() + "_uom text");
        } else {
          additionalCreates.add(
              createMeasurePropertyTable(tableName, measurePropMapping.getPropertyTable()));
          String comment =
              "COMMENT ON TABLE "
                  + measurePropMapping.getPropertyTable().getTable()
                  + " IS '"
                  + ft.getName().getLocalPart()
                  + ", property "
                  + pt.getName().getLocalPart()
                  + " (measure)';\n";
          additionalCreates.add(comment);
        }
      } else {
        throw new RuntimeException("Unhandled property mapping: " + propMapping.getClass());
      }
    }
    writer.println("\n);");
    writer.println("COMMENT ON TABLE " + tableName + " IS '" + ft.getName().getLocalPart() + "';");

    // write collected creates for property and join tables
    for (String create : additionalCreates) {
      writer.println();
      writer.print(create);
    }
    for (Pair<String, String> comment : comments) {
      writer.println(
          "COMMENT ON COLUMN " + tableName + "." + comment.first + " IS '" + comment.second + "';");
    }
    writer.println();
    writer.println("/* --- END Feature type '" + ft.getName() + "' --- */");
  }
  private void writeCreateGeneral(FeatureType[] fts, PrintWriter writer, String dbSchema) {

    writer.println("/* --- BEGIN global section --- */");
    writer.println();
    writer.println("CREATE SEQUENCE internal_id_seq;");
    writer.println();
    writer.println("CREATE TABLE feature_types (");
    writer.println("    id smallint PRIMARY KEY,");
    writer.println("    qname text NOT NULL,");
    writer.println("    tablename varchar(32) NOT NULL");
    writer.println(");");
    writer.println(
        "COMMENT ON TABLE feature_types IS 'All concrete feature types and their tables';");
    writer.println();
    writer.println(
        "SELECT ADDGEOMETRYCOLUMN('"
            + dbSchema
            + "', 'feature_types','wgs84bbox','4326','GEOMETRY',2);");
    writer.println(
        "ALTER TABLE feature_types ADD CONSTRAINT feature_types_check_bbox CHECK (isvalid(wgs84bbox));");
    writer.println(
        "/* (no spatial index needed, as envelope is only used for keeping track of feature type extents) */");

    int typeId = 0;
    for (FeatureType ft : fts) {
      if (!ft.isAbstract()) {
        QName qName = ft.getName();
        String tableName = ftNamesToHints.get(qName).getFeatureTypeHints().getDBTable();
        writer.println(
            "INSERT INTO feature_types (id,qname,tablename) VALUES ("
                + (typeId++)
                + ",'"
                + qName
                + "', '"
                + tableName
                + "');");
      }
    }

    writer.println();
    writer.println("CREATE TABLE gml_objects (");
    writer.println("    id SERIAL PRIMARY KEY,");
    writer.println("    gml_id text UNIQUE NOT NULL,");
    writer.println("    gml_description text,");
    writer.println("    ft_type smallint REFERENCES feature_types,");
    writer.println("    binary_object bytea");
    writer.println(");");
    writer.println("COMMENT ON TABLE gml_objects IS 'All objects (features and geometries)';");

    writer.println(
        "SELECT ADDGEOMETRYCOLUMN('"
            + dbSchema
            + "', 'gml_objects','gml_bounded_by','-1','GEOMETRY',2);");
    writer.println(
        "ALTER TABLE gml_objects ADD CONSTRAINT gml_objects_geochk CHECK (isvalid(gml_bounded_by));");
    writer.println(
        "CREATE INDEX gml_objects_sidx ON gml_objects USING GIST ( gml_bounded_by GIST_GEOMETRY_OPS );");
    writer.println();

    writer.println("CREATE TABLE gml_names (");
    writer.println("    gml_object_id integer REFERENCES GML_OBJECTS,");
    writer.println("    name text NOT NULL,");
    writer.println("    codespace text,");
    writer.println("    prop_idx smallint NOT NULL");
    writer.println(");");
    writer.println();
    writer.println("/* --- END global section --- */");
  }
 private FeatureType mockFeatureType(String name) {
   FeatureType mockedFeatureType = mock(FeatureType.class);
   QName qName = new QName(name);
   when(mockedFeatureType.getName()).thenReturn(qName);
   return mockedFeatureType;
 }