001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.wfs.xml.v1_0_0;
006:
007: import net.opengis.wfs.DescribeFeatureTypeType;
008:
009: import org.geoserver.ows.util.RequestUtils;
010: import org.geoserver.ows.util.ResponseUtils;
011: import org.geoserver.platform.Operation;
012: import org.geoserver.platform.ServiceException;
013: import org.geoserver.wfs.WFS;
014: import org.geoserver.wfs.WFSDescribeFeatureTypeOutputFormat;
015: import org.geoserver.wfs.WFSException;
016: import org.geotools.feature.FeatureType;
017: import org.geotools.gml.producer.FeatureTypeTransformer;
018: import org.vfny.geoserver.global.Data;
019: import org.vfny.geoserver.global.FeatureTypeInfo;
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.IOException;
023: import java.io.OutputStream;
024: import java.io.StringWriter;
025: import java.util.HashSet;
026: import java.util.Iterator;
027: import java.util.Set;
028: import java.util.logging.Level;
029: import java.util.logging.Logger;
030: import javax.xml.transform.TransformerException;
031:
032: public class XmlSchemaEncoder extends
033: WFSDescribeFeatureTypeOutputFormat {
034: /** Standard logging instance for class */
035: private static final Logger LOGGER = org.geotools.util.logging.Logging
036: .getLogger("org.vfny.geoserver.responses");
037:
038: // Initialize some generic GML information
039: // ABSTRACT OUTSIDE CLASS, IF POSSIBLE
040: private static final String SCHEMA_URI = "\"http://www.w3.org/2001/XMLSchema\"";
041: private static final String XS_NAMESPACE = "\n xmlns:xs="
042: + SCHEMA_URI;
043: private static final String GML_URL = "\"http://www.opengis.net/gml\"";
044: private static final String GML_NAMESPACE = "\n xmlns:gml="
045: + GML_URL;
046: private static final String ELEMENT_FORM_DEFAULT = "\n elementFormDefault=\"qualified\"";
047: private static final String ATTR_FORM_DEFAULT = "\n attributeFormDefault=\"unqualified\" version=\"1.0\">";
048: private static final String TARGETNS_PREFIX = "\n targetNamespace=\"";
049: private static final String TARGETNS_SUFFIX = "\" ";
050:
051: /** Fixed return footer information */
052: private static final String FOOTER = "\n</xs:schema>";
053: WFS wfs;
054: Data catalog;
055:
056: public XmlSchemaEncoder(WFS wfs, Data catalog) {
057: super ("XMLSCHEMA");
058: this .wfs = wfs;
059: this .catalog = catalog;
060: }
061:
062: public String getMimeType(Object value, Operation operation)
063: throws ServiceException {
064: return "text/xml";
065: }
066:
067: protected void write(FeatureTypeInfo[] featureTypeInfos,
068: OutputStream output, Operation describeFeatureType)
069: throws IOException {
070: //generates response, using general function
071: String xmlResponse = generateTypes(featureTypeInfos,
072: (DescribeFeatureTypeType) describeFeatureType
073: .getParameters()[0]);
074:
075: if (!wfs.isVerbose()) {
076: //strip out the formatting. This is pretty much the only way we
077: //can do this, as the user files are going to have newline
078: //characters and whatnot, unless we can get rid of formatting
079: //when we read the file, which could be worth looking into if
080: //this slows things down.
081: xmlResponse = xmlResponse.replaceAll(">\n[ \\t\\n]*", ">");
082: xmlResponse = xmlResponse.replaceAll("\n[ \\t\\n]*", " ");
083: }
084:
085: byte[] content = xmlResponse.getBytes();
086: output.write(content);
087: }
088:
089: /**
090: * Internal method to generate the XML response object, using feature
091: * types.
092: *
093: * @param wfsRequest The request object.
094: *
095: * @return The XMLSchema describing the features requested.
096: *
097: * @throws WFSException For any problems.
098: */
099: private final String generateTypes(FeatureTypeInfo[] infos,
100: DescribeFeatureTypeType request) throws IOException {
101: // Initialize return information and intermediate return objects
102: StringBuffer tempResponse = new StringBuffer();
103:
104: tempResponse.append("<?xml version=\"1.0\" encoding=\""
105: + wfs.getCharSet().name() + "\"?>" + "\n<xs:schema ");
106:
107: String proxifiedBaseUrl = RequestUtils.proxifiedBaseURL(request
108: .getBaseUrl(), wfs.getGeoServer().getProxyBaseUrl());
109: //allSameType will throw WFSException if there are types that are not found.
110: if (allSameType(infos)) {
111: //all the requested have the same namespace prefix, so return their
112: //schemas.
113: FeatureTypeInfo ftInfo = infos[0];
114: String targetNs = ftInfo.getNameSpace().getURI();
115:
116: //String targetNs = nsInfoType.getXmlns();
117: tempResponse.append(TARGETNS_PREFIX + targetNs
118: + TARGETNS_SUFFIX);
119:
120: //namespace
121: tempResponse.append("\n " + "xmlns:"
122: + ftInfo.getNameSpace().getPrefix() + "=\""
123: + targetNs + "\"");
124:
125: //xmlns:" + nsPrefix + "=\"" + targetNs
126: //+ "\"");
127: tempResponse.append(GML_NAMESPACE);
128: tempResponse.append(XS_NAMESPACE);
129: tempResponse.append(ELEMENT_FORM_DEFAULT
130: + ATTR_FORM_DEFAULT);
131:
132: //request.getBaseUrl should actually be GeoServer.getSchemaBaseUrl()
133: //but that method is broken right now. See the note there.
134:
135: //JD: need a good way to publish resources under a web url, at the
136: // same time abstracting away the httpness of the service, for
137: // now replacing the schemas.opengis.net
138:
139: // tempResponse.append("\n\n<xs:import namespace=" + GML_URL
140: // + " schemaLocation=\"" + request.getSchemaBaseUrl()
141: // + "gml/2.1.2/feature.xsd\"/>\n\n");
142: tempResponse.append("\n\n<xs:import namespace="
143: + GML_URL
144: + " schemaLocation=\""
145: + ResponseUtils.appendPath(proxifiedBaseUrl,
146: "schemas/gml/2.1.2.1/feature.xsd")
147: + "\"/>\n\n");
148: tempResponse.append(generateSpecifiedTypes(infos));
149: } else {
150: //the featureTypes do not have all the same prefixes.
151: tempResponse.append(XS_NAMESPACE);
152: tempResponse.append(ELEMENT_FORM_DEFAULT
153: + ATTR_FORM_DEFAULT);
154:
155: Set prefixes = new HashSet();
156:
157: //iterate through the types, and make a set of their prefixes.
158: for (int i = 0; i < infos.length; i++) {
159: FeatureTypeInfo ftInfo = infos[i];
160: prefixes.add(ftInfo.getNameSpace().getPrefix());
161: }
162:
163: Iterator prefixIter = prefixes.iterator();
164:
165: while (prefixIter.hasNext()) {
166: //iterate through prefixes, and add the types that have that prefix.
167: String prefix = prefixIter.next().toString();
168: String wfsBaseUrl;
169: if (proxifiedBaseUrl.endsWith("/"))
170: wfsBaseUrl = proxifiedBaseUrl
171: + request.getService().toLowerCase();
172: else
173: wfsBaseUrl = proxifiedBaseUrl + "/"
174: + request.getService().toLowerCase();
175: tempResponse.append(getNSImport(prefix, infos,
176: wfsBaseUrl));
177: }
178: }
179:
180: tempResponse.append(FOOTER);
181:
182: return tempResponse.toString();
183: }
184:
185: /**
186: * Creates a import namespace element, for cases when requests contain
187: * multiple namespaces, as you can not have more than one target
188: * namespace. See wfs spec. 8.3.1. All the typeNames that have the
189: * correct prefix are added to the import statement.
190: *
191: * @param prefix the namespace prefix, which must be mapped in the main
192: * ConfigInfo, for this import statement.
193: * @param typeNames a list of all requested typeNames, only those that
194: * match the prefix will be a part of this import statement.
195: * @param r DOCUMENT ME!
196: *
197: * @return The namespace element.
198: */
199: private StringBuffer getNSImport(String prefix,
200: FeatureTypeInfo[] infos, String baseUrl) {
201: LOGGER.finer("prefix is " + prefix);
202:
203: StringBuffer retBuffer = new StringBuffer(
204: "\n <xs:import namespace=\"");
205: String namespace = catalog.getNameSpace(prefix).getURI();
206: retBuffer.append(namespace + "\"");
207: retBuffer
208: .append("\n schemaLocation=\""
209: + baseUrl
210: + "?request=DescribeFeatureType&service=wfs&version=1.0.0&typeName=");
211:
212: for (int i = 0; i < infos.length; i++) {
213: FeatureTypeInfo info = infos[i];
214: String typeName = info.getName();
215:
216: if (typeName.startsWith(prefix + ":")) {
217: retBuffer.append(typeName + ",");
218: }
219:
220: //JD: some of this logic should be fixed by poplulating the
221: // info objects properly, double check
222: // if (typeName.startsWith(prefix)
223: // || ((typeName.indexOf(':') == -1)
224: // && prefix.equals(r.getWFS().getData().getDefaultNameSpace()
225: // .getPrefix()))) {
226: // retBuffer.append(typeName + ",");
227: // }
228: }
229:
230: retBuffer.deleteCharAt(retBuffer.length() - 1);
231: retBuffer.append("\"/>");
232:
233: return retBuffer;
234: }
235:
236: /**
237: * Internal method to print just the requested types. They should all be
238: * in the same namespace, that handling should be done before. This will
239: * not do any namespace handling, just prints up either what's in the
240: * schema file, or if it's not there then generates the types from their
241: * FeatureTypes. Also appends the global element so that the types can
242: * substitute as features.
243: *
244: * @param requestedTypes The requested table names.
245: * @param gs DOCUMENT ME!
246: *
247: * @return A string of the types printed.
248: *
249: * @throws WFSException DOCUMENT ME!
250: *
251: * @task REVISIT: We need a way to make sure the extension bases are
252: * correct. should likely add a field to the info.xml in the
253: * featureTypes folder, that optionally references an extension base
254: * (should it be same namespace? we could also probably just do an
255: * import on the extension base). This function then would see if
256: * the typeInfo has an extension base, and would add or import the
257: * file appropriately, and put the correct substitution group in
258: * this function.
259: */
260: private String generateSpecifiedTypes(FeatureTypeInfo[] infos) {
261: //TypeRepository repository = TypeRepository.getInstance();
262: String tempResponse = new String();
263:
264: String generatedType = new String();
265: Set validTypes = new HashSet();
266:
267: // Loop through requested tables to add element types
268: for (int i = 0; i < infos.length; i++) {
269: FeatureTypeInfo ftInfo = (FeatureTypeInfo) infos[i];
270:
271: if (!validTypes.contains(ftInfo)) {
272: File schemaFile = ftInfo.getSchemaFile();
273:
274: try {
275: //Hack here, schemaFile should not be null, but it is
276: //when a fType is first created, since we only add the
277: //schemaFile param to dto on a load. This should be
278: //fixed, maybe even have the schema file persist, or at
279: //the very least be present right after creation.
280: if ((schemaFile != null) && schemaFile.exists()
281: && schemaFile.canRead()) {
282: generatedType = writeFile(schemaFile);
283: } else {
284: FeatureType ft2 = ftInfo.getFeatureType();
285: String gType2 = generateFromSchema(ft2);
286:
287: if ((gType2 != null) && (gType2 != "")) {
288: generatedType = gType2;
289: }
290: }
291: } catch (IOException e) {
292: generatedType = "";
293: }
294:
295: if (!generatedType.equals("")) {
296: tempResponse = tempResponse + generatedType;
297: validTypes.add(ftInfo);
298: }
299: }
300: }
301:
302: // Loop through requested tables again to add elements
303: // NOT VERY EFFICIENT - PERHAPS THE MYSQL ABSTRACTION CAN FIX THIS;
304: // STORE IN HASH?
305: for (Iterator i = validTypes.iterator(); i.hasNext();) {
306: // Print element representation of table
307: tempResponse = tempResponse
308: + printElement((FeatureTypeInfo) i.next());
309: }
310:
311: tempResponse = tempResponse + "\n\n";
312:
313: return tempResponse;
314: }
315:
316: /**
317: * Transforms a FeatureTypeInfo into gml, with no headers.
318: *
319: * @param schema the schema to transform.
320: *
321: * @return DOCUMENT ME!
322: *
323: * @task REVISIT: when this class changes to writing directly to out this
324: * can just take a writer and write directly to it.
325: */
326: private String generateFromSchema(FeatureType schema)
327: throws IOException {
328: try {
329: StringWriter writer = new StringWriter();
330: FeatureTypeTransformer t = new FeatureTypeTransformer();
331: t.setIndentation(4);
332: t.setOmitXMLDeclaration(true);
333: t.transform(schema, writer);
334:
335: return writer.getBuffer().toString();
336: } catch (TransformerException te) {
337: LOGGER.log(Level.WARNING,
338: "Error generating schema from feature type", te);
339: throw (IOException) new IOException(
340: "problem transforming type").initCause(te);
341: }
342: }
343:
344: /**
345: * Internal method to print XML element information for table.
346: *
347: * @param type The table name.
348: *
349: * @return The element part of the response.
350: */
351: private static String printElement(FeatureTypeInfo type) {
352: return "\n <xs:element name=\"" + type.getTypeName()
353: + "\" type=\"" + type.getNameSpace().getPrefix() + ":"
354: + type.getSchemaName()
355: + "\" substitutionGroup=\"gml:_Feature\"/>";
356: }
357:
358: /**
359: * Adds a feature type object to the final output buffer
360: *
361: * @param inputFileName The name of the feature type.
362: *
363: * @return The string representation of the file containing the schema.
364: *
365: * @throws WFSException For io problems reading the file.
366: */
367: public String writeFile(File inputFile) throws IOException {
368: LOGGER.finest("writing file " + inputFile);
369:
370: String finalOutput = new String();
371:
372: try {
373: // File inputFile = new File(inputFileName);
374: FileInputStream inputStream = new FileInputStream(inputFile);
375: byte[] fileBuffer = new byte[inputStream.available()];
376: int bytesRead;
377:
378: while ((bytesRead = inputStream.read(fileBuffer)) != -1) {
379: String tempOutput = new String(fileBuffer);
380: finalOutput = finalOutput + tempOutput;
381: }
382: } catch (IOException e) {
383: //REVISIT: should things fail if there are featureTypes that
384: //don't have schemas in the right place? Because as it is now
385: //a describe all will choke if there is one ft with no schema.xml
386: throw (IOException) new IOException(
387: "problem writing featureType information "
388: + " from " + inputFile).initCause(e);
389: }
390:
391: return finalOutput;
392: }
393:
394: /**
395: * Checks that the collection of featureTypeNames all have the same prefix.
396: * Used to determine if their schemas are all in the same namespace or if
397: * imports need to be done.
398: *
399: * @param infos list of feature type info objects..
400: *
401: * @return true if all the types in the collection have the same prefix.
402: *
403: */
404: public boolean allSameType(FeatureTypeInfo[] infos) {
405: boolean sameType = true;
406:
407: if (infos.length == 0) {
408: return false;
409: }
410:
411: FeatureTypeInfo first = infos[0];
412:
413: for (int i = 0; i < infos.length; i++) {
414: FeatureTypeInfo ftInfo = infos[i];
415:
416: if (!first.getNameSpace().equals(ftInfo.getNameSpace())) {
417: return false;
418: }
419: }
420:
421: return sameType;
422: }
423: }
|