0001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
0002: * This code is licensed under the GPL 2.0 license, availible at the root
0003: * application directory.
0004: */
0005: package org.geoserver.ows;
0006:
0007: import java.io.BufferedInputStream;
0008: import java.io.BufferedReader;
0009: import java.io.File;
0010: import java.io.FileInputStream;
0011: import java.io.IOException;
0012: import java.io.InputStream;
0013: import java.io.OutputStream;
0014: import java.io.Reader;
0015: import java.lang.reflect.InvocationTargetException;
0016: import java.lang.reflect.Method;
0017: import java.nio.charset.Charset;
0018: import java.util.ArrayList;
0019: import java.util.Collection;
0020: import java.util.Collections;
0021: import java.util.Comparator;
0022: import java.util.HashMap;
0023: import java.util.HashSet;
0024: import java.util.Iterator;
0025: import java.util.List;
0026: import java.util.Map;
0027: import java.util.Set;
0028: import java.util.logging.Level;
0029: import java.util.logging.Logger;
0030:
0031: import javax.servlet.http.HttpServletRequest;
0032: import javax.servlet.http.HttpServletResponse;
0033: import javax.xml.namespace.QName;
0034:
0035: import org.acegisecurity.AcegiSecurityException;
0036: import org.eclipse.emf.ecore.EObject;
0037: import org.geoserver.ows.security.OperationInterceptor;
0038: import org.geoserver.ows.util.EncodingInfo;
0039: import org.geoserver.ows.util.OwsUtils;
0040: import org.geoserver.ows.util.RequestUtils;
0041: import org.geoserver.ows.util.XmlCharsetDetector;
0042: import org.geoserver.platform.GeoServerExtensions;
0043: import org.geoserver.platform.Operation;
0044: import org.geoserver.platform.Service;
0045: import org.geoserver.platform.ServiceException;
0046: import org.geotools.util.Version;
0047: import org.geotools.xml.EMFUtils;
0048: import org.springframework.beans.factory.NoSuchBeanDefinitionException;
0049: import org.springframework.web.servlet.ModelAndView;
0050: import org.springframework.web.servlet.mvc.AbstractController;
0051: import org.xmlpull.v1.XmlPullParser;
0052: import org.xmlpull.v1.XmlPullParserFactory;
0053:
0054: /**
0055: * Dispatches an http request to an open web service (OWS).
0056: * <p>
0057: * An OWS request contains three bits of information:
0058: * <ol>
0059: * <li>The service being called
0060: * <li>The operation of the service to execute
0061: * <li>The version of the service ( optional )
0062: * </ol>
0063: * Additional, an OWS request can contain an arbitray number of additional
0064: * parameters.
0065: * </p>
0066: * <p>
0067: * An OWS request can be specified in two forms. The first form is known as "KVP"
0068: * in which all the parameters come in the form of a set of key-value pairs.
0069: * Commonly this type of request is made in an http "GET" request, the parameters
0070: * being specified in the query string:
0071: *
0072: * <pre>
0073: * <code>http://www.xyz.com/geoserver?service=someService&request=someRequest&version=X.Y.Z¶m1=...¶m2=...
0074: * </pre>
0075: *
0076: * This type of request can also be made in a "POST" request in with a
0077: * mime-type of "application/x-www-form-urlencoded".
0078: * </p>
0079: * <p>
0080: * The second form is known as "XML" in which all the parameters come in the
0081: * form of an xml document. This type of request is made in an http "POST"
0082: * request.
0083: *
0084: * <pre>
0085: * <code>
0086: * <?xml version="1.0" encoding="UTF-8"?>
0087: * <SomeRequest service="someService" version="X.Y.Z">
0088: * <Param1>...</Param1>
0089: * <Param2>...</Param2>
0090: * ...
0091: * </SomeRequest>
0092: * </code>
0093: * </pre>
0094: * </p>
0095: * <p>
0096: * When a request is received, the <b>service</b> the <b>version</b> parameters
0097: * are used to locate a service desciptor, an instance of {@link Service}. With
0098: * the service descriptor, the <b>request</b> parameter is used to locate the
0099: * operation of the service to call.
0100: * </p>
0101: *
0102: * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
0103: *
0104: */
0105: public class Dispatcher extends AbstractController {
0106: /**
0107: * Logging instance
0108: */
0109: static Logger logger = org.geotools.util.logging.Logging
0110: .getLogger("org.geoserver.ows");
0111:
0112: /** flag to control wether the dispatcher is cite compliant */
0113: boolean citeCompliant = false;
0114:
0115: /** The security interceptor to be used for authorization checks **/
0116: OperationInterceptor securityInterceptor = null;
0117:
0118: /**
0119: * Sets the flag to control wether the dispatcher is cite compliante.
0120: * <p>
0121: * If set to <code>true</code>, the dispatcher with throw exceptions when
0122: * it encounters something that is not 100% compliant with CITE standards.
0123: * An example would be a request which specifies the servce in the context
0124: * path: '.../geoserver/wfs?request=...' and not with the kvp '&service=wfs'.
0125: * </p>
0126: *
0127: * @param citeCompliant <code>true</code> to set compliance,
0128: * <code>false</code> to unset it.
0129: */
0130: public void setCiteCompliant(boolean citeCompliant) {
0131: this .citeCompliant = citeCompliant;
0132: }
0133:
0134: public boolean isCiteCompliant() {
0135: return citeCompliant;
0136: }
0137:
0138: protected void preprocessRequest(HttpServletRequest request)
0139: throws Exception {
0140: //set the charset
0141: Charset charSet = null;
0142:
0143: try {
0144: charSet = Charset.forName(request.getCharacterEncoding());
0145: } catch (Exception e) {
0146: //TODO: make this server settable
0147: charSet = Charset.forName("UTF-8");
0148: }
0149:
0150: request.setCharacterEncoding(charSet.name());
0151: }
0152:
0153: protected ModelAndView handleRequestInternal(
0154: HttpServletRequest httpRequest,
0155: HttpServletResponse httpResponse) throws Exception {
0156: preprocessRequest(httpRequest);
0157:
0158: //create a new request instance
0159: Request request = new Request();
0160:
0161: //set request / response
0162: request.httpRequest = httpRequest;
0163: request.httpResponse = httpResponse;
0164:
0165: Service service = null;
0166:
0167: try {
0168: //initialize the request
0169: init(request);
0170:
0171: //find the service
0172: try {
0173: service = service(request);
0174: } catch (Throwable t) {
0175: exception(t, null, request);
0176:
0177: return null;
0178: }
0179:
0180: //throw any outstanding errors
0181: if (request.error != null) {
0182: throw request.error;
0183: }
0184:
0185: //dispatch the operation
0186: Operation operation = dispatch(request, service);
0187:
0188: //execute it
0189: Object result = execute(request, operation);
0190:
0191: //write the response
0192: if (result != null) {
0193: response(result, request, operation);
0194: }
0195: } catch (AcegiSecurityException e) {
0196: // make Acegi exceptions flow so that exception transformer filter can handle them
0197: throw e;
0198: } catch (Throwable t) {
0199: exception(t, service, request);
0200: }
0201:
0202: return null;
0203: }
0204:
0205: Request init(Request request) throws ServiceException, IOException {
0206: HttpServletRequest httpRequest = request.httpRequest;
0207:
0208: //figure out method
0209: request.get = "GET".equalsIgnoreCase(httpRequest.getMethod())
0210: || "application/x-www-form-urlencoded"
0211: .equals(httpRequest.getContentType());
0212:
0213: //create the kvp map
0214: parseKVP(request);
0215:
0216: if (!request.get) {
0217: //wrap the input stream in a buffer input stream
0218: request.input = reader(httpRequest);
0219:
0220: //mark the input stream, support up to 2KB, TODO: make this configuratable
0221: request.input.mark(2048);
0222:
0223: if (logger.isLoggable(Level.FINE)) {
0224: char[] req = new char[1024];
0225: int read = request.input.read(req, 0, 1024);
0226: if (read < 1024) {
0227: logger.fine("Raw XML request starts with: "
0228: + new String(req));
0229: } else {
0230: logger.fine("Raw XML request starts with: "
0231: + new String(req) + "...");
0232: }
0233: request.input.reset();
0234: }
0235: }
0236:
0237: return request;
0238: }
0239:
0240: BufferedReader reader(HttpServletRequest httpRequest)
0241: throws IOException {
0242: //create a buffer so we can reset the input stream
0243: BufferedInputStream input = new BufferedInputStream(httpRequest
0244: .getInputStream());
0245: input.mark(2048);
0246:
0247: //create object to hold encoding info
0248: EncodingInfo encoding = new EncodingInfo();
0249:
0250: //call this method to set the encoding info
0251: XmlCharsetDetector.getCharsetAwareReader(input, encoding);
0252:
0253: //call this method to create the reader
0254: Reader reader = XmlCharsetDetector
0255: .createReader(input, encoding);
0256:
0257: //rest the input
0258: input.reset();
0259:
0260: //ensure the reader is a buffered reader
0261: if (reader instanceof BufferedReader) {
0262: return (BufferedReader) reader;
0263: }
0264:
0265: return new BufferedReader(reader);
0266: }
0267:
0268: Service service(Request req) throws Exception {
0269: //check kvp
0270: if (req.kvp != null) {
0271:
0272: req.service = normalize((String) req.kvp.get("service"));
0273: req.version = normalize((String) req.kvp.get("version"));
0274: req.request = normalize((String) req.kvp.get("request"));
0275: req.outputFormat = normalize((String) req.kvp
0276: .get("outputFormat"));
0277: }
0278: //check the body
0279: if (req.input != null) {
0280: Map xml = readOpPost(req.input);
0281: if (req.service == null) {
0282: req.service = normalize((String) xml.get("service"));
0283: }
0284: if (req.version == null) {
0285: req.version = normalize((String) xml.get("version"));
0286: }
0287: if (req.request == null) {
0288: req.request = normalize((String) xml.get("request"));
0289: }
0290: if (req.outputFormat == null) {
0291: req.outputFormat = normalize((String) xml
0292: .get("outputFormat"));
0293: }
0294: }
0295:
0296: //try to infer from context
0297: //JD: for cite compliance, a service *must* be specified explicitley by
0298: // either a kvp, or an xml attribute, however in reality the context
0299: // is often a good way to infer the service or request
0300: String service = req.service;
0301:
0302: if ((service == null) || (req.request == null)) {
0303: Map map = readOpContext(req.httpRequest);
0304:
0305: if (service == null) {
0306: service = normalize((String) map.get("service"));
0307:
0308: if ((service != null) && !citeCompliant) {
0309: req.service = service;
0310: }
0311: }
0312:
0313: if (req.request == null) {
0314: req.request = normalize((String) map.get("request"));
0315: }
0316: }
0317:
0318: if (service == null) {
0319: //give up
0320: throw new ServiceException("Could not determine service",
0321: "MissingParameterValue", "service");
0322: }
0323:
0324: //load from teh context
0325: return findService(service, req.version);
0326: }
0327:
0328: String normalize(String value) {
0329: if (value == null) {
0330: return null;
0331: }
0332:
0333: if ("".equals(value.trim())) {
0334: return null;
0335: }
0336:
0337: return value.trim();
0338: }
0339:
0340: Operation dispatch(Request req, Service serviceDescriptor)
0341: throws Throwable {
0342: if (req.request == null) {
0343: String msg = "Could not determine geoserver request from http request "
0344: + req.httpRequest;
0345: throw new ServiceException(msg, "MissingParameterValue",
0346: "request");
0347: }
0348:
0349: // lookup the operation, initial lookup based on (service,request)
0350: Object serviceBean = serviceDescriptor.getService();
0351: Method operation = OwsUtils.method(serviceBean.getClass(),
0352: req.request);
0353:
0354: if (operation == null) {
0355: String msg = "No such operation " + req;
0356: throw new ServiceException(msg, "OperationNotSupported",
0357: req.request);
0358: }
0359:
0360: //step 4: setup the paramters
0361: Object[] parameters = new Object[operation.getParameterTypes().length];
0362:
0363: for (int i = 0; i < parameters.length; i++) {
0364: Class parameterType = operation.getParameterTypes()[i];
0365:
0366: //first check for servlet request and response
0367: if (parameterType
0368: .isAssignableFrom(HttpServletRequest.class)) {
0369: parameters[i] = req.httpRequest;
0370: } else if (parameterType
0371: .isAssignableFrom(HttpServletResponse.class)) {
0372: parameters[i] = req.httpResponse;
0373: }
0374: //next check for input and output
0375: else if (parameterType.isAssignableFrom(InputStream.class)) {
0376: parameters[i] = req.httpRequest.getInputStream();
0377: } else if (parameterType
0378: .isAssignableFrom(OutputStream.class)) {
0379: parameters[i] = req.httpResponse.getOutputStream();
0380: } else {
0381: //check for a request object
0382: Object requestBean = null;
0383:
0384: if (req.kvp != null && req.kvp.size() > 0) {
0385: //use the kvp reader mechanism
0386: requestBean = parseRequestKVP(parameterType, req);
0387: }
0388: if (req.input != null) {
0389: //use the xml reader mechanism
0390: requestBean = parseRequestXML(requestBean,
0391: req.input, req);
0392: }
0393:
0394: //if no reader found for the request, throw exception
0395: //TODO: we may wish to make this configurable, as perhaps there
0396: // might be cases when the service prefers that null be passed in?
0397: if (requestBean == null) {
0398: throw new ServiceException(
0399: "Could not find request reader for: "
0400: + parameterType.getName());
0401: }
0402:
0403: // GEOS-934 and GEOS-1288
0404: Method setBaseUrl = OwsUtils.setter(requestBean
0405: .getClass(), "baseUrl", String.class);
0406: if (setBaseUrl != null) {
0407: setBaseUrl.invoke(requestBean,
0408: new String[] { RequestUtils
0409: .baseURL(req.httpRequest) });
0410: }
0411:
0412: // another couple of thos of those lovley cite things, version+service has to specified for
0413: // non capabilities request, so if we dont have either thus far, check the request
0414: // objects to try and find one
0415: // TODO: should make this configurable
0416: if (requestBean != null) {
0417: //if we dont have a version thus far, check the request object
0418: if (req.service == null) {
0419: req.service = lookupRequestBeanProperty(
0420: requestBean, "service", false);
0421: }
0422:
0423: if (req.version == null) {
0424: req.version = lookupRequestBeanProperty(
0425: requestBean, "version", false);
0426: }
0427:
0428: if (req.outputFormat == null) {
0429: req.outputFormat = lookupRequestBeanProperty(
0430: requestBean, "outputFormat", true);
0431: }
0432:
0433: parameters[i] = requestBean;
0434: }
0435: }
0436: }
0437:
0438: //if we are in cite compliant mode, do some additional checks to make
0439: // sure the "mandatory" parameters are specified, even though we
0440: // succesfully dispatched the request.
0441: if (citeCompliant) {
0442: if (!"GetCapabilities".equalsIgnoreCase(req.request)) {
0443: if (req.version == null) {
0444: //must be a version on non-capabilities requests
0445: throw new ServiceException(
0446: "Could not determine version",
0447: "MissingParameterValue", "version");
0448: } else {
0449: //version must be valid
0450: if (!req.version.matches("[0-99].[0-99].[0-99]")) {
0451: throw new ServiceException("Invalid version: "
0452: + req.version, "InvalidParameterValue",
0453: "version");
0454: }
0455:
0456: //make sure the versoin actually exists
0457: boolean found = false;
0458: Version version = new Version(req.version);
0459:
0460: for (Iterator s = loadServices().iterator(); s
0461: .hasNext();) {
0462: Service service = (Service) s.next();
0463:
0464: if (version.equals(service.getVersion())) {
0465: found = true;
0466:
0467: break;
0468: }
0469: }
0470:
0471: if (!found) {
0472: throw new ServiceException("Invalid version: "
0473: + req.version, "InvalidParameterValue",
0474: "version");
0475: }
0476: }
0477:
0478: if (req.service == null) {
0479: //give up
0480: throw new ServiceException(
0481: "Could not determine service",
0482: "MissingParameterValue", "service");
0483: }
0484: }
0485: }
0486:
0487: return new Operation(req.request, serviceDescriptor, operation,
0488: parameters);
0489: }
0490:
0491: String lookupRequestBeanProperty(Object requestBean,
0492: String property, boolean allowDefaultValues) {
0493: if (requestBean instanceof EObject
0494: && EMFUtils.has((EObject) requestBean, property)) {
0495: //special case hack for eObject, we should move
0496: // this out into an extension ppint
0497: EObject eObject = (EObject) requestBean;
0498:
0499: if (allowDefaultValues || EMFUtils.isSet(eObject, property)) {
0500: return normalize((String) EMFUtils.get(eObject,
0501: property));
0502: }
0503: } else {
0504: //straight reflection
0505: String version = (String) OwsUtils.property(requestBean,
0506: property, String.class);
0507:
0508: if (version != null) {
0509: return normalize(version);
0510: }
0511: }
0512:
0513: return null;
0514: }
0515:
0516: Object execute(Request req, Operation opDescriptor)
0517: throws Throwable {
0518: Service serviceDescriptor = opDescriptor.getService();
0519: Object serviceBean = serviceDescriptor.getService();
0520: Method operation = opDescriptor.getMethod();
0521: Object[] parameters = opDescriptor.getParameters();
0522:
0523: //step 5: execute
0524: Object result = null;
0525:
0526: try {
0527: if (securityInterceptor != null) {
0528: result = securityInterceptor.invoke(opDescriptor,
0529: operation, serviceBean, parameters);
0530: } else {
0531: result = operation.invoke(serviceBean, parameters);
0532: }
0533: } catch (InvocationTargetException e) {
0534: if (e.getTargetException() != null) {
0535: throw e.getTargetException();
0536: }
0537: }
0538:
0539: return result;
0540: }
0541:
0542: void response(Object result, Request req, Operation opDescriptor)
0543: throws Throwable {
0544: //step 6: write response
0545: if (result != null) {
0546: //look up respones
0547: List responses = GeoServerExtensions
0548: .extensions(Response.class);
0549:
0550: //first filter by binding, and canHandle
0551: O: for (Iterator itr = responses.iterator(); itr.hasNext();) {
0552: Response response = (Response) itr.next();
0553:
0554: Class binding = response.getBinding();
0555:
0556: if (!binding.isAssignableFrom(result.getClass())
0557: || !response.canHandle(opDescriptor)) {
0558: itr.remove();
0559:
0560: continue;
0561: }
0562:
0563: //filter by output format
0564: Set outputFormats = response.getOutputFormats();
0565:
0566: if ((req.outputFormat != null)
0567: && (!outputFormats.isEmpty())
0568: && !outputFormats.contains(req.outputFormat)) {
0569:
0570: //must do a case insensitive check
0571: for (Iterator of = outputFormats.iterator(); of
0572: .hasNext();) {
0573: String outputFormat = (String) of.next();
0574: if (req.outputFormat
0575: .equalsIgnoreCase(outputFormat)) {
0576: continue O;
0577: }
0578: }
0579:
0580: itr.remove();
0581: }
0582: }
0583:
0584: if (responses.isEmpty()) {
0585: String msg = "No response: ( object = "
0586: + result.getClass();
0587:
0588: if (req.outputFormat != null) {
0589: msg += (", outputFormat = " + req.outputFormat);
0590: }
0591:
0592: msg += " )";
0593:
0594: throw new RuntimeException(msg);
0595: }
0596:
0597: if (responses.size() > 1) {
0598: //sort by class hierarchy
0599: Collections.sort(responses, new Comparator() {
0600: public int compare(Object o1, Object o2) {
0601: Class c1 = ((Response) o1).getBinding();
0602: Class c2 = ((Response) o2).getBinding();
0603:
0604: if (c1.equals(c2)) {
0605: return 0;
0606: }
0607:
0608: if (c1.isAssignableFrom(c2)) {
0609: return 1;
0610: }
0611:
0612: if (c2.isAssignableFrom(c1)) {
0613: ;
0614: }
0615:
0616: return -1;
0617: }
0618: });
0619:
0620: //check first two and make sure bindings are not equal
0621: Response r1 = (Response) responses.get(0);
0622: Response r2 = (Response) responses.get(1);
0623:
0624: if (r1.getBinding().equals(r2.getBinding())) {
0625: String msg = "Multiple responses: ("
0626: + result.getClass() + ")";
0627: throw new RuntimeException(msg);
0628: }
0629: }
0630:
0631: Response response = (Response) responses.get(0);
0632:
0633: //load the output strategy to be used
0634: ServiceStrategy outputStrategy = findOutputStrategy(req.httpResponse);
0635:
0636: if (outputStrategy == null) {
0637: outputStrategy = new DefaultOutputStrategy();
0638: }
0639:
0640: //set the mime type
0641: req.httpResponse.setContentType(response.getMimeType(
0642: result, opDescriptor));
0643:
0644: //set any extra headers, other than the mime-type
0645: if (response.getHeaders(result, opDescriptor) != null) {
0646: String[][] headers = response.getHeaders(result,
0647: opDescriptor);
0648:
0649: for (int i = 0; i < headers.length; i++) {
0650: req.httpResponse.addHeader(headers[i][0],
0651: headers[i][1]);
0652: }
0653: }
0654:
0655: //TODO: initialize any header params (gzip,deflate,etc...)
0656: OutputStream output = outputStrategy
0657: .getDestination(req.httpResponse);
0658: response.write(result, output, opDescriptor);
0659:
0660: outputStrategy.flush(req.httpResponse);
0661:
0662: //flush the underlying out stream for good meaure
0663: req.httpResponse.getOutputStream().flush();
0664: }
0665: }
0666:
0667: Collection loadServices() {
0668: Collection services = GeoServerExtensions
0669: .extensions(Service.class);
0670:
0671: if (!(new HashSet(services).size() == services.size())) {
0672: String msg = "Two identical service descriptors found";
0673: throw new IllegalStateException(msg);
0674: }
0675:
0676: return services;
0677: }
0678:
0679: Service findService(String id, String ver) throws ServiceException {
0680: Version version = (ver != null) ? new Version(ver) : null;
0681: Collection services = loadServices();
0682:
0683: //first just match on service,request
0684: List matches = new ArrayList();
0685:
0686: for (Iterator itr = services.iterator(); itr.hasNext();) {
0687: Service sBean = (Service) itr.next();
0688:
0689: if (sBean.getId().equalsIgnoreCase(id)) {
0690: matches.add(sBean);
0691: }
0692: }
0693:
0694: if (matches.isEmpty()) {
0695: String msg = "No service: ( " + id + " )";
0696: throw new ServiceException(msg, "InvalidParameterValue",
0697: "service");
0698: }
0699:
0700: Service sBean = null;
0701:
0702: //if multiple, use version to filter match
0703: if (matches.size() > 1) {
0704: List vmatches = new ArrayList(matches);
0705:
0706: //match up the version
0707: if (version != null) {
0708: //version specified, look for a match
0709: for (Iterator itr = vmatches.iterator(); itr.hasNext();) {
0710: Service s = (Service) itr.next();
0711:
0712: if (version.equals(s.getVersion())) {
0713: continue;
0714: }
0715:
0716: itr.remove();
0717: }
0718:
0719: if (vmatches.isEmpty()) {
0720: //no matching version found, drop out and next step
0721: // will sort to return highest version
0722: vmatches = new ArrayList(matches);
0723: }
0724: }
0725:
0726: //multiple services found, sort by version
0727: if (vmatches.size() > 1) {
0728: //use highest version
0729: Comparator comparator = new Comparator() {
0730: public int compare(Object o1, Object o2) {
0731: Service s1 = (Service) o1;
0732: Service s2 = (Service) o2;
0733:
0734: return s1.getVersion().compareTo(
0735: s2.getVersion());
0736: }
0737: };
0738:
0739: Collections.sort(vmatches, comparator);
0740: }
0741:
0742: sBean = (Service) vmatches.get(vmatches.size() - 1);
0743: } else {
0744: //only a single match, that was easy
0745: sBean = (Service) matches.get(0);
0746: }
0747:
0748: return sBean;
0749: }
0750:
0751: Collection loadKvpRequestReaders() {
0752: Collection kvpReaders = GeoServerExtensions
0753: .extensions(KvpRequestReader.class);
0754:
0755: if (!(new HashSet(kvpReaders).size() == kvpReaders.size())) {
0756: String msg = "Two identical kvp readers found";
0757: throw new IllegalStateException(msg);
0758: }
0759:
0760: return kvpReaders;
0761: }
0762:
0763: KvpRequestReader findKvpRequestReader(Class type) {
0764: Collection kvpReaders = loadKvpRequestReaders();
0765:
0766: List matches = new ArrayList();
0767:
0768: for (Iterator itr = kvpReaders.iterator(); itr.hasNext();) {
0769: KvpRequestReader kvpReader = (KvpRequestReader) itr.next();
0770:
0771: if (kvpReader.getRequestBean().isAssignableFrom(type)) {
0772: matches.add(kvpReader);
0773: }
0774: }
0775:
0776: if (matches.isEmpty()) {
0777: //try to instantiate one
0778: String msg = "No kvp reader: ( " + type + " )";
0779: throw new RuntimeException(msg);
0780: }
0781:
0782: if (matches.size() > 1) {
0783: //sort by class hierarchy
0784: Comparator comparator = new Comparator() {
0785: public int compare(Object o1, Object o2) {
0786: KvpRequestReader kvp1 = (KvpRequestReader) o1;
0787: KvpRequestReader kvp2 = (KvpRequestReader) o2;
0788:
0789: if (kvp2.getRequestBean().isAssignableFrom(
0790: kvp1.getRequestBean())) {
0791: return -1;
0792: }
0793:
0794: return 1;
0795: }
0796: };
0797:
0798: Collections.sort(matches, comparator);
0799: }
0800:
0801: return (KvpRequestReader) matches.get(0);
0802: }
0803:
0804: Collection loadXmlReaders() {
0805: Collection xmlReaders = GeoServerExtensions
0806: .extensions(XmlRequestReader.class);
0807:
0808: if (!(new HashSet(xmlReaders).size() == xmlReaders.size())) {
0809: String msg = "Two identical xml readers found";
0810: throw new IllegalStateException(msg);
0811: }
0812:
0813: return xmlReaders;
0814: }
0815:
0816: XmlRequestReader findXmlReader(String namespace, String element, /*String serviceId,*/
0817: String ver) {
0818: Collection xmlReaders = loadXmlReaders();
0819:
0820: //first just match on namespace, element
0821: List matches = new ArrayList();
0822:
0823: for (Iterator itr = xmlReaders.iterator(); itr.hasNext();) {
0824: XmlRequestReader xmlReader = (XmlRequestReader) itr.next();
0825: QName xmlElement = xmlReader.getElement();
0826:
0827: if (xmlElement.getLocalPart().equalsIgnoreCase(element)) {
0828: if (xmlElement.getNamespaceURI().equalsIgnoreCase(
0829: namespace)) {
0830: matches.add(xmlReader);
0831: }
0832: }
0833: }
0834:
0835: if (matches.isEmpty()) {
0836: //do a more lax serach, search only on the element name if the
0837: // namespace was unspecified
0838: if (namespace == null || namespace.equals("")) {
0839: String msg = "No namespace specified in request, searching for "
0840: + " xml reader by element name only";
0841: logger.info(msg);
0842:
0843: for (Iterator itr = xmlReaders.iterator(); itr
0844: .hasNext();) {
0845: XmlRequestReader xmlReader = (XmlRequestReader) itr
0846: .next();
0847: if (xmlReader.getElement().getLocalPart().equals(
0848: element)) {
0849: matches.add(xmlReader);
0850: }
0851: }
0852:
0853: if (!matches.isEmpty()) {
0854: //we found some matches, make sure they are all in the
0855: // same namespace
0856: Iterator itr = matches.iterator();
0857: XmlRequestReader first = (XmlRequestReader) itr
0858: .next();
0859: while (itr.hasNext()) {
0860: XmlRequestReader xmlReader = (XmlRequestReader) itr
0861: .next();
0862: if (!first.getElement().equals(
0863: xmlReader.getElement())) {
0864: //abort
0865: matches.clear();
0866: break;
0867: }
0868: }
0869: }
0870: }
0871: }
0872:
0873: if (matches.isEmpty()) {
0874: String msg = "No xml reader: (" + namespace + "," + element
0875: + ")";
0876: logger.info(msg);
0877: return null;
0878: }
0879:
0880: XmlRequestReader xmlReader = null;
0881:
0882: //if multiple, use version to filter match
0883: if (matches.size() > 1) {
0884: List vmatches = new ArrayList(matches);
0885:
0886: //match up the version and the service
0887: if (ver != null) {
0888: Version version = new Version(ver);
0889:
0890: //version specified, look for a match
0891: for (Iterator itr = vmatches.iterator(); itr.hasNext();) {
0892: XmlRequestReader r = (XmlRequestReader) itr.next();
0893:
0894: if (version.equals(r.getVersion())) {
0895: continue;
0896: }
0897:
0898: itr.remove();
0899: }
0900:
0901: if (vmatches.isEmpty()) {
0902: //no matching version found, drop out and next step
0903: // will sort to return highest version
0904: vmatches = new ArrayList(matches);
0905: }
0906: }
0907:
0908: //multiple readers found, sort by version and by service match
0909: if (vmatches.size() > 1) {
0910: //use highest version
0911: Comparator comparator = new Comparator() {
0912: public int compare(Object o1, Object o2) {
0913: XmlRequestReader r1 = (XmlRequestReader) o1;
0914: XmlRequestReader r2 = (XmlRequestReader) o2;
0915:
0916: Version v1 = r1.getVersion();
0917: Version v2 = r2.getVersion();
0918:
0919: if ((v1 == null) && (v2 == null)) {
0920: return 0;
0921: }
0922:
0923: if ((v1 != null) && (v2 == null)) {
0924: return 1;
0925: }
0926:
0927: if ((v1 == null) && (v2 != null)) {
0928: return -1;
0929: }
0930:
0931: int versionCompare = v1.compareTo(v2);
0932:
0933: if (versionCompare != 0) {
0934: return versionCompare;
0935: }
0936:
0937: String sid1 = r1.getServiceId();
0938: String sid2 = r2.getServiceId();
0939:
0940: if ((sid1 == null) && (sid2 == null)) {
0941: return 0;
0942: }
0943:
0944: if ((sid1 != null) && (sid2 == null)) {
0945: return 1;
0946: }
0947:
0948: if ((sid1 == null) && (sid2 != null)) {
0949: return -1;
0950: }
0951:
0952: return sid1.compareTo(sid2);
0953: }
0954: };
0955:
0956: Collections.sort(vmatches, comparator);
0957: }
0958:
0959: xmlReader = (XmlRequestReader) vmatches
0960: .get(vmatches.size() - 1);
0961: } else {
0962: //only a single match, that was easy
0963: xmlReader = (XmlRequestReader) matches.get(0);
0964: }
0965:
0966: return xmlReader;
0967: }
0968:
0969: ServiceStrategy findOutputStrategy(HttpServletResponse response) {
0970: OutputStrategyFactory factory = null;
0971: try {
0972: factory = (OutputStrategyFactory) GeoServerExtensions
0973: .bean("serviceStrategyFactory");
0974: } catch (NoSuchBeanDefinitionException e) {
0975: return null;
0976: }
0977: return factory.createOutputStrategy(response);
0978: }
0979:
0980: BufferedInputStream input(File cache) throws IOException {
0981: return (cache == null) ? null : new BufferedInputStream(
0982: new FileInputStream(cache));
0983: }
0984:
0985: void parseKVP(Request req) throws ServiceException {
0986: HttpServletRequest request = req.httpRequest;
0987:
0988: //unparsed kvp set
0989: Map kvp = request.getParameterMap();
0990:
0991: if (kvp == null || kvp.isEmpty()) {
0992: req.kvp = Collections.EMPTY_MAP;
0993: //req.kvp = null;
0994: return;
0995: }
0996:
0997: //look up parser objects
0998: Collection parsers = GeoServerExtensions
0999: .extensions(KvpParser.class);
1000: Map parsedKvp = new KvpMap();
1001: Map rawKvp = new KvpMap();
1002:
1003: for (Iterator itr = kvp.entrySet().iterator(); itr.hasNext();) {
1004: Map.Entry entry = (Map.Entry) itr.next();
1005: String key = (String) entry.getKey();
1006: String value = null;
1007:
1008: if (entry.getValue() instanceof String) {
1009: value = (String) entry.getValue();
1010: } else if (entry.getValue() instanceof String[]) {
1011: //TODO: perhaps handle multiple values for a key
1012: value = (String) ((String[]) entry.getValue())[0];
1013: }
1014:
1015: //trim the string
1016: if (value != null) {
1017: value = value.trim();
1018: }
1019:
1020: //find the parser for this key value pair
1021: Object parsed = null;
1022:
1023: for (Iterator pitr = parsers.iterator(); pitr.hasNext();) {
1024: KvpParser parser = (KvpParser) pitr.next();
1025:
1026: if (key.equalsIgnoreCase(parser.getKey())) {
1027: try {
1028: parsed = parser.parse(value);
1029: } catch (Throwable t) {
1030: //dont throw any exceptions yet, befor the service is
1031: // known
1032: req.error = t;
1033: }
1034: }
1035: }
1036:
1037: //if noone could parse, just set to string value
1038: if (parsed == null) {
1039: parsed = value;
1040: }
1041:
1042: //convert key to lowercase
1043: parsedKvp.put(key.toLowerCase(), parsed);
1044: rawKvp.put(key.toLowerCase(), value);
1045: }
1046:
1047: req.kvp = parsedKvp;
1048: req.rawKvp = rawKvp;
1049: }
1050:
1051: Object parseRequestKVP(Class type, Request request)
1052: throws Exception {
1053: KvpRequestReader kvpReader = findKvpRequestReader(type);
1054:
1055: if (kvpReader != null) {
1056: //check for http request awareness
1057: if (kvpReader instanceof HttpServletRequestAware) {
1058: ((HttpServletRequestAware) kvpReader)
1059: .setHttpRequest(request.httpRequest);
1060: }
1061:
1062: Object requestBean = kvpReader.createRequest();
1063:
1064: if (requestBean != null) {
1065: requestBean = kvpReader.read(requestBean, request.kvp,
1066: request.rawKvp);
1067: }
1068:
1069: return requestBean;
1070: }
1071:
1072: return null;
1073: }
1074:
1075: Object parseRequestXML(Object requestBean, BufferedReader input,
1076: Request request) throws Exception {
1077: //check for an empty input stream
1078: //if (input.available() == 0) {
1079: if (!input.ready()) {
1080: return null;
1081: }
1082:
1083: //create stream parser
1084: XmlPullParserFactory factory = XmlPullParserFactory
1085: .newInstance();
1086: factory.setNamespaceAware(true);
1087: factory.setValidating(false);
1088:
1089: //parse root element
1090: XmlPullParser parser = factory.newPullParser();
1091: //parser.setInput(input, "UTF-8");
1092: parser.setInput(input);
1093: parser.nextTag();
1094:
1095: String namespace = (parser.getNamespace() != null) ? parser
1096: .getNamespace() : "";
1097: String element = parser.getName();
1098: String version = null;
1099:
1100: for (int i = 0; i < parser.getAttributeCount(); i++) {
1101: if ("version".equals(parser.getAttributeName(i))) {
1102: version = parser.getAttributeValue(i);
1103:
1104: break;
1105: }
1106: }
1107:
1108: parser.setInput(null);
1109:
1110: //reset input stream
1111: input.reset();
1112:
1113: XmlRequestReader xmlReader = findXmlReader(namespace, element,
1114: version);
1115: if (xmlReader == null) {
1116: //no xml reader, just return object passed in
1117: return requestBean;
1118: }
1119:
1120: if (xmlReader instanceof HttpServletRequestAware) {
1121: ((HttpServletRequestAware) xmlReader)
1122: .setHttpRequest(request.httpRequest);
1123: }
1124:
1125: //return xmlReader.read(input);
1126: return xmlReader.read(requestBean, input, request.kvp);
1127: }
1128:
1129: Map readOpContext(HttpServletRequest request) {
1130: //try to get from request url
1131: String ctxPath = request.getContextPath();
1132: String reqPath = request.getRequestURI();
1133: reqPath = reqPath.substring(ctxPath.length());
1134:
1135: if (reqPath.startsWith("/")) {
1136: reqPath = reqPath.substring(1, reqPath.length());
1137: }
1138:
1139: if (reqPath.endsWith("/")) {
1140: reqPath = reqPath.substring(0, reqPath.length() - 1);
1141: }
1142:
1143: Map map = new HashMap();
1144: int index = reqPath.indexOf('/');
1145:
1146: if (index != -1) {
1147: map.put("service", reqPath.substring(0, index));
1148: map.put("request", reqPath.substring(index + 1));
1149: } else {
1150: map.put("service", reqPath);
1151: }
1152:
1153: return map;
1154: }
1155:
1156: Map readOpPost(BufferedReader input) throws Exception {
1157: //create stream parser
1158: XmlPullParserFactory factory = XmlPullParserFactory
1159: .newInstance();
1160: factory.setNamespaceAware(true);
1161: factory.setValidating(false);
1162:
1163: //parse root element
1164: XmlPullParser parser = factory.newPullParser();
1165: parser.setInput(input);
1166: parser.nextTag();
1167:
1168: Map map = new HashMap();
1169: map.put("request", parser.getName());
1170:
1171: for (int i = 0; i < parser.getAttributeCount(); i++) {
1172: String attName = parser.getAttributeName(i);
1173:
1174: if ("service".equals(attName)) {
1175: map.put("service", parser.getAttributeValue(i));
1176: }
1177:
1178: if ("version".equals(parser.getAttributeName(i))) {
1179: map.put("version", parser.getAttributeValue(i));
1180: }
1181:
1182: if ("outputFormat".equals(attName)) {
1183: map.put("outputFormat", parser.getAttributeValue(i));
1184: }
1185: }
1186:
1187: //close parser + release resources
1188: parser.setInput(null);
1189:
1190: //reset the input stream
1191: input.reset();
1192:
1193: return map;
1194: }
1195:
1196: void exception(Throwable t, Service service, Request request) {
1197: {
1198: Throwable current = t;
1199: while (current != null
1200: && !(current instanceof ClientStreamAbortedException)) {
1201: current = current.getCause();
1202: }
1203: if (current instanceof ClientStreamAbortedException) {
1204: logger.log(Level.FINER, "Client has closed stream");
1205: return;
1206: }
1207: }
1208: logger.log(Level.WARNING, "", t);
1209:
1210: //wrap in service exception if necessary
1211: ServiceException se = null;
1212:
1213: if (t instanceof ServiceException) {
1214: se = (ServiceException) t;
1215: } else {
1216: //unwind the exception stack, look for a service exception
1217: Throwable cause = t.getCause();
1218:
1219: while (cause != null) {
1220: if (cause instanceof ServiceException) {
1221: ServiceException cse = (ServiceException) cause;
1222: se = new ServiceException(cse.getMessage(), t, cse
1223: .getCode(), cse.getLocator());
1224:
1225: break;
1226: }
1227:
1228: cause = cause.getCause();
1229: }
1230: }
1231:
1232: if (se == null) {
1233: //couldn't find one, just wrap in one
1234: se = new ServiceException(t);
1235: }
1236:
1237: //find an exception handler
1238: ServiceExceptionHandler handler = null;
1239:
1240: if (service != null) {
1241: //look up the service exception handler
1242: Collection handlers = GeoServerExtensions
1243: .extensions(ServiceExceptionHandler.class);
1244: for (Iterator h = handlers.iterator(); h.hasNext();) {
1245: ServiceExceptionHandler seh = (ServiceExceptionHandler) h
1246: .next();
1247:
1248: if (seh.getServices().contains(service)) {
1249: //found one,
1250: handler = seh;
1251:
1252: break;
1253: }
1254: }
1255: }
1256:
1257: if (handler == null) {
1258: //none found, fall back on default
1259: handler = new DefaultServiceExceptionHandler();
1260: }
1261:
1262: handler.handleServiceException(se, service,
1263: request.httpRequest, request.httpResponse);
1264: }
1265:
1266: /**
1267: * Map which makes keys case insensitive.
1268: *
1269: * @author Justin Deoliveira, The Open Planning Project
1270: *
1271: */
1272: private static class KvpMap extends HashMap {
1273: private static final long serialVersionUID = 1L;
1274:
1275: public boolean containsKey(Object key) {
1276: return super .containsKey(upper(key));
1277: }
1278:
1279: public Object get(Object key) {
1280: return super .get(upper(key));
1281: }
1282:
1283: public Object put(Object key, Object value) {
1284: return super .put(upper(key), value);
1285: }
1286:
1287: Object upper(Object key) {
1288: if ((key != null) && key instanceof String) {
1289: return ((String) key).toUpperCase();
1290: }
1291:
1292: return key;
1293: }
1294: }
1295:
1296: /**
1297: * Helper class to hold attributes of hte request
1298: *
1299: */
1300: static class Request {
1301: /**
1302: * Http request / response
1303: */
1304: HttpServletRequest httpRequest;
1305: HttpServletResponse httpResponse;
1306:
1307: /**
1308: * flag indicating if the request is get
1309: */
1310: boolean get;
1311:
1312: /**
1313: * Kvp parameters, only non-null if get = true
1314: */
1315: Map kvp;
1316:
1317: /**
1318: * raw kvp parameters, unparsed
1319: */
1320: Map rawKvp;
1321: /**
1322: * buffered input stream, only non-null if get = false
1323: */
1324: BufferedReader input;
1325:
1326: /**
1327: * The ows service,request,version
1328: */
1329: String service;
1330: String request;
1331: String version;
1332:
1333: /**
1334: * The output format of hte request
1335: */
1336: String outputFormat;
1337:
1338: /**
1339: * Any errors that occur tryinng to determine the service
1340: */
1341: Throwable error;
1342:
1343: public String toString() {
1344: return service + " " + version + " " + request;
1345: }
1346: }
1347:
1348: /**
1349: * Allows setting up a security interceptor that will allow security checks around
1350: * service invocations
1351: * @return
1352: */
1353: public OperationInterceptor getSecurityInterceptor() {
1354: return securityInterceptor;
1355: }
1356:
1357: public void setSecurityInterceptor(OperationInterceptor interceptor) {
1358: this.securityInterceptor = interceptor;
1359: }
1360: }
|