// Copyright 2013 Michel Kraemer // // 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 de.undercouch.citeproc.remote; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Map; import de.undercouch.citeproc.helper.json.JsonLexer; import de.undercouch.citeproc.helper.json.JsonParser; import de.undercouch.citeproc.helper.oauth.OAuth; import de.undercouch.citeproc.helper.oauth.OAuth.Method; import de.undercouch.citeproc.helper.oauth.OAuth1; import de.undercouch.citeproc.helper.oauth.Response; import de.undercouch.citeproc.helper.oauth.Token; import de.undercouch.citeproc.helper.oauth.UnauthorizedException; /** * Abstract base class for remote connectors * @author Michel Kraemer */ public abstract class AbstractRemoteConnector implements RemoteConnector { /** * OAuth client that is used to authenticate the user */ protected final OAuth auth; /** * The request token used to authorize the app */ protected Token requestToken; /** * The access token used to sign requests */ protected Token accessToken; /** * Constructs a new connector * @param consumerKey the app's consumer key * @param consumerSecret the app's consumer secret * @param redirectUri the location users are redirected to after * they granted the app access */ public AbstractRemoteConnector(String consumerKey, String consumerSecret, String redirectUri) { auth = createOAuth(consumerKey, consumerSecret, redirectUri); } /** * @return the remote service's end-point for temporary credentials or * null if not supported */ protected abstract String getOAuthRequestTokenURL(); /** * @return the remote service's authorization end-point */ protected abstract String getOAuthAuthorizationURL(); /** * @return the remote service's end-point for access tokens */ protected abstract String getOAuthAccessTokenURL(); /** * @return the HTTP method to use while requesting access tokens */ protected abstract Method getOAuthAccessTokenMethod(); /** * Creates an OAuth object * @param consumerKey the app's consumer key * @param consumerSecret the app's consumer secret * @param redirectUri the location users are redirected to after * they granted the app access * @return the created object */ protected OAuth createOAuth(String consumerKey, String consumerSecret, String redirectUri) { return new OAuth1(consumerKey, consumerSecret); } @Override public String getAuthorizationURL() throws IOException { String rtu = getOAuthRequestTokenURL(); if (rtu == null) { return getOAuthAuthorizationURL(); } try { requestToken = auth.requestTemporaryCredentials( new URL(rtu), Method.GET); } catch (MalformedURLException e) { //should never happen throw new RuntimeException(e); } return getOAuthAuthorizationURL() + requestToken.getToken(); } @Override public void authorize(String verificationCode) throws IOException { try { accessToken = auth.requestTokenCredentials( new URL(getOAuthAccessTokenURL()), getOAuthAccessTokenMethod(), requestToken, verificationCode); } catch (MalformedURLException e) { //should never happen throw new RuntimeException(e); } } @Override public void setAccessToken(String token, String secret) { accessToken = new Token(token, secret); } @Override public String getAccessTokenValue() { if (accessToken == null) { return null; } return accessToken.getToken(); } @Override public String getAccessTokenSecret() { if (accessToken == null) { return null; } return accessToken.getSecret(); } /** * Performs a request * @param url the URL to query * @param additionalHeaders additional HTTP request headers (may be null) * @return the parsed response * @throws IOException if the request was not successful */ protected Map<String, Object> performRequestObject(String url, Map<String, String> additionalHeaders) throws IOException { Response response = performRequest(url, additionalHeaders); try (InputStream is = response.getInputStream()) { return parseResponseObject(response); } } /** * Performs a request * @param url the URL to query * @param additionalHeaders additional HTTP request headers (may be null) * @return the parsed response * @throws IOException if the request was not successful */ protected List<Object> performRequestArray(String url, Map<String, String> additionalHeaders) throws IOException { Response response = performRequest(url, additionalHeaders); try (InputStream is = response.getInputStream()) { return parseResponseArray(response); } } /** * Performs a request * @param url the URL to query * @param additionalHeaders additional HTTP request headers (may be null) * @return the response * @throws IOException if the request was not successful */ protected Response performRequest(String url, Map<String, String> additionalHeaders) throws IOException { if (accessToken == null) { throw new UnauthorizedException("Access token has not yet been requested"); } URL u = new URL(url); return auth.request(u, Method.GET, accessToken, additionalHeaders); } /** * Parses the given response. The response's input stream doesn't have * to be closed. The caller will already do this. * @param response the HTTP response to parse * @return the parsed result * @throws IOException if the response could not be read */ private JsonParser parseResponse(Response response) throws IOException { InputStream is = response.getInputStream(); Reader r = new BufferedReader(new InputStreamReader(is)); return new JsonParser(new JsonLexer(r)); } /** * Parses the given response. The response's input stream doesn't have * to be closed. The caller will already do this. * @param response the HTTP response to parse * @return the parsed result * @throws IOException if the response could not be read */ protected Map<String, Object> parseResponseObject(Response response) throws IOException { return parseResponse(response).parseObject(); } /** * Parses the given response. The response's input stream doesn't have * to be closed. The caller will already do this. * @param response the HTTP response to parse * @return the parsed result * @throws IOException if the response could not be read */ protected List<Object> parseResponseArray(Response response) throws IOException { return parseResponse(response).parseArray(); } }