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.vfny.geoserver.wms.requests;
006:
007: import org.geotools.feature.FeatureCollection;
008: import org.geotools.feature.IllegalAttributeException;
009: import org.geotools.feature.SchemaException;
010: import org.geotools.resources.coverage.CoverageUtilities;
011: import org.geotools.styling.FeatureTypeStyle;
012: import org.geotools.styling.Rule;
013: import org.geotools.styling.SLDParser;
014: import org.geotools.styling.Style;
015: import org.geotools.styling.StyleFactory;
016: import org.geotools.styling.StyleFactoryFinder;
017: import org.opengis.referencing.operation.TransformException;
018: import org.vfny.geoserver.Request;
019: import org.vfny.geoserver.ServiceException;
020: import org.vfny.geoserver.global.CoverageInfo;
021: import org.vfny.geoserver.global.Data;
022: import org.vfny.geoserver.global.FeatureTypeInfo;
023: import org.vfny.geoserver.global.MapLayerInfo;
024: import org.vfny.geoserver.global.WMS;
025: import org.vfny.geoserver.util.Requests;
026: import org.vfny.geoserver.wms.WmsException;
027: import org.vfny.geoserver.wms.responses.GetLegendGraphicResponse;
028: import org.vfny.geoserver.wms.servlets.WMService;
029: import java.awt.Font;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.InputStreamReader;
033: import java.io.Reader;
034: import java.io.StringReader;
035: import java.net.MalformedURLException;
036: import java.net.URL;
037: import java.util.Iterator;
038: import java.util.Map;
039: import java.util.NoSuchElementException;
040: import java.util.StringTokenizer;
041: import java.util.logging.Level;
042: import java.util.logging.Logger;
043: import javax.servlet.http.HttpServletRequest;
044: import javax.xml.parsers.FactoryConfigurationError;
045:
046: /**
047: * Key/Value pair set parsed for a GetLegendGraphic request. When calling
048: * <code>getRequest</code> produces a {@linkPlain
049: * org.vfny.geoserver.requests.wms.GetLegendGraphicRequest}
050: * <p>
051: * See {@linkplain org.vfny.geoserver.wms.requests.GetLegendGraphicRequest} for
052: * a complete list of expected request parameters.
053: * </p>
054: *
055: * @author Gabriel Roldan, Axios Engineering
056: * @version $Id: GetLegendGraphicKvpReader.java 7037 2007-06-18 23:19:20Z
057: * saul.farber $
058: * @see org.vfny.geoserver.wms.requests.GetLegendGraphicRequest
059: */
060: public class GetLegendGraphicKvpReader extends WmsKvpRequestReader {
061: /** DOCUMENT ME! */
062: private static final Logger LOGGER = org.geotools.util.logging.Logging
063: .getLogger(GetLegendGraphicKvpReader.class.getPackage()
064: .getName());
065:
066: /**
067: * Factory to create styles from inline or remote SLD documents (aka, from
068: * SLD_BODY or SLD parameters).
069: */
070: private static final StyleFactory styleFactory = StyleFactoryFinder
071: .createStyleFactory();
072:
073: /**
074: * Creates a new GetLegendGraphicKvpReader object.
075: *
076: * @param params
077: * map of key/value pairs with the parameters for a
078: * GetLegendGraphic request
079: * @param service
080: * service handle request
081: */
082: public GetLegendGraphicKvpReader(Map params, WMService service) {
083: super (params, service);
084: }
085:
086: /**
087: * DOCUMENT ME!
088: *
089: * @param httpRequest
090: * DOCUMENT ME!
091: *
092: * @return DOCUMENT ME!
093: *
094: * @throws ServiceException
095: * see <code>throws WmsException</code>
096: * @throws WmsException
097: * if some invalid parameter was passed.
098: */
099: public Request getRequest(HttpServletRequest httpRequest)
100: throws ServiceException {
101: GetLegendGraphicRequest request = new GetLegendGraphicRequest(
102: (WMService) getServiceRef());
103: // TODO: we should really get rid of the HttpServletRequest dependency
104: // beyond the HTTP facade. Neither the request readers should depend on
105: // it
106: request.setHttpServletRequest(httpRequest);
107:
108: String version = super .getRequestVersion();
109:
110: // Fix for http://jira.codehaus.org/browse/GEOS-710
111: // Since at the moment none of the other request do check the version
112: // numbers, we
113: // disable this check for the moment, and wait for a proper fix once the
114: // we support more than one version of WMS/WFS specs
115: // if (!GetLegendGraphicRequest.SLD_VERSION.equals(version)) {
116: // throw new WmsException("Invalid SLD version number \"" + version
117: // + "\"");
118: // }
119: String layer = getValue("LAYER");
120: MapLayerInfo mli = new MapLayerInfo();
121:
122: try {
123: WMS wms = request.getWMS();
124: Data catalog = wms.getData();
125:
126: FeatureTypeInfo fti = catalog.getFeatureTypeInfo(layer);
127: mli.setFeature(fti);
128: request.setLayer(mli.getFeature().getFeatureType());
129: } catch (NoSuchElementException e) {
130: try {
131: CoverageInfo cvi = request.getWMS().getData()
132: .getCoverageInfo(layer);
133: mli.setCoverage(cvi);
134:
135: FeatureCollection feature = CoverageUtilities
136: .wrapGc(cvi.getCoverage(null, null));
137: request.setLayer(feature.getFeatureType());
138: } catch (NoSuchElementException ne) {
139: throw new WmsException(ne, new StringBuffer(layer)
140: .append(" layer does not exists.").toString(),
141: ne.getLocalizedMessage());
142: } catch (TransformException te) {
143: throw new WmsException(
144: te,
145: "Can't obtain the schema for the required layer.",
146: te.getLocalizedMessage());
147: } catch (FactoryConfigurationError fce) {
148: throw new WmsException(
149: fce,
150: "Can't obtain the schema for the required layer.",
151: fce.getLocalizedMessage());
152: } catch (SchemaException se) {
153: throw new WmsException(
154: se,
155: "Can't obtain the schema for the required layer.",
156: se.getLocalizedMessage());
157: } catch (IllegalAttributeException iae) {
158: throw new WmsException(
159: iae,
160: "Can't obtain the schema for the required layer.",
161: iae.getLocalizedMessage());
162: }
163: } catch (IOException e) {
164: throw new WmsException(
165: "Can't obtain the schema for the required layer.");
166: }
167:
168: String format = getValue("FORMAT");
169:
170: if (getServiceRef().getApplicationContext() == null) {
171: LOGGER
172: .log(Level.SEVERE,
173: "Application Context is null. No producer beans can be found!");
174: } else if (!GetLegendGraphicResponse.supportsFormat(format,
175: getServiceRef().getApplicationContext())) {
176: throw new WmsException(new StringBuffer(
177: "Invalid graphic format: ").append(format)
178: .toString(), "InvalidFormat");
179: } else {
180: request.setFormat(format);
181: }
182:
183: parseOptionalParameters(request, mli);
184:
185: return request;
186: }
187:
188: /**
189: * Parses the GetLegendGraphic optional parameters.
190: * <p>
191: * The parameters parsed by this method are:
192: * <ul>
193: * <li>FEATURETYPE for the
194: * {@link GetLegendGraphicRequest#getFeatureType() featureType} property.</li>
195: * <li>SCALE for the {@link GetLegendGraphicRequest#getScale() scale}
196: * property.</li>
197: * <li>WIDTH for the {@link GetLegendGraphicRequest#getWidth() width}
198: * property.</li>
199: * <li>HEIGHT for the {@link GetLegendGraphicRequest#getHeight() height}
200: * property.</li>
201: * <li>EXCEPTIONS for the
202: * {@link GetLegendGraphicRequest#getExceptionsFormat() exceptions}
203: * property.</li>
204: * <li>TRANSPARENT for the
205: * {@link GetLegendGraphicRequest#isTransparent() transparent} property.</li>
206: * <li>LEGEND_OPTIONS for the
207: * {@link GetLegendGraphicRequest#getLegendOptions() legendOptions}
208: * property.</li>
209: * </ul>
210: * </p>
211: *
212: * @param req
213: * The request to set the properties to.
214: * @param mli
215: * the {@link MapLayerInfo layer} for which the legend graphic is
216: * to be produced, from where to extract the style information.
217: *
218: * @task TODO: validate EXCEPTIONS parameter
219: */
220: private void parseOptionalParameters(GetLegendGraphicRequest req,
221: MapLayerInfo mli) {
222: parseStyleAndRule(req, mli);
223:
224: // not used by now, since we don't support nested layers yet
225: String featureType = getValue("FEATURETYPE");
226:
227: String scale = getValue("SCALE");
228:
229: if ((scale != null) && !"".equals(scale)) {
230: double scaleFactor = Double.valueOf(scale).doubleValue();
231: req.setScale(scaleFactor);
232: }
233:
234: String width = getValue("WIDTH");
235:
236: if ((width != null) && !"".equals(width)) {
237: int legendW = Integer.valueOf(width).intValue();
238: req.setWidth(legendW);
239: }
240:
241: String height = getValue("HEIGHT");
242:
243: if ((height != null) && !"".equals(height)) {
244: int legendH = Integer.valueOf(height).intValue();
245: req.setHeight(legendH);
246: }
247:
248: String exceptions = getValue("EXCEPTIONS");
249:
250: if (exceptions != null) {
251: req.setExceptionsFormat(exceptions);
252: }
253:
254: String transparentParam = getValue("TRANSPARENT");
255: boolean transparentBackground = "true"
256: .equalsIgnoreCase(transparentParam);
257: req.setTransparent(transparentBackground);
258:
259: // the LEGEND_OPTIONS parameter gets parsed here.
260: req.setLegendOptions(Requests
261: .parseOptionParameter(getValue("LEGEND_OPTIONS")));
262: }
263:
264: /**
265: * Parses the STYLE, SLD and SLD_BODY parameters, as well as RULE.
266: *
267: * <p>
268: * STYLE, SLD and SLD_BODY are mutually exclusive. STYLE refers to a named
269: * style known by the server and applicable to the requested layer (i.e., it
270: * is exposed as one of the layer's styles in the Capabilities document).
271: * SLD is a URL to an externally available SLD document, and SLD_BODY is a
272: * string containing the SLD document itself.
273: * </p>
274: *
275: * <p>
276: * As I don't completelly understand which takes priority over which from
277: * the spec, I assume the precedence order as follow: SLD, SLD_BODY, STYLE,
278: * in decrecent order of precedence.
279: * </p>
280: *
281: * @param req
282: * @param ftype
283: */
284: private void parseStyleAndRule(GetLegendGraphicRequest req,
285: MapLayerInfo layer) {
286: String styleName = getValue("STYLE");
287: String sldUrl = getValue("SLD");
288: String sldBody = getValue("SLD_BODY");
289:
290: if (LOGGER.isLoggable(Level.FINE)) {
291: LOGGER.fine(new StringBuffer("looking for style ").append(
292: styleName).toString());
293: }
294:
295: Style sldStyle = null;
296:
297: if (sldUrl != null) {
298: if (LOGGER.isLoggable(Level.FINER)) {
299: LOGGER.finer("taking style from SLD parameter");
300: }
301:
302: Style[] styles = loadRemoteStyle(sldUrl); // may throw an
303: // exception
304:
305: sldStyle = findStyle(styleName, styles);
306: } else if (sldBody != null) {
307: if (LOGGER.isLoggable(Level.FINER)) {
308: LOGGER.finer("taking style from SLD_BODY parameter");
309: }
310:
311: Style[] styles = parseSldBody(sldBody); // may throw an exception
312: sldStyle = findStyle(styleName, styles);
313: } else if ((styleName != null) && !"".equals(styleName)) {
314: if (LOGGER.isLoggable(Level.FINER)) {
315: LOGGER.finer("taking style from STYLE parameter");
316: }
317:
318: sldStyle = req.getWMS().getData().getStyle(styleName);
319: } else {
320: sldStyle = layer.getDefaultStyle();
321: }
322:
323: req.setStyle(sldStyle);
324:
325: String rule = getValue("RULE");
326: Rule sldRule = extractRule(sldStyle, rule);
327:
328: if (sldRule != null) {
329: req.setRule(sldRule);
330: }
331: }
332:
333: /**
334: * Finds the Style named <code>styleName</code> in <code>styles</code>.
335: *
336: * @param styleName
337: * name of style to search for in the list of styles. If
338: * <code>null</code>, it is assumed the request is made in
339: * literal mode and the user has requested the first style.
340: * @param styles
341: * non null, non empty, list of styles
342: * @return
343: * @throws NoSuchElementException
344: * if no style named <code>styleName</code> is found in
345: * <code>styles</code>
346: */
347: private Style findStyle(String styleName, Style[] styles)
348: throws NoSuchElementException {
349: if ((styles == null) || (styles.length == 0)) {
350: throw new NoSuchElementException(
351: "No styles have been provided to search for "
352: + styleName);
353: }
354:
355: if (styleName == null) {
356: if (LOGGER.isLoggable(Level.FINER)) {
357: LOGGER
358: .finer("styleName is null, request in literal mode, returning first style");
359: }
360:
361: return styles[0];
362: }
363:
364: if (LOGGER.isLoggable(Level.FINER)) {
365: LOGGER.finer(new StringBuffer(
366: "request in library mode, looking for style ")
367: .append(styleName).toString());
368: }
369:
370: StringBuffer noMatchNames = new StringBuffer();
371:
372: for (int i = 0; i < styles.length; i++) {
373: if ((styles[i] != null)
374: && styleName.equals(styles[i].getName())) {
375: return styles[i];
376: }
377:
378: noMatchNames.append(styles[i].getName());
379:
380: if (i < styles.length) {
381: noMatchNames.append(", ");
382: }
383: }
384:
385: throw new NoSuchElementException(styleName
386: + " not found. Provided style names: " + noMatchNames);
387: }
388:
389: /**
390: * Loads a remote SLD document and parses it to a Style object
391: *
392: * @param sldUrl
393: * an URL to a SLD document
394: *
395: * @return the document parsed to a Style object
396: *
397: * @throws WmsException
398: * if <code>sldUrl</code> is not a valid URL, a stream can't
399: * be opened or a parsing error occurs
400: */
401: private Style[] loadRemoteStyle(String sldUrl) throws WmsException {
402: InputStream in;
403:
404: try {
405: URL url = new URL(sldUrl);
406: in = url.openStream();
407: } catch (MalformedURLException e) {
408: throw new WmsException(e,
409: "Not a valid URL to an SLD document " + sldUrl,
410: "loadRemoteStyle");
411: } catch (IOException e) {
412: throw new WmsException(e, "Can't open the SLD URL "
413: + sldUrl, "loadRemoteStyle");
414: }
415:
416: return parseSld(new InputStreamReader(in));
417: }
418:
419: /**
420: * Parses a SLD Style from a xml string
421: *
422: * @param sldBody
423: * the string containing the SLD document
424: *
425: * @return the SLD document string parsed to a Style object
426: *
427: * @throws WmsException
428: * if a parsing error occurs.
429: */
430: private Style[] parseSldBody(String sldBody) throws WmsException {
431: // return parseSld(new StringBufferInputStream(sldBody));
432: return parseSld(new StringReader(sldBody));
433: }
434:
435: /**
436: * Parses the content of the given input stream to an SLD Style, provided
437: * that a valid SLD document can be read from <code>xmlIn</code>.
438: *
439: * @param xmlIn
440: * where to read the SLD document from.
441: *
442: * @return the parsed Style
443: *
444: * @throws WmsException
445: * if a parsing error occurs
446: */
447: private Style[] parseSld(Reader xmlIn) throws WmsException {
448: SLDParser parser = new SLDParser(styleFactory, xmlIn);
449: Style[] styles = null;
450:
451: try {
452: styles = parser.readXML();
453: } catch (RuntimeException e) {
454: throw new WmsException(e);
455: }
456:
457: if ((styles == null) || (styles.length == 0)) {
458: throw new WmsException("Document contains no styles");
459: }
460:
461: return styles;
462: }
463:
464: /**
465: * DOCUMENT ME!
466: *
467: * @param sldStyle
468: * @param rule
469: *
470: * @return DOCUMENT ME!
471: *
472: * @throws WmsException
473: */
474: private Rule extractRule(Style sldStyle, String rule)
475: throws WmsException {
476: Rule sldRule = null;
477:
478: if ((rule != null) && !"".equals(rule)) {
479: FeatureTypeStyle[] fts = sldStyle.getFeatureTypeStyles();
480:
481: for (int i = 0; i < fts.length; i++) {
482: Rule[] rules = fts[i].getRules();
483:
484: for (int r = 0; r < rules.length; r++) {
485: if (rule.equalsIgnoreCase(rules[r].getName())) {
486: sldRule = rules[r];
487:
488: if (LOGGER.isLoggable(Level.FINE)) {
489: LOGGER.fine(new StringBuffer(
490: "found requested rule: ").append(
491: rule).toString());
492: }
493:
494: break;
495: }
496: }
497: }
498:
499: if (sldRule == null) {
500: throw new WmsException("Style " + sldStyle.getName()
501: + " does not contains a rule named " + rule);
502: }
503: }
504:
505: return sldRule;
506: }
507: }
|