001/** 002 * Copyright 2014 Tampere University of Technology, Pori Department 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package core.tut.pori.utils; 017 018import java.io.InputStream; 019import java.io.StringReader; 020import java.io.StringWriter; 021 022import javax.xml.bind.JAXBContext; 023import javax.xml.bind.JAXBException; 024import javax.xml.bind.Marshaller; 025import javax.xml.bind.Unmarshaller; 026import javax.xml.bind.ValidationEvent; 027import javax.xml.bind.ValidationEventHandler; 028import javax.xml.transform.OutputKeys; 029import javax.xml.transform.Transformer; 030import javax.xml.transform.TransformerException; 031import javax.xml.transform.TransformerFactory; 032import javax.xml.transform.TransformerFactoryConfigurationError; 033import javax.xml.transform.dom.DOMSource; 034import javax.xml.transform.stream.StreamResult; 035 036import org.apache.commons.lang3.ArrayUtils; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.log4j.Logger; 039import org.w3c.dom.Document; 040import org.w3c.dom.Node; 041 042import core.tut.pori.http.Response; 043import core.tut.pori.http.ResponseData; 044 045 046/** 047 * XML formatter. 048 * 049 * This class can be used to marshal objects to xml output, and unmarshal objects from xml input. 050 */ 051public class XMLFormatter { 052 private static final Logger LOGGER = Logger.getLogger(XMLFormatter.class); 053 private boolean _omitXMLDeclaration = false; 054 private boolean _throwOnError = true; 055 056 /** 057 * 058 * @param string 059 * @param cls 060 * @return the object or null if bad/null/empty string 061 * @throws IllegalArgumentException on bad xml 062 */ 063 @SuppressWarnings("unchecked") 064 public <T> T toObject(String string, Class<T> cls) throws IllegalArgumentException{ 065 if(StringUtils.isBlank(string)){ 066 return null; 067 } 068 T retval = null; 069 try (StringReader reader = new StringReader(string)) { 070 JAXBContext context = JAXBContext.newInstance(cls); 071 Unmarshaller um = createUnMarshaller(context); 072 Object o = um.unmarshal(reader); 073 if(o.getClass() != cls){ 074 throw new IllegalArgumentException("Contents not of expected type."); 075 }else{ 076 retval = (T) o; 077 } 078 } catch (JAXBException ex) { 079 LOGGER.error(ex, ex); 080 throw new IllegalArgumentException("Failed to parse xml."); 081 } 082 return retval; 083 } 084 085 /** 086 * 087 * @param in 088 * @param cls 089 * @return the object or null if bad/null in 090 * @throws IllegalArgumentException on bad xml 091 */ 092 @SuppressWarnings("unchecked") 093 public <T> T toObject(InputStream in, Class<T> cls) throws IllegalArgumentException{ 094 if(in == null){ 095 return null; 096 } 097 T retval = null; 098 try{ 099 JAXBContext context = JAXBContext.newInstance(cls); 100 Unmarshaller um = createUnMarshaller(context); 101 Object o = um.unmarshal(in); 102 if(o.getClass() != cls){ 103 throw new IllegalArgumentException("Contents not of expected type."); 104 }else{ 105 retval = (T) o; 106 } 107 } catch(JAXBException ex){ 108 LOGGER.error(ex, ex); 109 throw new IllegalArgumentException("Failed to parse xml."); 110 } 111 return retval; 112 } 113 114 /** 115 * 116 * @param in 117 * @param objectClass class of the object to create 118 * @param requiredClasses additional classes required, note: objectClass is automatically added to requiredClasses 119 * @return the object or null if bad/null input 120 * @throws IllegalArgumentException on bad xml 121 */ 122 @SuppressWarnings("unchecked") 123 public <T> T toObject(InputStream in, Class<T> objectClass, Class<?> ...requiredClasses) throws IllegalArgumentException{ 124 if(in == null){ 125 return null; 126 } 127 try{ 128 JAXBContext context = JAXBContext.newInstance(ArrayUtils.add(requiredClasses, objectClass)); 129 Unmarshaller um = createUnMarshaller(context); 130 Object o = um.unmarshal(in); 131 if(o.getClass() != objectClass){ 132 throw new IllegalArgumentException("Contents not of expected type."); 133 }else{ 134 return (T) o; 135 } 136 } catch(JAXBException ex){ 137 LOGGER.error(ex, ex); 138 throw new IllegalArgumentException("Failed to parse xml."); 139 } 140 } 141 142 /** 143 * 144 * @param node 145 * @param cls 146 * @return the node as an object 147 * @throws IllegalArgumentException on bad xml 148 */ 149 @SuppressWarnings("unchecked") 150 public <T> T toObject(Node node, Class<T> cls) throws IllegalArgumentException{ 151 if(node == null){ 152 return null; 153 } 154 T retval = null; 155 try{ 156 JAXBContext context = JAXBContext.newInstance(cls); 157 Unmarshaller um = createUnMarshaller(context); 158 Object o = um.unmarshal(node); 159 if(o.getClass() != cls){ 160 throw new IllegalArgumentException("Contents not of expected type."); 161 }else{ 162 retval = (T) o; 163 } 164 } catch(JAXBException ex){ 165 LOGGER.error(ex, ex); 166 throw new IllegalArgumentException("Failed to parse xml."); 167 } 168 return retval; 169 } 170 171 /** 172 * 173 * @param r annotated xml object 174 * @return the object as a xml string or null if bad object 175 */ 176 public String toString(Response r){ 177 String retval = null; 178 try { 179 JAXBContext context = null; 180 ResponseData t = r.getResponseData(); 181 if(t == null){ 182 context = JAXBContext.newInstance(Response.class); 183 }else{ 184 context = JAXBContext.newInstance(ArrayUtils.add(t.getDataClasses(), Response.class)); 185 } 186 Marshaller marshaller = createMarshaller(context); 187 StringWriter w = new StringWriter(); 188 marshaller.marshal(r, w); 189 retval = w.toString(); 190 } catch (JAXBException ex) { 191 LOGGER.error(ex, ex); 192 } 193 194 return retval; 195 } 196 197 /** 198 * 199 * @param o annotated xml object 200 * @return the object as a xml string or null if bad object 201 */ 202 public <T> String toString(T o){ 203 String retval = null; 204 try { 205 Marshaller marshaller = createMarshaller(JAXBContext.newInstance(o.getClass())); 206 StringWriter w = new StringWriter(); 207 marshaller.marshal(o, w); 208 retval = w.toString(); 209 } catch (JAXBException ex) { 210 LOGGER.error(ex, ex); 211 } 212 213 return retval; 214 } 215 216 /** 217 * 218 * @param doc 219 * @return the document as string or null if not a valid document 220 */ 221 public String toString(Document doc){ 222 String result = null; 223 try { 224 Transformer tf = TransformerFactory.newInstance().newTransformer(); 225 if(_omitXMLDeclaration){ 226 tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 227 }else{ 228 tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); 229 } 230 StringWriter w = new StringWriter(); 231 tf.transform(new DOMSource(doc), new StreamResult(w)); 232 result = w.toString(); 233 } catch (TransformerFactoryConfigurationError | TransformerException ex) { 234 LOGGER.error(ex, ex); 235 } 236 return result; 237 } 238 239 /** 240 * create and return new marshaller, and set the default values 241 * @param context 242 * @return marshaller for the given context 243 * @throws JAXBException 244 * @throws IllegalArgumentException 245 */ 246 protected Marshaller createMarshaller(JAXBContext context) throws JAXBException, IllegalArgumentException{ 247 Marshaller m = context.createMarshaller(); 248 if(_throwOnError){ 249 m.setEventHandler(new ValidationEventHandler() { 250 @Override 251 public boolean handleEvent(ValidationEvent event ) { 252 throw new IllegalArgumentException(event.getMessage(), event.getLinkedException()); 253 } 254 }); 255 } 256 m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 257 m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); 258 if(_omitXMLDeclaration){ 259 m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); 260 } 261 return m; 262 } 263 264 /** 265 * 266 * @param context 267 * @return unmarshaller for the given context 268 * @throws JAXBException 269 * @throws IllegalArgumentException 270 */ 271 protected Unmarshaller createUnMarshaller(JAXBContext context) throws JAXBException, IllegalArgumentException{ 272 Unmarshaller um = context.createUnmarshaller(); 273 if(_throwOnError){ 274 um.setEventHandler(new ValidationEventHandler() { 275 @Override 276 public boolean handleEvent(ValidationEvent event ) { 277 throw new IllegalArgumentException(event.getMessage(), event.getLinkedException()); 278 } 279 }); 280 } 281 return um; 282 } 283 284 /** 285 * 286 * @return true if xml declaration is omitted from the output 287 */ 288 public boolean isOmitXMLDeclaration() { 289 return _omitXMLDeclaration; 290 } 291 292 /** 293 * 294 * @param omitXML 295 */ 296 public void setOmitXMLDeclaration(boolean omitXML) { 297 _omitXMLDeclaration = omitXML; 298 } 299 300 /** 301 * @return the throwOnError 302 */ 303 public boolean isThrowOnError() { 304 return _throwOnError; 305 } 306 307 /** 308 * @param throwOnError the throwOnError to set 309 */ 310 public void setThrowOnError(boolean throwOnError) { 311 _throwOnError = throwOnError; 312 } 313 314 /** 315 * 316 * @param in 317 * @param dataClass 318 * @return the object or null if bad/null input 319 * @throws IllegalArgumentException on bad xml 320 */ 321 public Response toResponse(InputStream in, Class<?> dataClass) throws IllegalArgumentException{ 322 if(in == null){ 323 return null; 324 } 325 Response retval = toObject(in, Response.class, dataClass); 326 if(retval == null){ 327 throw new IllegalArgumentException("Contents not of expected type."); 328 } 329 330 ResponseData data = retval.getResponseData(); 331 if(data == null){ 332 LOGGER.warn("Response contains no data."); 333 }else if(!data.getClass().equals(dataClass)){ 334 throw new IllegalArgumentException("Contents not of expected type: bad data."); 335 } 336 return retval; 337 } 338}