// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.api.ads.adwords.axis.utils;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.io.ByteSource;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.client.AxisClient;
import org.apache.axis.encoding.DeserializationContext;
import org.apache.axis.encoding.SerializerFactory;
import org.apache.axis.encoding.TypeMapping;
import org.apache.axis.encoding.TypeMappingRegistryImpl;
import org.apache.axis.message.MessageElement;
import org.apache.axis.message.SOAPEnvelope;
import org.xml.sax.InputSource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.rpc.encoding.DeserializerFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* Utility for deserializing XML to Axis objects.
*/
public class AxisDeserializer {
private static final String SOAP_START_BODY =
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ "<soapenv:Body>";
private static final String SOAP_END_BODY = "</soapenv:Body></soapenv:Envelope>";
private static final String INDENT_AMOUNT = "4";
public <ResultT> List<ResultT> deserializeBatchJobMutateResults(
URL url, List<TypeMapping> serviceTypeMappings, Class<ResultT> resultClass, QName resultQName)
throws Exception {
List<ResultT> results = Lists.newArrayList();
// Build a wrapped input stream from the response.
InputStream wrappedStream = buildWrappedInputStream(url.openStream());
// Create a MessageContext with a new TypeMappingRegistry that will only
// contain deserializers derived from serviceTypeMappings and the
// result class/QName pair.
MessageContext messageContext = new MessageContext(new AxisClient());
TypeMappingRegistryImpl typeMappingRegistry = new TypeMappingRegistryImpl(true);
messageContext.setTypeMappingRegistry(typeMappingRegistry);
// Construct an Axis deserialization context.
DeserializationContext deserializationContext =
new DeserializationContext(
new InputSource(wrappedStream), messageContext, Message.RESPONSE);
// Register all type mappings with the new type mapping registry.
TypeMapping registryTypeMapping =
typeMappingRegistry.getOrMakeTypeMapping(messageContext.getEncodingStyle());
registerTypeMappings(registryTypeMapping, serviceTypeMappings);
// Parse the wrapped input stream.
deserializationContext.parse();
// Read the deserialized mutate results from the parsed stream.
SOAPEnvelope envelope = deserializationContext.getEnvelope();
MessageElement body = envelope.getFirstBody();
for (Iterator<?> iter = body.getChildElements(); iter.hasNext(); ) {
Object child = iter.next();
MessageElement childElm = (MessageElement) child;
@SuppressWarnings("unchecked")
ResultT mutateResult = (ResultT) childElm.getValueAsType(resultQName, resultClass);
results.add(mutateResult);
}
return results;
}
/**
* Returns a new input stream that wraps the download input stream in a SOAP body so
* it can be parsed by Axis.
*/
private InputStream buildWrappedInputStream(InputStream downloadInputStream)
throws TransformerException, IOException {
// Pass the download input stream through a Transformer that removes the XML
// declaration. Create a new TransformerFactory and Transformer on each invocation
// since these objects are <em>not</em> thread safe.
Transformer omitXmlDeclarationTransformer = TransformerFactory.newInstance().newTransformer();
omitXmlDeclarationTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
omitXmlDeclarationTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
omitXmlDeclarationTransformer.setOutputProperty(
"{http://xml.apache.org/xslt}indent-amount", INDENT_AMOUNT);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
StreamResult streamResult = new StreamResult(outputStream);
Source xmlSource = new StreamSource(downloadInputStream);
omitXmlDeclarationTransformer.transform(xmlSource, streamResult);
return ByteSource.concat(
ByteSource.wrap(SOAP_START_BODY.getBytes()),
ByteSource.wrap(outputStream.toByteArray()),
ByteSource.wrap(SOAP_END_BODY.getBytes())).openStream();
}
/**
* Adds the type mappings in the list to {@code registryTypeMapping}.
*/
private void registerTypeMappings(
TypeMapping registryTypeMapping, List<TypeMapping> typeMappings) {
Preconditions.checkNotNull(registryTypeMapping, "Null registry type mapping");
Preconditions.checkNotNull(typeMappings, "Null type mappings");
Preconditions.checkArgument(!typeMappings.isEmpty(), "Empty type mappings");
for (TypeMapping typeMapping : typeMappings) {
for (Class<?> mappingClass : typeMapping.getAllClasses()) {
QName classQName = typeMapping.getTypeQName(mappingClass);
DeserializerFactory deserializer = typeMapping.getDeserializer(mappingClass, classQName);
if (deserializer != null && !registryTypeMapping.isRegistered(mappingClass, classQName)) {
registryTypeMapping.register(
mappingClass, classQName, (SerializerFactory) null, deserializer);
}
}
}
}
}