| 1 | /** | 
| 2 |  * Licensed to jclouds, Inc. (jclouds) under one or more | 
| 3 |  * contributor license agreements.  See the NOTICE file | 
| 4 |  * distributed with this work for additional information | 
| 5 |  * regarding copyright ownership.  jclouds licenses this file | 
| 6 |  * to you under the Apache License, Version 2.0 (the | 
| 7 |  * "License"); you may not use this file except in compliance | 
| 8 |  * with the License.  You may obtain a copy of the License at | 
| 9 |  * | 
| 10 |  *   http://www.apache.org/licenses/LICENSE-2.0 | 
| 11 |  * | 
| 12 |  * Unless required by applicable law or agreed to in writing, | 
| 13 |  * software distributed under the License is distributed on an | 
| 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
| 15 |  * KIND, either express or implied.  See the License for the | 
| 16 |  * specific language governing permissions and limitations | 
| 17 |  * under the License. | 
| 18 |  */ | 
| 19 | package org.jclouds.http.functions; | 
| 20 |   | 
| 21 | import static com.google.common.base.Preconditions.checkArgument; | 
| 22 | import static com.google.common.base.Preconditions.checkNotNull; | 
| 23 | import static com.google.common.io.Closeables.closeQuietly; | 
| 24 |   | 
| 25 | import java.io.IOException; | 
| 26 | import java.io.InputStream; | 
| 27 | import java.io.StringReader; | 
| 28 |   | 
| 29 | import javax.inject.Inject; | 
| 30 |   | 
| 31 | import org.jclouds.http.HttpRequest; | 
| 32 | import org.jclouds.http.HttpResponse; | 
| 33 | import org.jclouds.rest.InvocationContext; | 
| 34 | import org.jclouds.rest.internal.GeneratedHttpRequest; | 
| 35 | import org.jclouds.util.Strings2; | 
| 36 | import org.xml.sax.InputSource; | 
| 37 | import org.xml.sax.SAXException; | 
| 38 | import org.xml.sax.SAXParseException; | 
| 39 | import org.xml.sax.XMLReader; | 
| 40 | import org.xml.sax.helpers.DefaultHandler; | 
| 41 |   | 
| 42 | import com.google.common.base.Function; | 
| 43 | import com.google.common.base.Throwables; | 
| 44 |   | 
| 45 | /** | 
| 46 |  * This object will parse the body of an HttpResponse and return the result of type <T> back to the | 
| 47 |  * caller. | 
| 48 |  *  | 
| 49 |  * @author Adrian Cole | 
| 50 |  */ | 
| 51 | public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext<ParseSax<T>> { | 
| 52 |   | 
| 53 |    private final XMLReader parser; | 
| 54 |    private final HandlerWithResult<T> handler; | 
| 55 |    private HttpRequest request; | 
| 56 |   | 
| 57 |    public static interface Factory { | 
| 58 |       <T> ParseSax<T> create(HandlerWithResult<T> handler); | 
| 59 |    } | 
| 60 |   | 
| 61 |    @Inject | 
| 62 |    public ParseSax(XMLReader parser, HandlerWithResult<T> handler) { | 
| 63 |       this.parser = checkNotNull(parser, "parser"); | 
| 64 |       this.handler = checkNotNull(handler, "handler"); | 
| 65 |    } | 
| 66 |   | 
| 67 |    public T apply(HttpResponse from) { | 
| 68 |       try { | 
| 69 |          checkNotNull(from, "http response"); | 
| 70 |          checkNotNull(from.getPayload(), "payload in " + from); | 
| 71 |       } catch (NullPointerException e) { | 
| 72 |          return addDetailsAndPropagate(from, e); | 
| 73 |       } | 
| 74 |       if (from.getStatusCode() >= 300) | 
| 75 |          return convertStreamToStringAndParse(from); | 
| 76 |       InputStream is = from.getPayload().getInput(); | 
| 77 |       try { | 
| 78 |          return parse(new InputSource(is)); | 
| 79 |       } catch (RuntimeException e) { | 
| 80 |          return addDetailsAndPropagate(from, e); | 
| 81 |       } finally { | 
| 82 |          closeQuietly(is); | 
| 83 |       } | 
| 84 |    } | 
| 85 |   | 
| 86 |    private T convertStreamToStringAndParse(HttpResponse from) { | 
| 87 |       try { | 
| 88 |          return parse(Strings2.toStringAndClose(from.getPayload().getInput())); | 
| 89 |       } catch (Exception e) { | 
| 90 |          return addDetailsAndPropagate(from, e); | 
| 91 |       } | 
| 92 |    } | 
| 93 |   | 
| 94 |    public T parse(String from) { | 
| 95 |       try { | 
| 96 |          checkNotNull(from, "xml string"); | 
| 97 |          checkArgument(from.indexOf('<') >= 0, String.format("not an xml document [%s] ", from)); | 
| 98 |       } catch (RuntimeException e) { | 
| 99 |          return addDetailsAndPropagate(null, e); | 
| 100 |       } | 
| 101 |       return parse(new InputSource(new StringReader(from))); | 
| 102 |    } | 
| 103 |   | 
| 104 |    public T parse(InputStream from) { | 
| 105 |       try { | 
| 106 |          return parse(new InputSource(from)); | 
| 107 |       } finally { | 
| 108 |          closeQuietly(from); | 
| 109 |       } | 
| 110 |    } | 
| 111 |   | 
| 112 |    public T parse(InputSource from) { | 
| 113 |       try { | 
| 114 |          return doParse(from); | 
| 115 |       } catch (Exception e) { | 
| 116 |          return addDetailsAndPropagate(null, e); | 
| 117 |       } | 
| 118 |    } | 
| 119 |   | 
| 120 |    protected T doParse(InputSource from) throws IOException, SAXException { | 
| 121 |       checkNotNull(from, "xml inputsource"); | 
| 122 |       from.setEncoding("UTF-8"); | 
| 123 |       parser.setContentHandler(getHandler()); | 
| 124 |       // This method should accept documents with a BOM (Byte-order mark) | 
| 125 |       parser.parse(from); | 
| 126 |       return getHandler().getResult(); | 
| 127 |    } | 
| 128 |   | 
| 129 |    public T addDetailsAndPropagate(HttpResponse response, Exception e) { | 
| 130 |       StringBuilder message = new StringBuilder(); | 
| 131 |       if (request != null) { | 
| 132 |          message.append("request: ").append(request.getRequestLine()); | 
| 133 |       } | 
| 134 |       if (response != null) { | 
| 135 |          if (message.length() != 0) | 
| 136 |             message.append("; "); | 
| 137 |          message.append("response: ").append(response.getStatusLine()); | 
| 138 |       } | 
| 139 |       if (e instanceof SAXParseException) { | 
| 140 |          SAXParseException parseException = (SAXParseException) e; | 
| 141 |          String systemId = parseException.getSystemId(); | 
| 142 |          if (systemId == null) { | 
| 143 |             systemId = ""; | 
| 144 |          } | 
| 145 |          if (message.length() != 0) | 
| 146 |             message.append("; "); | 
| 147 |          message.append(String.format("error at %d:%d in document %s", parseException.getColumnNumber(), | 
| 148 |                parseException.getLineNumber(), systemId)); | 
| 149 |       } | 
| 150 |       if (message.length() != 0) { | 
| 151 |          message.append("; cause: ").append(e.toString()); | 
| 152 |          throw new RuntimeException(message.toString(), e); | 
| 153 |       } else { | 
| 154 |          Throwables.propagate(e); | 
| 155 |          return null; | 
| 156 |       } | 
| 157 |   | 
| 158 |    } | 
| 159 |   | 
| 160 |    public HandlerWithResult<T> getHandler() { | 
| 161 |       return handler; | 
| 162 |    } | 
| 163 |   | 
| 164 |    /** | 
| 165 |     * Handler that produces a useable domain object accessible after parsing completes. | 
| 166 |     *  | 
| 167 |     * @author Adrian Cole | 
| 168 |     */ | 
| 169 |    public abstract static class HandlerWithResult<T> extends DefaultHandler implements | 
| 170 |          InvocationContext<HandlerWithResult<T>> { | 
| 171 |       private HttpRequest request; | 
| 172 |   | 
| 173 |       protected HttpRequest getRequest() { | 
| 174 |          return request; | 
| 175 |       } | 
| 176 |   | 
| 177 |       public abstract T getResult(); | 
| 178 |   | 
| 179 |       @Override | 
| 180 |       public HandlerWithResult<T> setContext(HttpRequest request) { | 
| 181 |          this.request = request; | 
| 182 |          return this; | 
| 183 |       } | 
| 184 |    } | 
| 185 |   | 
| 186 |    public abstract static class HandlerForGeneratedRequestWithResult<T> extends HandlerWithResult<T> { | 
| 187 |       @Override | 
| 188 |       protected GeneratedHttpRequest<?> getRequest() { | 
| 189 |          return (GeneratedHttpRequest<?>) super.getRequest(); | 
| 190 |       } | 
| 191 |   | 
| 192 |       @Override | 
| 193 |       public HandlerForGeneratedRequestWithResult<T> setContext(HttpRequest request) { | 
| 194 |          checkArgument(request instanceof GeneratedHttpRequest<?>, "note this handler requires a GeneratedHttpRequest"); | 
| 195 |          super.setContext(request); | 
| 196 |          return this; | 
| 197 |       } | 
| 198 |    } | 
| 199 |   | 
| 200 |    @Override | 
| 201 |    public ParseSax<T> setContext(HttpRequest request) { | 
| 202 |       handler.setContext(request); | 
| 203 |       this.request = request; | 
| 204 |       return this; | 
| 205 |    } | 
| 206 | } |