View Javadoc

1   /**
2    * Copyright (c) 2011, University of Konstanz, Distributed Systems Group
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    *     * Redistributions of source code must retain the above copyright
8    *       notice, this list of conditions and the following disclaimer.
9    *     * Redistributions in binary form must reproduce the above copyright
10   *       notice, this list of conditions and the following disclaimer in the
11   *       documentation and/or other materials provided with the distribution.
12   *     * Neither the name of the University of Konstanz nor the
13   *       names of its contributors may be used to endorse or promote products
14   *       derived from this software without specific prior written permission.
15   *
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package org.jaxrx.resource;
28  
29  import static org.jaxrx.core.JaxRxConstants.*;
30  import java.io.InputStream;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.Properties;
34  import java.util.Scanner;
35  import java.util.Set;
36  
37  import javax.ws.rs.core.HttpHeaders;
38  import javax.ws.rs.core.MediaType;
39  import javax.ws.rs.core.MultivaluedMap;
40  import javax.ws.rs.core.Response;
41  import javax.ws.rs.core.StreamingOutput;
42  import javax.ws.rs.core.UriInfo;
43  import org.jaxrx.JaxRx;
44  import org.jaxrx.core.JaxRxException;
45  import org.jaxrx.core.QueryParameter;
46  import org.jaxrx.core.ResourcePath;
47  import org.jaxrx.core.SchemaChecker;
48  import org.jaxrx.core.Systems;
49  import org.w3c.dom.Document;
50  import org.w3c.dom.NamedNodeMap;
51  import org.w3c.dom.Node;
52  import org.w3c.dom.NodeList;
53  
54  /**
55   * This is an abstract resource class, which assembles common methods from
56   * resource implementations.
57   * 
58   * @author Sebastian Graf, Christian Gruen, Lukas Lewandowski, University of
59   *         Konstanz
60   */
61  abstract class AResource {
62    /**
63     * Content type for query expressions.
64     */
65    protected static final String APPLICATION_QUERY_XML = "application/query+xml";
66  
67    /**
68     * Returns a stream output, depending on the query parameters.
69     * 
70     * @param impl implementation
71     * @param path path info
72     * 
73     * @return parameter map
74     */
75    private StreamingOutput createOutput(final JaxRx impl, final ResourcePath path) {
76  
77      // check for command parameter
78      String qu = path.getValue(QueryParameter.COMMAND);
79      if(qu != null) {
80        return impl.command(qu, path);
81      }
82  
83      // check for run parameter
84      qu = path.getValue(QueryParameter.RUN);
85      if(qu != null) {
86        return impl.run(qu, path);
87      }
88  
89      // check for query parameter
90      qu = path.getValue(QueryParameter.QUERY);
91      if(qu != null) {
92        return impl.query(qu, path);
93      }
94  
95      // no parameter found
96      return impl.get(path);
97    }
98  
99    /**
100    * Returns a result, depending on the query parameters.
101    * 
102    * @param impl implementation
103    * @param path path info
104    * 
105    * @return parameter map
106    */
107   Response createResponse(final JaxRx impl, final ResourcePath path) {
108     final StreamingOutput out = createOutput(impl, path);
109 
110     // change media type, dependent on WRAP value
111     final boolean wrap = path.getValue(QueryParameter.WRAP) == null
112         || path.getValue(QueryParameter.WRAP).equals("yes");
113     String type = wrap ? MediaType.APPLICATION_XML : MediaType.TEXT_PLAIN;
114 
115     // overwrite type if METHOD or MEDIA-TYPE parameters are specified
116     final String op = path.getValue(QueryParameter.OUTPUT);
117     if(op != null) {
118       final Scanner sc = new Scanner(op);
119       sc.useDelimiter(",");
120       while(sc.hasNext()) {
121         final String[] sp = sc.next().split("=", 2);
122         if(sp.length == 1) continue;
123         if(sp[0].equals(METHOD)) {
124           for(final String[] m : METHODS)
125             if(sp[1].equals(m[0])) type = m[1];
126         } else if(sp[0].equals(MEDIATYPE)) {
127           type = sp[1];
128         }
129       }
130     }
131 
132     // check validity of media type
133     MediaType mt = null;
134     try {
135       mt = MediaType.valueOf(type);
136     } catch(final IllegalArgumentException ex) {
137       throw new JaxRxException(400, ex.getMessage());
138     }
139     return Response.ok(out, mt).build();
140 
141   }
142 
143   /**
144    * Extracts and returns query parameters from the specified map. If a
145    * parameter is specified multiple times, its values will be separated with
146    * tab characters.
147    * 
148    * @param uri uri info with query parameters
149    * @param jaxrx JAX-RX implementation
150    * @return The parameters as {@link Map}.
151    */
152   protected Map<QueryParameter, String> getParameters(final UriInfo uri,
153       final JaxRx jaxrx) {
154 
155     final MultivaluedMap<String, String> params = uri.getQueryParameters();
156     final Map<QueryParameter, String> newParam = createMap();
157     final Set<QueryParameter> impl = jaxrx.getParameters();
158 
159     for(final String key : params.keySet()) {
160       for(final String s : params.get(key)) {
161         addParameter(key, s, newParam, impl);
162       }
163     }
164     return newParam;
165   }
166 
167   /**
168    * Extracts and returns query parameters, variables, and output options from
169    * the specified document instance. The keys and values of variables are
170    * separated with the control code {@code '\2'}.
171    * 
172    * @param doc The XML {@link Document} containing the XQuery XML post request.
173    * @param jaxrx current implementation
174    * @return The parameters as {@link Map}.
175    */
176   protected Map<QueryParameter, String> getParameters(final Document doc,
177       final JaxRx jaxrx) {
178 
179     final Map<QueryParameter, String> newParams = createMap();
180     final Set<QueryParameter> impl = jaxrx.getParameters();
181 
182     // store name of root element and contents of text node
183     final String root = doc.getDocumentElement().getNodeName();
184     final QueryParameter ep = QueryParameter.valueOf(root.toUpperCase());
185     newParams.put(ep, doc.getElementsByTagName("text").item(0).getTextContent());
186 
187     // add additional parameters
188     NodeList props = doc.getElementsByTagName("parameter");
189     for(int i = 0; i < props.getLength(); i++) {
190       final NamedNodeMap nnm = props.item(i).getAttributes();
191       addParameter(nnm.getNamedItem("name").getNodeValue(),
192           nnm.getNamedItem("value").getNodeValue(), newParams, impl);
193     }
194     // add additional variables; tab characters are used as delimiters
195     props = doc.getElementsByTagName("variable");
196     for(int i = 0; i < props.getLength(); i++) {
197       final NamedNodeMap nnm = props.item(i).getAttributes();
198       // use \2 as delimiter for keys, values, and optional data types
199       String val = nnm.getNamedItem("name").getNodeValue() + '\2'
200           + nnm.getNamedItem("value").getNodeValue();
201       final Node type = nnm.getNamedItem("type");
202       if(type != null) val += '\2' + type.getNodeValue();
203       addParameter("var", val, newParams, impl);
204     }
205     // add additional variables; tab characters are used as delimiters
206     props = doc.getElementsByTagName("output");
207     for(int i = 0; i < props.getLength(); i++) {
208       final NamedNodeMap nnm = props.item(i).getAttributes();
209       // use \2 as delimiter for keys, values, and optional data types
210       final String val = nnm.getNamedItem("name").getNodeValue() + '='
211           + nnm.getNamedItem("value").getNodeValue();
212       addParameter("output", val, newParams, impl);
213     }
214     return newParams;
215   }
216 
217   /**
218    * Adds a key/value combination to the parameter map. Multiple output
219    * parameters are separated with commas.
220    * 
221    * @param key The parameter key
222    * @param value The parameter value
223    * @param newParams New query parameter map
224    * @param impl Implementation parameters
225    */
226   private void addParameter(final String key, final String value,
227       final Map<QueryParameter, String> newParams,
228       final Set<QueryParameter> impl) {
229 
230     try {
231       final QueryParameter ep = QueryParameter.valueOf(key.toUpperCase());
232       if(!impl.contains(ep)) {
233         throw new JaxRxException(400, "Parameter '" + key
234             + "' is not supported by the implementation.");
235       }
236 
237       // append multiple parameters
238       final String old = newParams.get(ep);
239       // skip multiple key/value combinations if different to OUTPUT
240       if(ep != QueryParameter.OUTPUT && ep != QueryParameter.VAR && old != null) return;
241 
242       // use \1 as delimiter for multiple values
243       final char del = ep == QueryParameter.OUTPUT ? ',' : 0x01;
244       newParams.put(ep, old == null ? value : old + del + value);
245     } catch(final IllegalArgumentException ex) {
246       throw new JaxRxException(400, "Parameter '" + key + "' is unknown.");
247     }
248   }
249 
250   /**
251    * Returns a fresh parameter map. This map contains all parameters as defaults
252    * which have been specified by the user via system properties with the
253    * pattern "org.jaxrx.parameter.KEY" as key.
254    * 
255    * @return parameter map
256    */
257   private Map<QueryParameter, String> createMap() {
258     final Map<QueryParameter, String> params = new HashMap<QueryParameter, String>();
259 
260     final Properties props = System.getProperties();
261     for(final Map.Entry<Object, Object> set : props.entrySet()) {
262       final String key = set.getKey().toString();
263       final String up = key.replace("org.jaxrx.parameter.", "");
264       if(key.equals(up)) continue;
265       try {
266         params.put(QueryParameter.valueOf(up.toUpperCase()),
267             set.getValue().toString());
268       } catch(final IllegalArgumentException ex) { /* ignore */
269       }
270     }
271 
272     return params;
273   }
274 
275   /**
276    * This method will be called when a HTTP client sends a POST request to an
277    * existing resource with 'application/query+xml' as Content-Type.
278    * 
279    * @param system The implementation system.
280    * @param input The input stream.
281    * @param resource The resource
282    * @param httpHeaders HTTP header attributes.
283    * @return The {@link Response} which can be empty when no response is
284    *         expected. Otherwise it holds the response XML file.
285    */
286   public Response postQuery(final String system, final InputStream input,
287       final String resource, final HttpHeaders httpHeaders) {
288 
289     final JaxRx impl = Systems.getInstance(system);
290     final Document doc = new SchemaChecker("post").check(input);
291     final Map<QueryParameter, String> param = getParameters(doc, impl);
292     final ResourcePath path = new ResourcePath(resource, param, httpHeaders);
293     return createResponse(impl, path);
294   }
295 
296   /**
297    * This method will be called when a HTTP client sends a POST request to an
298    * existing resource with 'application/query+xml' as Content-Type.
299    * 
300    * @param system The implementation system.
301    * @param uri The context information due to the requested URI.
302    * @param resource The resource
303    * @param headers HTTP header attributes.
304    * @return The {@link Response} which can be empty when no response is
305    *         expected. Otherwise it holds the response XML file.
306    */
307   public Response getResource(final String system, final UriInfo uri,
308       final String resource, final HttpHeaders headers) {
309 
310     final JaxRx impl = Systems.getInstance(system);
311     final Map<QueryParameter, String> param = getParameters(uri, impl);
312     final ResourcePath path = new ResourcePath(resource, param, headers);
313     return createResponse(impl, path);
314   }
315 }