Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.jclouds.rest.internal; import static com.google.common.base.Functions.compose; import static com.google.inject.util.Types.newParameterizedType; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.APPLICATION_XML; import java.io.InputStream; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.net.URI; import java.util.Set; import javax.inject.Inject; import javax.lang.model.type.NullType; import org.jclouds.functions.IdentityFunction; import org.jclouds.functions.OnlyElementOrNull; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.http.functions.ParseFirstJsonValueNamed; import org.jclouds.http.functions.ParseJson; import org.jclouds.http.functions.ParseSax; import org.jclouds.http.functions.ParseSax.Factory; import org.jclouds.http.functions.ParseSax.HandlerWithResult; import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x; import org.jclouds.http.functions.ParseXMLWithJAXB; import org.jclouds.http.functions.ReleasePayloadAndReturn; import org.jclouds.http.functions.ReturnInputStream; import org.jclouds.http.functions.ReturnStringIf2xx; import org.jclouds.http.functions.ReturnTrueIf2xx; import org.jclouds.http.functions.UnwrapOnlyJsonValue; import org.jclouds.json.internal.GsonWrapper; import org.jclouds.reflect.Invocation; import org.jclouds.rest.InvocationContext; import org.jclouds.rest.annotations.JAXBResponseParser; import org.jclouds.rest.annotations.OnlyElement; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.SelectJson; import org.jclouds.rest.annotations.Transform; import org.jclouds.rest.annotations.Unwrap; import org.jclouds.rest.annotations.XMLResponseParser; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.reflect.Invokable; import com.google.common.reflect.TypeToken; import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; public class TransformerForRequest implements Function<HttpRequest, Function<HttpResponse, ?>> { private final ParseSax.Factory parserFactory; private final Injector injector; private final GetAcceptHeaders getAcceptHeaders; @Inject TransformerForRequest(Injector injector, Factory parserFactory, GetAcceptHeaders getAcceptHeaders) { this.injector = injector; this.parserFactory = parserFactory; this.getAcceptHeaders = getAcceptHeaders; } @SuppressWarnings("unchecked") @Override public Function<HttpResponse, ?> apply(HttpRequest in) { GeneratedHttpRequest request = GeneratedHttpRequest.class.cast(in); Function<HttpResponse, ?> transformer; Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull( request.getInvocation().getInvokable()); if (handler != null) { transformer = parserFactory.create(injector.getInstance(handler)); } else { transformer = getTransformerForMethod(request.getInvocation(), injector); } if (transformer instanceof InvocationContext<?>) { InvocationContext.class.cast(transformer).setContext(request); } if (request.getInvocation().getInvokable().isAnnotationPresent(Transform.class)) { Function<?, ?> wrappingTransformer = injector .getInstance(request.getInvocation().getInvokable().getAnnotation(Transform.class).value()); if (wrappingTransformer instanceof InvocationContext<?>) { ((InvocationContext<?>) wrappingTransformer).setContext(request); } transformer = compose(Function.class.cast(wrappingTransformer), transformer); } return transformer; } private static final TypeToken<ListenableFuture<Boolean>> futureBooleanToken = new TypeToken<ListenableFuture<Boolean>>() { private static final long serialVersionUID = 1L; }; private static final TypeToken<ListenableFuture<String>> futureStringToken = new TypeToken<ListenableFuture<String>>() { private static final long serialVersionUID = 1L; }; private static final TypeToken<ListenableFuture<Void>> futureVoidToken = new TypeToken<ListenableFuture<Void>>() { private static final long serialVersionUID = 1L; }; private static final TypeToken<ListenableFuture<URI>> futureURIToken = new TypeToken<ListenableFuture<URI>>() { private static final long serialVersionUID = 1L; }; private static final TypeToken<ListenableFuture<InputStream>> futureInputStreamToken = new TypeToken<ListenableFuture<InputStream>>() { private static final long serialVersionUID = 1L; }; private static final TypeToken<ListenableFuture<HttpResponse>> futureHttpResponseToken = new TypeToken<ListenableFuture<HttpResponse>>() { private static final long serialVersionUID = 1L; }; @SuppressWarnings("unchecked") @VisibleForTesting protected Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Invocation invocation) { Invokable<?, ?> invoked = invocation.getInvokable(); Set<String> acceptHeaders = getAcceptHeaders.apply(invocation); ResponseParser annotation = invoked.getAnnotation(ResponseParser.class); Class<?> rawReturnType = invoked.getReturnType().getRawType(); if (annotation == null) { if (rawReturnType.equals(void.class) || invoked.getReturnType().equals(futureVoidToken)) { return Key.get(ReleasePayloadAndReturn.class); } else if (rawReturnType.equals(boolean.class) || rawReturnType.equals(Boolean.class) || invoked.getReturnType().equals(futureBooleanToken)) { return Key.get(ReturnTrueIf2xx.class); } else if (rawReturnType.equals(InputStream.class) || invoked.getReturnType().equals(futureInputStreamToken)) { return Key.get(ReturnInputStream.class); } else if (rawReturnType.equals(HttpResponse.class) || invoked.getReturnType().equals(futureHttpResponseToken)) { return Key.get(Class.class.cast(IdentityFunction.class)); } else if (acceptHeaders.contains(APPLICATION_JSON)) { return getJsonParserKeyForMethod(invoked); } else if (acceptHeaders.contains(APPLICATION_XML) || invoked.isAnnotationPresent(JAXBResponseParser.class)) { return getJAXBParserKeyForMethod(invoked); } else if (rawReturnType.equals(String.class) || invoked.getReturnType().equals(futureStringToken)) { return Key.get(ReturnStringIf2xx.class); } else if (rawReturnType.equals(URI.class) || invoked.getReturnType().equals(futureURIToken)) { return Key.get(ParseURIFromListOrLocationHeaderIf20x.class); } else { throw new IllegalStateException( "You must specify a ResponseParser annotation on: " + invoked.toString()); } } return Key.get(annotation.value()); } @SuppressWarnings("unchecked") private static Key<? extends Function<HttpResponse, ?>> getJAXBParserKeyForMethod(Invokable<?, ?> invoked) { Optional<Type> configuredReturnVal = Optional.absent(); if (invoked.isAnnotationPresent(JAXBResponseParser.class)) { Type configuredClass = invoked.getAnnotation(JAXBResponseParser.class).value(); configuredReturnVal = configuredClass.equals(NullType.class) ? Optional.<Type>absent() : Optional.<Type>of(configuredClass); } Type returnVal = configuredReturnVal.or(getReturnTypeFor(invoked.getReturnType())); Type parserType = newParameterizedType(ParseXMLWithJAXB.class, returnVal); return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType); } @SuppressWarnings({ "unchecked" }) private static Key<? extends Function<HttpResponse, ?>> getJsonParserKeyForMethod(Invokable<?, ?> invoked) { ParameterizedType parserType; if (invoked.isAnnotationPresent(Unwrap.class)) { parserType = newParameterizedType(UnwrapOnlyJsonValue.class, getReturnTypeFor(invoked.getReturnType())); } else { parserType = newParameterizedType(ParseJson.class, getReturnTypeFor(invoked.getReturnType())); } return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType); } static Type getReturnTypeFor(TypeToken<?> typeToken) { Type returnVal = typeToken.getType(); if (typeToken.getRawType().getTypeParameters().length == 0) { returnVal = typeToken.getRawType(); } else if (typeToken.getRawType().equals(ListenableFuture.class)) { ParameterizedType futureType = (ParameterizedType) typeToken.getType(); returnVal = futureType.getActualTypeArguments()[0]; if (returnVal instanceof WildcardType) returnVal = WildcardType.class.cast(returnVal).getUpperBounds()[0]; } return returnVal; } // TODO: refactor this out of here @VisibleForTesting @SuppressWarnings({ "rawtypes", "unchecked" }) public Function<HttpResponse, ?> getTransformerForMethod(Invocation invocation, Injector injector) { Invokable<?, ?> invoked = invocation.getInvokable(); Function<HttpResponse, ?> transformer; if (invoked.isAnnotationPresent(SelectJson.class)) { Type returnVal = getReturnTypeFor(invoked.getReturnType()); if (invoked.isAnnotationPresent(OnlyElement.class)) returnVal = newParameterizedType(Set.class, returnVal); transformer = new ParseFirstJsonValueNamed(injector.getInstance(GsonWrapper.class), TypeLiteral.get(returnVal), invoked.getAnnotation(SelectJson.class).value()); if (invoked.isAnnotationPresent(OnlyElement.class)) transformer = compose(new OnlyElementOrNull(), transformer); } else { transformer = injector.getInstance(getParserOrThrowException(invocation)); } return transformer; } static Class<? extends HandlerWithResult<?>> getSaxResponseParserClassOrNull(Invokable<?, ?> invoked) { XMLResponseParser annotation = invoked.getAnnotation(XMLResponseParser.class); if (annotation != null) { return annotation.value(); } return null; } }