From c155238d9cce9d1a2b170ac8a898b126b09c0f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= <--global> Date: Mon, 21 Jul 2014 13:33:34 +0200 Subject: [PATCH] [OLINGO-362] Now supporting refresh token --- .../java/org/apache/olingo/fit/V4OAuth2.java | 4 - .../olingo/fit/rest/OAuth2InInterceptor.java | 98 ------------ .../olingo/fit/rest/OAuth2Provider.java | 2 - .../olingo/fit/rest/OAuth2RequestFilter.java | 41 ++--- .../webapp/WEB-INF/applicationContext.xml | 5 +- ...y.java => CXFOAuth2HttpClientFactory.java} | 120 ++++++++++----- .../olingo/fit/v4/OAuth2TestITCase.java | 27 ++-- .../api/http/WrappingHttpClientFactory.java | 24 +++ .../http/AbstractOAuth2HttpClientFactory.java | 116 ++++++++++++++ .../client/core/http/OAuth2Exception.java | 4 + ...va => ProxyWrappingHttpClientFactory.java} | 11 +- .../AbstractODataDeserializer.java | 15 +- .../olingo/client/core/uri/URIUtils.java | 6 +- .../core/serialization/JsonDeserializer.java | 144 ++++++++++-------- 14 files changed, 358 insertions(+), 259 deletions(-) delete mode 100644 fit/src/main/java/org/apache/olingo/fit/rest/OAuth2InInterceptor.java rename lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpUriRequestFactory.java => fit/src/main/java/org/apache/olingo/fit/rest/OAuth2RequestFilter.java (51%) rename fit/src/test/java/org/apache/olingo/fit/{CXFOAuth2HttpUriRequestFactory.java => CXFOAuth2HttpClientFactory.java} (50%) create mode 100644 lib/client-api/src/main/java/org/apache/olingo/client/api/http/WrappingHttpClientFactory.java create mode 100644 lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpClientFactory.java rename lib/client-core/src/main/java/org/apache/olingo/client/core/http/{ProxyWrapperHttpClientFactory.java => ProxyWrappingHttpClientFactory.java} (86%) diff --git a/fit/src/main/java/org/apache/olingo/fit/V4OAuth2.java b/fit/src/main/java/org/apache/olingo/fit/V4OAuth2.java index 02dc88763..dd5e89eb6 100644 --- a/fit/src/main/java/org/apache/olingo/fit/V4OAuth2.java +++ b/fit/src/main/java/org/apache/olingo/fit/V4OAuth2.java @@ -18,16 +18,12 @@ */ package org.apache.olingo.fit; -import org.apache.cxf.interceptor.InInterceptors; -import org.apache.olingo.fit.rest.OAuth2InInterceptor; import org.springframework.stereotype.Service; - import javax.ws.rs.Path; import java.io.IOException; @Service @Path("/V40/OAuth2.svc") -@InInterceptors(classes = {OAuth2InInterceptor.class}) public class V4OAuth2 extends V4Services { public V4OAuth2() throws IOException { diff --git a/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2InInterceptor.java b/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2InInterceptor.java deleted file mode 100644 index d61d3f01a..000000000 --- a/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2InInterceptor.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.apache.olingo.fit.rest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.cxf.interceptor.Fault; -import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; -import org.apache.cxf.jaxrs.client.WebClient; -import org.apache.cxf.message.Message; -import org.apache.cxf.phase.AbstractPhaseInterceptor; -import org.apache.cxf.phase.Phase; -import org.apache.cxf.rs.security.oauth2.client.OAuthClientUtils; -import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; -import org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrant; -import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; -import org.apache.cxf.transport.http.AbstractHTTPDestination; - -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import java.net.URI; -import java.util.List; -import java.util.Map; - -public class OAuth2InInterceptor extends AbstractPhaseInterceptor { - - private static final OAuthClientUtils.Consumer OAUTH2_CONSUMER = - new OAuthClientUtils.Consumer(OAuth2Provider.CLIENT_ID, OAuth2Provider.CLIENT_SECRET); - - public OAuth2InInterceptor() { - super(Phase.PRE_INVOKE); - } - - @Override - public void handleMessage(final Message message) throws Fault { - final String requestURL = (String) message.get(Message.REQUEST_URL); - if (requestURL.contains("V40/OAuth2.svc")) { - @SuppressWarnings("unchecked") - final Map> headers = (Map>) message.get(Message.PROTOCOL_HEADERS); - final List oauth2CodeHeader = headers.get(OAuth2Provider.OAUTH2_CODE_HEADER); - if (oauth2CodeHeader == null || oauth2CodeHeader.isEmpty()) { - message.put(AbstractHTTPDestination.REQUEST_REDIRECTED, Boolean.TRUE); - - final HttpServletResponse response = (HttpServletResponse) message.get(AbstractHTTPDestination.HTTP_RESPONSE); - try { - final String authorizationServiceURI = - StringUtils.substringBefore(requestURL, "V40/OAuth2.svc") + "oauth/authorize"; - - final URI authorizationURI = OAuthClientUtils.getAuthorizationURI( - authorizationServiceURI, - OAuth2Provider.CLIENT_ID, - OAuth2Provider.REDIRECT_URI, - null, - null); - response.addHeader("Location", authorizationURI.toASCIIString()); - response.sendError(303); - } catch (Exception e) { - throw new Fault(e); - } - } else { - try { - final JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean(); - bean.setAddress(StringUtils.substringBefore(requestURL, "V40/OAuth2.svc") + "oauth/token"); - bean.setUsername("odatajclient"); - bean.setPassword("odatajclient"); - final WebClient accessTokenService = bean.createWebClient(). - type(MediaType.APPLICATION_FORM_URLENCODED_TYPE). - accept(MediaType.APPLICATION_JSON_TYPE); - - final AuthorizationCodeGrant codeGrant = new AuthorizationCodeGrant(oauth2CodeHeader.get(0)); - final ClientAccessToken accessToken = - OAuthClientUtils.getAccessToken(accessTokenService, OAUTH2_CONSUMER, codeGrant); - if (accessToken == null) { - throw new WebApplicationException("No OAuth2 access token"); - } - } catch (OAuthServiceException e) { - throw new Fault(e); - } - } - } - } -} diff --git a/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2Provider.java b/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2Provider.java index cfe25b9d3..7000650ce 100644 --- a/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2Provider.java +++ b/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2Provider.java @@ -40,8 +40,6 @@ public class OAuth2Provider implements AuthorizationCodeDataProvider { public static final String REDIRECT_URI = "/stub/StaticService/V40/OAuth2.svc/"; - public static final String OAUTH2_CODE_HEADER = "oauth2.token"; - private Client client; private ServerAuthorizationCodeGrant grant; diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpUriRequestFactory.java b/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2RequestFilter.java similarity index 51% rename from lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpUriRequestFactory.java rename to fit/src/main/java/org/apache/olingo/fit/rest/OAuth2RequestFilter.java index e756dcb13..90223746d 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpUriRequestFactory.java +++ b/fit/src/main/java/org/apache/olingo/fit/rest/OAuth2RequestFilter.java @@ -16,38 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.client.core.http; +package org.apache.olingo.fit.rest; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.olingo.client.api.http.HttpMethod; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.rs.security.oauth2.filters.OAuthRequestFilter; -import java.net.URI; - -public abstract class AbstractOAuth2HttpUriRequestFactory extends DefaultHttpUriRequestFactory { - - protected final URI redirectURI; - - public AbstractOAuth2HttpUriRequestFactory(final URI redirectURI) { - this.redirectURI = redirectURI; - } - - protected abstract boolean isInited(); - - protected abstract void init() throws OAuth2Exception; - - protected abstract void sign(HttpUriRequest request); +@Provider +public class OAuth2RequestFilter extends OAuthRequestFilter implements ContainerRequestFilter { @Override - public HttpUriRequest create(final HttpMethod method, final URI uri) { - if (!isInited()) { - init(); + public void filter(final ContainerRequestContext context) { + final String svcName = + StringUtils.substringBefore(StringUtils.substringAfter(context.getUriInfo().getPath(), "/"), "/"); + if ("OAuth2.svc".equals(svcName)) { + super.filter(context); } - - final HttpUriRequest request = super.create(method, uri); - - sign(request); - - return request; } - } diff --git a/fit/src/main/webapp/WEB-INF/applicationContext.xml b/fit/src/main/webapp/WEB-INF/applicationContext.xml index 2cadd6143..f1320b95e 100644 --- a/fit/src/main/webapp/WEB-INF/applicationContext.xml +++ b/fit/src/main/webapp/WEB-INF/applicationContext.xml @@ -40,6 +40,9 @@ + + + @@ -52,7 +55,7 @@ - + diff --git a/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpUriRequestFactory.java b/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java similarity index 50% rename from fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpUriRequestFactory.java rename to fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java index a682a5203..28ff3d8d5 100644 --- a/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpUriRequestFactory.java +++ b/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java @@ -20,70 +20,84 @@ package org.apache.olingo.fit; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import java.io.IOException; +import java.net.URI; +import javax.ws.rs.core.MediaType; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.cxf.rs.security.oauth2.client.OAuthClientUtils; +import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; +import org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrant; +import org.apache.cxf.rs.security.oauth2.grants.refresh.RefreshTokenGrant; +import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; -import org.apache.olingo.client.core.http.AbstractOAuth2HttpUriRequestFactory; +import org.apache.olingo.client.core.http.AbstractOAuth2HttpClientFactory; import org.apache.olingo.client.core.http.OAuth2Exception; import org.apache.olingo.fit.rest.OAuth2Provider; -import java.net.URI; +public class CXFOAuth2HttpClientFactory extends AbstractOAuth2HttpClientFactory { -public class CXFOAuth2HttpUriRequestFactory extends AbstractOAuth2HttpUriRequestFactory { + private static final OAuthClientUtils.Consumer OAUTH2_CONSUMER = + new OAuthClientUtils.Consumer(OAuth2Provider.CLIENT_ID, OAuth2Provider.CLIENT_SECRET); - private String code; + private ClientAccessToken accessToken; - public CXFOAuth2HttpUriRequestFactory(final URI redirectURI) { - super(redirectURI); + public CXFOAuth2HttpClientFactory(final URI oauth2GrantServiceURI, final URI oauth2TokenServiceURI) { + super(oauth2GrantServiceURI, oauth2TokenServiceURI); + } + + private WebClient getAccessTokenService() { + final JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean(); + bean.setAddress(oauth2TokenServiceURI.toASCIIString()); + bean.setUsername("odatajclient"); + bean.setPassword("odatajclient"); + return bean.createWebClient(). + type(MediaType.APPLICATION_FORM_URLENCODED_TYPE).accept(MediaType.APPLICATION_JSON_TYPE); } @Override - protected boolean isInited() { - return code != null; + protected boolean isInited() throws OAuth2Exception { + return accessToken != null; } @Override protected void init() throws OAuth2Exception { - // 1. Disable automatic redirects handling + final URI authURI = OAuthClientUtils.getAuthorizationURI( + oauth2GrantServiceURI.toASCIIString(), + OAuth2Provider.CLIENT_ID, + OAuth2Provider.REDIRECT_URI, + null, + null); + + // Disable automatic redirects handling final HttpParams params = new BasicHttpParams(); params.setParameter(ClientPNames.HANDLE_REDIRECTS, false); final DefaultHttpClient httpClient = new DefaultHttpClient(params); - // 2. Try to access the redirect URI without any special header: get redirected to the OAuth2 service - URI location = null; - try { - final HttpResponse response = httpClient.execute(new HttpGet(redirectURI)); - - final Header locationHeader = response.getFirstHeader("Location"); - if (response.getStatusLine().getStatusCode() != 303 || locationHeader == null) { - throw new IllegalStateException("OAuth flow is broken"); - } - - location = new URI(locationHeader.getValue()); - - EntityUtils.consumeQuietly(response.getEntity()); - } catch (Exception e) { - throw new OAuth2Exception(e); - } - JsonNode oAuthAuthorizationData = null; String authenticityCookie = null; try { - // 3. Need to (basic) authenticate against the OAuth2 service - final HttpGet method = new HttpGet(location); + // 1. Need to (basic) authenticate against the OAuth2 service + final HttpGet method = new HttpGet(authURI); method.addHeader("Authorization", "Basic " + Base64.encodeBase64String("odatajclient:odatajclient".getBytes())); final HttpResponse response = httpClient.execute(method); - // 4. Pull out OAuth2 authorization data and "authenticity" cookie (CXF specific) + // 2. Pull out OAuth2 authorization data and "authenticity" cookie (CXF specific) oAuthAuthorizationData = new XmlMapper().readTree(EntityUtils.toString(response.getEntity())); final Header setCookieHeader = response.getFirstHeader("Set-Cookie"); @@ -95,9 +109,10 @@ public class CXFOAuth2HttpUriRequestFactory extends AbstractOAuth2HttpUriRequest throw new OAuth2Exception(e); } + String code = null; try { - // 5. Submit the HTTP form for allowing access to the application - location = new URIBuilder(oAuthAuthorizationData.get("replyTo").asText()). + // 3. Submit the HTTP form for allowing access to the application + final URI location = new URIBuilder(oAuthAuthorizationData.get("replyTo").asText()). addParameter("session_authenticity_token", oAuthAuthorizationData.get("authenticityToken").asText()). addParameter("client_id", oAuthAuthorizationData.get("clientId").asText()). addParameter("redirect_uri", oAuthAuthorizationData.get("redirectUri").asText()). @@ -114,18 +129,53 @@ public class CXFOAuth2HttpUriRequestFactory extends AbstractOAuth2HttpUriRequest throw new IllegalStateException("OAuth flow is broken"); } - // 6. Finally get the code value out of this last redirect + // 4. Get the authorization code value out of this last redirect code = StringUtils.substringAfterLast(locationHeader.getValue(), "="); EntityUtils.consumeQuietly(response.getEntity()); } catch (Exception e) { throw new OAuth2Exception(e); } + + // 5. Obtain the access token + try { + accessToken = OAuthClientUtils.getAccessToken( + getAccessTokenService(), OAUTH2_CONSUMER, new AuthorizationCodeGrant(code)); + } catch (OAuthServiceException e) { + throw new OAuth2Exception(e); + } + + if (accessToken == null) { + throw new OAuth2Exception("No OAuth2 access token"); + } } @Override - protected void sign(final HttpUriRequest request) { - request.addHeader(OAuth2Provider.OAUTH2_CODE_HEADER, code); + protected void accessToken(final DefaultHttpClient client) throws OAuth2Exception { + client.addRequestInterceptor(new HttpRequestInterceptor() { + + @Override + public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { + request.removeHeaders(HttpHeaders.AUTHORIZATION); + request.addHeader(HttpHeaders.AUTHORIZATION, OAuthClientUtils.createAuthorizationHeader(accessToken)); + } + }); + } + + @Override + protected void refreshToken(final DefaultHttpClient client) throws OAuth2Exception { + final String refreshToken = accessToken.getRefreshToken(); + if (refreshToken == null) { + throw new OAuth2Exception("No OAuth2 refresh token"); + } + + // refresh the token + try { + accessToken = OAuthClientUtils.getAccessToken( + getAccessTokenService(), OAUTH2_CONSUMER, new RefreshTokenGrant(refreshToken)); + } catch (OAuthServiceException e) { + throw new OAuth2Exception(e); + } } } diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/OAuth2TestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/OAuth2TestITCase.java index 8595c3ff0..be8eabb42 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/OAuth2TestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/OAuth2TestITCase.java @@ -18,6 +18,9 @@ */ package org.apache.olingo.fit.v4; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.apache.commons.lang3.StringUtils; import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest; import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; @@ -25,39 +28,41 @@ import org.apache.olingo.client.api.uri.v4.URIBuilder; import org.apache.olingo.client.api.v4.EdmEnabledODataClient; import org.apache.olingo.client.api.v4.ODataClient; import org.apache.olingo.client.core.ODataClientFactory; -import org.apache.olingo.client.core.http.DefaultHttpUriRequestFactory; import org.apache.olingo.commons.api.domain.v4.ODataEntity; import org.apache.olingo.commons.api.format.ODataFormat; -import org.apache.olingo.fit.CXFOAuth2HttpUriRequestFactory; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; - import java.net.URI; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.apache.olingo.client.core.http.DefaultHttpClientFactory; +import org.apache.olingo.fit.CXFOAuth2HttpClientFactory; public class OAuth2TestITCase extends AbstractTestITCase { + private static final URI OAUTH2_GRANT_SERVICE_URI = + URI.create("http://localhost:9080/stub/StaticService/oauth2/authorize"); + + private static final URI OAUTH2_TOKEN_SERVICE_URI = + URI.create("http://localhost:9080/stub/StaticService/oauth2/token"); + private EdmEnabledODataClient _edmClient; @BeforeClass public static void enableOAuth2() { - client.getConfiguration().setHttpUriRequestFactory( - new CXFOAuth2HttpUriRequestFactory(URI.create(testOAuth2ServiceRootURL))); + client.getConfiguration().setHttpClientFactory( + new CXFOAuth2HttpClientFactory(OAUTH2_GRANT_SERVICE_URI, OAUTH2_TOKEN_SERVICE_URI)); } @AfterClass public static void disableOAuth2() { - client.getConfiguration().setHttpUriRequestFactory(new DefaultHttpUriRequestFactory()); + client.getConfiguration().setHttpClientFactory(new DefaultHttpClientFactory()); } protected EdmEnabledODataClient getEdmClient() { if (_edmClient == null) { _edmClient = ODataClientFactory.getEdmEnabledV4(testOAuth2ServiceRootURL); - _edmClient.getConfiguration().setHttpUriRequestFactory( - new CXFOAuth2HttpUriRequestFactory(URI.create(testOAuth2ServiceRootURL))); + _edmClient.getConfiguration().setHttpClientFactory( + new CXFOAuth2HttpClientFactory(OAUTH2_GRANT_SERVICE_URI, OAUTH2_TOKEN_SERVICE_URI)); } return _edmClient; diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/http/WrappingHttpClientFactory.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/http/WrappingHttpClientFactory.java new file mode 100644 index 000000000..244cc5fc1 --- /dev/null +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/http/WrappingHttpClientFactory.java @@ -0,0 +1,24 @@ +/* + * 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.apache.olingo.client.api.http; + +public interface WrappingHttpClientFactory extends HttpClientFactory { + + HttpClientFactory getWrappedHttpClientFactory(); +} diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpClientFactory.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpClientFactory.java new file mode 100644 index 000000000..dd8b48568 --- /dev/null +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/http/AbstractOAuth2HttpClientFactory.java @@ -0,0 +1,116 @@ +/* + * 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.apache.olingo.client.core.http; + +import java.io.IOException; +import java.net.URI; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.HttpContext; +import org.apache.olingo.client.api.http.HttpClientFactory; +import org.apache.olingo.client.api.http.HttpMethod; +import org.apache.olingo.client.api.http.WrappingHttpClientFactory; + +public abstract class AbstractOAuth2HttpClientFactory + extends AbstractHttpClientFactory implements WrappingHttpClientFactory { + + protected final DefaultHttpClientFactory wrapped; + + protected final URI oauth2GrantServiceURI; + + protected final URI oauth2TokenServiceURI; + + protected HttpUriRequest currentRequest; + + public AbstractOAuth2HttpClientFactory(final URI oauth2GrantServiceURI, final URI oauth2TokenServiceURI) { + this(new DefaultHttpClientFactory(), oauth2GrantServiceURI, oauth2TokenServiceURI); + } + + public AbstractOAuth2HttpClientFactory(final DefaultHttpClientFactory wrapped, + final URI oauth2GrantServiceURI, final URI oauth2TokenServiceURI) { + + super(); + this.wrapped = wrapped; + this.oauth2GrantServiceURI = oauth2GrantServiceURI; + this.oauth2TokenServiceURI = oauth2TokenServiceURI; + } + + @Override + public HttpClientFactory getWrappedHttpClientFactory() { + return wrapped; + } + + protected abstract boolean isInited() throws OAuth2Exception; + + protected abstract void init() throws OAuth2Exception; + + protected abstract void accessToken(DefaultHttpClient client) throws OAuth2Exception; + + protected abstract void refreshToken(DefaultHttpClient client) throws OAuth2Exception; + + @Override + public HttpClient create(final HttpMethod method, final URI uri) { + if (!isInited()) { + init(); + } + + final DefaultHttpClient httpClient = wrapped.create(method, uri); + accessToken(httpClient); + + httpClient.addRequestInterceptor(new HttpRequestInterceptor() { + + @Override + public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { + if (request instanceof HttpUriRequest) { + currentRequest = (HttpUriRequest) request; + } else { + currentRequest = null; + } + } + }); + httpClient.addResponseInterceptor(new HttpResponseInterceptor() { + + @Override + public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException { + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + refreshToken(httpClient); + + if (currentRequest != null) { + httpClient.execute(currentRequest); + } + } + } + }); + + return httpClient; + } + + @Override + public void close(final HttpClient httpClient) { + wrapped.close(httpClient); + } + +} diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/OAuth2Exception.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/http/OAuth2Exception.java index 8158a517f..9462ebc11 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/OAuth2Exception.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/http/OAuth2Exception.java @@ -22,6 +22,10 @@ public class OAuth2Exception extends RuntimeException { private static final long serialVersionUID = 5695438980473040134L; + public OAuth2Exception(final String message) { + super(message); + } + public OAuth2Exception(final Throwable cause) { super(cause); } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/ProxyWrapperHttpClientFactory.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/http/ProxyWrappingHttpClientFactory.java similarity index 86% rename from lib/client-core/src/main/java/org/apache/olingo/client/core/http/ProxyWrapperHttpClientFactory.java rename to lib/client-core/src/main/java/org/apache/olingo/client/core/http/ProxyWrappingHttpClientFactory.java index 365df373d..e371c5e09 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/http/ProxyWrapperHttpClientFactory.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/http/ProxyWrappingHttpClientFactory.java @@ -28,12 +28,13 @@ import org.apache.olingo.client.api.http.HttpClientFactory; import org.apache.olingo.client.api.http.HttpMethod; import java.net.URI; +import org.apache.olingo.client.api.http.WrappingHttpClientFactory; /** * Implementation for working behind an HTTP proxy (possibly requiring authentication); requires another concrete * {@link HttpClientFactory} implementation acting as real HTTP client factory. */ -public class ProxyWrapperHttpClientFactory implements HttpClientFactory { +public class ProxyWrappingHttpClientFactory implements WrappingHttpClientFactory { private final URI proxy; @@ -43,19 +44,19 @@ public class ProxyWrapperHttpClientFactory implements HttpClientFactory { private final DefaultHttpClientFactory wrapped; - public ProxyWrapperHttpClientFactory(final URI proxy) { + public ProxyWrappingHttpClientFactory(final URI proxy) { this(proxy, null, null, new DefaultHttpClientFactory()); } - public ProxyWrapperHttpClientFactory(final URI proxy, final String proxyUsername, final String proxyPassword) { + public ProxyWrappingHttpClientFactory(final URI proxy, final String proxyUsername, final String proxyPassword) { this(proxy, proxyUsername, proxyPassword, new DefaultHttpClientFactory()); } - public ProxyWrapperHttpClientFactory(final URI proxy, final DefaultHttpClientFactory wrapped) { + public ProxyWrappingHttpClientFactory(final URI proxy, final DefaultHttpClientFactory wrapped) { this(proxy, null, null, wrapped); } - public ProxyWrapperHttpClientFactory(final URI proxy, + public ProxyWrappingHttpClientFactory(final URI proxy, final String proxyUsername, final String proxyPassword, final DefaultHttpClientFactory wrapped) { this.proxy = proxy; diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/serialization/AbstractODataDeserializer.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/serialization/AbstractODataDeserializer.java index 0e11c588e..e739ec894 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/serialization/AbstractODataDeserializer.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/serialization/AbstractODataDeserializer.java @@ -46,10 +46,11 @@ import java.io.InputStream; public abstract class AbstractODataDeserializer { protected final ODataServiceVersion version; + protected final ODataDeserializer deserializer; public AbstractODataDeserializer(final ODataServiceVersion version, final boolean serverMode, - final ODataFormat format) { + final ODataFormat format) { this.version = version; if (format == ODataFormat.XML || format == ODataFormat.ATOM) { deserializer = new AtomDeserializer(version); @@ -76,18 +77,18 @@ public abstract class AbstractODataDeserializer { protected XmlMapper getXmlMapper() { final XmlMapper xmlMapper = new XmlMapper( - new XmlFactory(new InputFactoryImpl(), new OutputFactoryImpl()), new JacksonXmlModule()); + new XmlFactory(new InputFactoryImpl(), new OutputFactoryImpl()), new JacksonXmlModule()); xmlMapper.setInjectableValues(new InjectableValues.Std(). - addValue(ODataServiceVersion.class, version). - addValue(Boolean.class, Boolean.FALSE)); + addValue(ODataServiceVersion.class, version). + addValue(Boolean.class, Boolean.FALSE)); xmlMapper.addHandler(new DeserializationProblemHandler() { @Override public boolean handleUnknownProperty(final DeserializationContext ctxt, final JsonParser jp, - final com.fasterxml.jackson.databind.JsonDeserializer deserializer, - final Object beanOrClass, final String propertyName) - throws IOException, JsonProcessingException { + final com.fasterxml.jackson.databind.JsonDeserializer deserializer, + final Object beanOrClass, final String propertyName) + throws IOException, JsonProcessingException { // skip any unknown property ctxt.getParser().skipChildren(); diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java index 8ddc27381..b81b977d3 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java @@ -31,7 +31,6 @@ import org.apache.olingo.client.api.CommonODataClient; import org.apache.olingo.client.api.http.HttpClientFactory; import org.apache.olingo.client.api.uri.SegmentType; import org.apache.olingo.client.core.http.BasicAuthHttpClientFactory; -import org.apache.olingo.client.core.http.ProxyWrapperHttpClientFactory; import org.apache.olingo.commons.api.Constants; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; @@ -65,6 +64,7 @@ import java.util.Iterator; import java.util.Map; import java.util.UUID; import java.util.regex.Pattern; +import org.apache.olingo.client.api.http.WrappingHttpClientFactory; /** * URI utilities. @@ -354,8 +354,8 @@ public final class URIUtils { HttpClientFactory httpclientFactory = client.getConfiguration().getHttpClientFactory(); if (httpclientFactory instanceof BasicAuthHttpClientFactory) { return true; - } else if (httpclientFactory instanceof ProxyWrapperHttpClientFactory) { - ProxyWrapperHttpClientFactory tmp = (ProxyWrapperHttpClientFactory) httpclientFactory; + } else if (httpclientFactory instanceof WrappingHttpClientFactory) { + WrappingHttpClientFactory tmp = (WrappingHttpClientFactory) httpclientFactory; if (tmp.getWrappedHttpClientFactory() instanceof BasicAuthHttpClientFactory) { return true; } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/JsonDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/JsonDeserializer.java index 1563e1783..e1b8b1f4e 100755 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/JsonDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/JsonDeserializer.java @@ -53,7 +53,6 @@ import org.apache.olingo.commons.core.data.LinkImpl; import org.apache.olingo.commons.core.data.LinkedComplexValueImpl; import org.apache.olingo.commons.core.data.PropertyImpl; import org.apache.olingo.commons.core.edm.EdmTypeInfo; - import java.io.IOException; import java.io.InputStream; import java.util.AbstractMap.SimpleEntry; @@ -69,23 +68,39 @@ import java.util.regex.Pattern; public class JsonDeserializer implements ODataDeserializer { protected final Pattern CUSTOM_ANNOTATION = Pattern.compile("(.+)@(.+)\\.(.+)"); + protected final ODataServiceVersion version; + protected final boolean serverMode; protected String jsonType; + protected String jsonId; + protected String jsonETag; + protected String jsonReadLink; + protected String jsonEditLink; + protected String jsonMediaEditLink; + protected String jsonMediaReadLink; + protected String jsonMediaContentType; + protected String jsonMediaETag; + protected String jsonAssociationLink; + protected String jsonNavigationLink; + protected String jsonCount; + protected String jsonNextLink; + protected String jsonDeltaLink; + protected String jsonError; private JsonGeoValueDeserializer geoDeserializer; @@ -129,7 +144,7 @@ public class JsonDeserializer implements ODataDeserializer { } protected String setInline(final String name, final String suffix, final JsonNode tree, - final ObjectCodec codec, final LinkImpl link) throws IOException { + final ObjectCodec codec, final LinkImpl link) throws IOException { final String entityNamePrefix = name.substring(0, name.indexOf(suffix)); if (tree.has(entityNamePrefix)) { @@ -143,11 +158,9 @@ public class JsonDeserializer implements ODataDeserializer { } else if (inline instanceof ArrayNode) { link.setType(ODataLinkType.ENTITY_SET_NAVIGATION.toString()); - EntitySet entitySet = new EntitySetImpl(); - Iterator entries = inline.elements(); - while (entries.hasNext()) { - entitySet.getEntities().add( - entityDeserializer.doDeserialize(entries.next().traverse(codec)).getPayload()); + final EntitySet entitySet = new EntitySetImpl(); + for (final Iterator entries = inline.elements(); entries.hasNext();) { + entitySet.getEntities().add(entityDeserializer.doDeserialize(entries.next().traverse(codec)).getPayload()); } link.setInlineEntitySet(entitySet); @@ -157,7 +170,7 @@ public class JsonDeserializer implements ODataDeserializer { } protected void links(final Map.Entry field, final Linked linked, final Set toRemove, - final JsonNode tree, final ObjectCodec codec) throws IOException { + final JsonNode tree, final ObjectCodec codec) throws IOException { if (serverMode) { serverLinks(field, linked, toRemove, tree, codec); } else { @@ -166,7 +179,7 @@ public class JsonDeserializer implements ODataDeserializer { } private void clientLinks(final Map.Entry field, final Linked linked, final Set toRemove, - final JsonNode tree, final ObjectCodec codec) throws IOException { + final JsonNode tree, final ObjectCodec codec) throws IOException { if (field.getKey().endsWith(jsonNavigationLink)) { final LinkImpl link = new LinkImpl(); @@ -195,10 +208,10 @@ public class JsonDeserializer implements ODataDeserializer { } private void serverLinks(final Map.Entry field, final Linked linked, final Set toRemove, - final JsonNode tree, final ObjectCodec codec) throws IOException { + final JsonNode tree, final ObjectCodec codec) throws IOException { if (field.getKey().endsWith(Constants.JSON_BIND_LINK_SUFFIX) - || field.getKey().endsWith(jsonNavigationLink)) { + || field.getKey().endsWith(jsonNavigationLink)) { if (field.getValue().isValueNode()) { final String suffix = field.getKey().replaceAll("^.*@", "@"); @@ -258,7 +271,7 @@ public class JsonDeserializer implements ODataDeserializer { if (node.has(Constants.ATTR_TYPE)) { type = ODataPropertyType.PRIMITIVE; typeInfo = new EdmTypeInfo.Builder(). - setTypeExpression("Edm.Geography" + node.get(Constants.ATTR_TYPE).asText()).build(); + setTypeExpression("Edm.Geography" + node.get(Constants.ATTR_TYPE).asText()).build(); } else { type = ODataPropertyType.COMPLEX; } @@ -270,8 +283,8 @@ public class JsonDeserializer implements ODataDeserializer { } protected void populate(final Annotatable annotatable, final List properties, - final ObjectNode tree, final ObjectCodec codec) - throws IOException, EdmPrimitiveTypeException { + final ObjectNode tree, final ObjectCodec codec) + throws IOException, EdmPrimitiveTypeException { String type = null; Annotation annotation = null; @@ -297,8 +310,8 @@ public class JsonDeserializer implements ODataDeserializer { final PropertyImpl property = new PropertyImpl(); property.setName(field.getKey()); property.setType(type == null - ? null - : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal()); + ? null + : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal()); type = null; value(property, field.getValue(), codec); @@ -313,25 +326,25 @@ public class JsonDeserializer implements ODataDeserializer { } private Object fromPrimitive(final JsonNode node, final EdmTypeInfo typeInfo) throws EdmPrimitiveTypeException { - return node.isNull() ? null : - typeInfo == null ? node.asText() : - typeInfo.getPrimitiveTypeKind().isGeospatial() ? - getGeoDeserializer().deserialize(node, typeInfo) : - ((EdmPrimitiveType) typeInfo.getType()) - .valueOfString(node.asText(), true, null, - Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, true, - ((EdmPrimitiveType) typeInfo.getType()).getDefaultType()); + return node.isNull() ? null + : typeInfo == null ? node.asText() + : typeInfo.getPrimitiveTypeKind().isGeospatial() + ? getGeoDeserializer().deserialize(node, typeInfo) + : ((EdmPrimitiveType) typeInfo.getType()) + .valueOfString(node.asText(), true, null, + Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, true, + ((EdmPrimitiveType) typeInfo.getType()).getDefaultType()); } private Object fromComplex(final ObjectNode node, final ObjectCodec codec) - throws IOException, EdmPrimitiveTypeException { + throws IOException, EdmPrimitiveTypeException { if (version.compareTo(ODataServiceVersion.V40) < 0) { - List properties = new ArrayList(); + final List properties = new ArrayList(); populate(null, properties, node, codec); return properties; } else { - LinkedComplexValue linkComplexValue = new LinkedComplexValueImpl(); + final LinkedComplexValue linkComplexValue = new LinkedComplexValueImpl(); final Set toRemove = new HashSet(); for (final Iterator> itor = node.fields(); itor.hasNext();) { final Map.Entry field = itor.next(); @@ -346,13 +359,13 @@ public class JsonDeserializer implements ODataDeserializer { } private void fromCollection(final Valuable valuable, final Iterator nodeItor, final EdmTypeInfo typeInfo, - final ObjectCodec codec) throws IOException, EdmPrimitiveTypeException { + final ObjectCodec codec) throws IOException, EdmPrimitiveTypeException { - List values = new ArrayList(); + final List values = new ArrayList(); ValueType valueType = ValueType.COLLECTION_PRIMITIVE; - final EdmTypeInfo type = typeInfo == null ? null : - new EdmTypeInfo.Builder().setTypeExpression(typeInfo.getFullQualifiedName().toString()).build(); + final EdmTypeInfo type = typeInfo == null ? null + : new EdmTypeInfo.Builder().setTypeExpression(typeInfo.getFullQualifiedName().toString()).build(); while (nodeItor.hasNext()) { final JsonNode child = nodeItor.next(); @@ -371,8 +384,8 @@ public class JsonDeserializer implements ODataDeserializer { ((ObjectNode) child).remove(jsonType); } final Object value = fromComplex((ObjectNode) child, codec); - valueType = value instanceof LinkedComplexValue ? ValueType.COLLECTION_LINKED_COMPLEX : - ValueType.COLLECTION_COMPLEX; + valueType = value instanceof LinkedComplexValue ? ValueType.COLLECTION_LINKED_COMPLEX + : ValueType.COLLECTION_COMPLEX; values.add(value); } } @@ -380,50 +393,51 @@ public class JsonDeserializer implements ODataDeserializer { } protected void value(final Valuable valuable, final JsonNode node, final ObjectCodec codec) - throws IOException, EdmPrimitiveTypeException { - EdmTypeInfo typeInfo = StringUtils.isBlank(valuable.getType()) ? null : - new EdmTypeInfo.Builder().setTypeExpression(valuable.getType()).build(); + throws IOException, EdmPrimitiveTypeException { + + EdmTypeInfo typeInfo = StringUtils.isBlank(valuable.getType()) ? null + : new EdmTypeInfo.Builder().setTypeExpression(valuable.getType()).build(); final Map.Entry guessed = guessPropertyType(node); if (typeInfo == null) { typeInfo = guessed.getValue(); } - final ODataPropertyType propType = typeInfo == null ? guessed.getKey() : - typeInfo.isCollection() ? ODataPropertyType.COLLECTION : - typeInfo.isPrimitiveType() ? ODataPropertyType.PRIMITIVE : - node.isValueNode() ? ODataPropertyType.ENUM : ODataPropertyType.COMPLEX; + final ODataPropertyType propType = typeInfo == null ? guessed.getKey() + : typeInfo.isCollection() ? ODataPropertyType.COLLECTION + : typeInfo.isPrimitiveType() ? ODataPropertyType.PRIMITIVE + : node.isValueNode() ? ODataPropertyType.ENUM : ODataPropertyType.COMPLEX; switch (propType) { - case COLLECTION: - fromCollection(valuable, node.elements(), typeInfo, codec); - break; + case COLLECTION: + fromCollection(valuable, node.elements(), typeInfo, codec); + break; - case COMPLEX: - if (node.has(jsonType)) { - valuable.setType(node.get(jsonType).asText()); - ((ObjectNode) node).remove(jsonType); - } - final Object value = fromComplex((ObjectNode) node, codec); - valuable.setValue(value instanceof LinkedComplexValue ? ValueType.LINKED_COMPLEX : ValueType.COMPLEX, value); - break; + case COMPLEX: + if (node.has(jsonType)) { + valuable.setType(node.get(jsonType).asText()); + ((ObjectNode) node).remove(jsonType); + } + final Object value = fromComplex((ObjectNode) node, codec); + valuable.setValue(value instanceof LinkedComplexValue ? ValueType.LINKED_COMPLEX : ValueType.COMPLEX, value); + break; - case ENUM: - valuable.setValue(ValueType.ENUM, node.asText()); - break; + case ENUM: + valuable.setValue(ValueType.ENUM, node.asText()); + break; - case PRIMITIVE: - if (valuable.getType() == null && typeInfo != null) { - valuable.setType(typeInfo.getFullQualifiedName().toString()); - } - final Object primitiveValue = fromPrimitive(node, typeInfo); - valuable.setValue(primitiveValue instanceof Geospatial ? ValueType.GEOSPATIAL : ValueType.PRIMITIVE, - primitiveValue); - break; + case PRIMITIVE: + if (valuable.getType() == null && typeInfo != null) { + valuable.setType(typeInfo.getFullQualifiedName().toString()); + } + final Object primitiveValue = fromPrimitive(node, typeInfo); + valuable.setValue(primitiveValue instanceof Geospatial ? ValueType.GEOSPATIAL : ValueType.PRIMITIVE, + primitiveValue); + break; - case EMPTY: - default: - valuable.setValue(ValueType.PRIMITIVE, StringUtils.EMPTY); + case EMPTY: + default: + valuable.setValue(ValueType.PRIMITIVE, StringUtils.EMPTY); } }