/**
* Mule Development Kit
* Copyright 2010-2011 (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* 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 org.mule.devkit.generation;
import org.apache.commons.lang.StringUtils;
import org.mule.api.MessagingException;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.annotations.oauth.OAuthAccessToken;
import org.mule.api.annotations.oauth.OAuthAccessTokenSecret;
import org.mule.api.callback.HttpCallback;
import org.mule.api.context.MuleContextAware;
import org.mule.api.lifecycle.Initialisable;
import org.mule.api.lifecycle.Startable;
import org.mule.api.lifecycle.Stoppable;
import org.mule.api.oauth.NotAuthorizedException;
import org.mule.api.oauth.RestoreAccessTokenCallback;
import org.mule.api.oauth.SaveAccessTokenCallback;
import org.mule.api.processor.MessageProcessor;
import org.mule.config.i18n.MessageFactory;
import org.mule.devkit.generation.callback.DefaultHttpCallbackGenerator;
import org.mule.devkit.model.code.Block;
import org.mule.devkit.model.code.CatchBlock;
import org.mule.devkit.model.code.ClassAlreadyExistsException;
import org.mule.devkit.model.code.Conditional;
import org.mule.devkit.model.code.DefinedClass;
import org.mule.devkit.model.code.ExpressionFactory;
import org.mule.devkit.model.code.FieldVariable;
import org.mule.devkit.model.code.Invocation;
import org.mule.devkit.model.code.Method;
import org.mule.devkit.model.code.Modifier;
import org.mule.devkit.model.code.Op;
import org.mule.devkit.model.code.TryStatement;
import org.mule.devkit.model.code.Variable;
import org.mule.devkit.model.code.builders.FieldBuilder;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractOAuthAdapterGenerator extends AbstractModuleGenerator {
public static final String VERIFIER_FIELD_NAME = "oauthVerifier";
public static final String HAS_TOKEN_EXPIRED_METHOD_NAME = "hasTokenExpired";
public static final String RESET_METHOD_NAME = "reset";
public static final String OAUTH_ACCESS_TOKEN_FIELD_NAME = "accessToken";
public static final String OAUTH_ACCESS_TOKEN_SECRET_FIELD_NAME = "accessTokenSecret";
public static final String GET_AUTHORIZATION_URL_METHOD_NAME = "getAuthorizationUrl";
public static final String FETCH_ACCESS_TOKEN_METHOD_NAME = "fetchAccessToken";
public static final String OAUTH_VERIFIER_FIELD_NAME = "oauthVerifier";
protected static final String REDIRECT_URL_FIELD_NAME = "redirectUrl";
protected static final String ACCESS_TOKEN_FIELD_NAME = "accessToken";
protected static final String ENCODING = "UTF-8";
protected static final String GRANT_TYPE = "authorization_code";
protected static final String ACCESS_CODE_PATTERN_FIELD_NAME = "ACCESS_CODE_PATTERN";
protected static final String CALLBACK_FIELD_NAME = "oauthCallback";
protected static final String AUTH_CODE_PATTERN_FIELD_NAME = "AUTH_CODE_PATTERN";
protected static final String EXPIRATION_TIME_PATTERN_FIELD_NAME = "EXPIRATION_TIME_PATTERN";
protected static final String EXPIRATION_FIELD_NAME = "expiration";
public static final String OAUTH_SAVE_ACCESS_TOKEN_CALLBACK_FIELD_NAME = "oauthSaveAccessToken";
public static final String OAUTH_RESTORE_ACCESS_TOKEN_CALLBACK_FIELD_NAME = "oauthRestoreAccessToken";
protected DefinedClass getOAuthAdapterClass(TypeElement typeElement, String classSuffix, Class<?> interf) {
String oauthAdapterName = context.getNameUtils().generateClassName(typeElement, NamingContants.ADAPTERS_NAMESPACE, classSuffix);
org.mule.devkit.model.code.Package pkg = context.getCodeModel()._package(context.getNameUtils().getPackageName(oauthAdapterName));
DefinedClass classToExtend = context.getClassForRole(context.getNameUtils().generateModuleObjectRoleKey(typeElement));
DefinedClass oauthAdapter = pkg._class(context.getNameUtils().getClassName(oauthAdapterName), classToExtend);
oauthAdapter._implements(MuleContextAware.class);
oauthAdapter._implements(Startable.class);
oauthAdapter._implements(Initialisable.class);
oauthAdapter._implements(Stoppable.class);
oauthAdapter._implements(interf);
context.setClassRole(context.getNameUtils().generateModuleObjectRoleKey(typeElement), oauthAdapter);
oauthAdapter.javadoc().add("A {@code " + oauthAdapter.name() + "} is a wrapper around ");
oauthAdapter.javadoc().add(ref(typeElement.asType()));
oauthAdapter.javadoc().add(" that adds OAuth capabilites to the pojo.");
return oauthAdapter;
}
protected FieldVariable authorizationCodePatternConstant(DefinedClass oauthAdapter, String regex) {
return new FieldBuilder(oauthAdapter).type(Pattern.class).name(AUTH_CODE_PATTERN_FIELD_NAME).staticField().finalField().
initialValue(ref(Pattern.class).staticInvoke("compile").arg(regex)).build();
}
protected FieldVariable authorizationCodeField(DefinedClass oauthAdapter) {
return new FieldBuilder(oauthAdapter).type(String.class).name(OAUTH_VERIFIER_FIELD_NAME).getterAndSetter().build();
}
protected FieldVariable saveAccessTokenCallbackField(DefinedClass oauthAdapter) {
return new FieldBuilder(oauthAdapter).type(SaveAccessTokenCallback.class).name(OAUTH_SAVE_ACCESS_TOKEN_CALLBACK_FIELD_NAME).getterAndSetter().build();
}
protected FieldVariable restoreAccessTokenCallbackField(DefinedClass oauthAdapter) {
return new FieldBuilder(oauthAdapter).type(RestoreAccessTokenCallback.class).name(OAUTH_RESTORE_ACCESS_TOKEN_CALLBACK_FIELD_NAME).getterAndSetter().build();
}
protected FieldVariable redirectUrlField(DefinedClass oauthAdapter) {
return new FieldBuilder(oauthAdapter).type(String.class).name(REDIRECT_URL_FIELD_NAME).getter().build();
}
protected FieldVariable accessTokenField(DefinedClass oauthAdapter) {
return new FieldBuilder(oauthAdapter).type(String.class).name(ACCESS_TOKEN_FIELD_NAME).getterAndSetter().build();
}
protected FieldVariable oauthCallbackField(DefinedClass oauthAdapter) {
return new FieldBuilder(oauthAdapter).type(HttpCallback.class).name(CALLBACK_FIELD_NAME).build();
}
protected void generateStartMethod(DefinedClass oauthAdapter) {
Method start = oauthAdapter.method(Modifier.PUBLIC, context.getCodeModel().VOID, Startable.PHASE_NAME);
start._throws(MuleException.class);
start.body().invoke(ExpressionFactory._super(), Startable.PHASE_NAME);
start.body().invoke(oauthAdapter.fields().get(CALLBACK_FIELD_NAME), Startable.PHASE_NAME);
start.body().assign(oauthAdapter.fields().get(REDIRECT_URL_FIELD_NAME), oauthAdapter.fields().get(CALLBACK_FIELD_NAME).invoke("getUrl"));
}
protected void generateStopMethod(DefinedClass oauthAdapter) {
Method start = oauthAdapter.method(Modifier.PUBLIC, context.getCodeModel().VOID, Stoppable.PHASE_NAME);
start._throws(MuleException.class);
start.body().invoke(ExpressionFactory._super(), (Stoppable.PHASE_NAME));
start.body().invoke(oauthAdapter.fields().get(CALLBACK_FIELD_NAME), Stoppable.PHASE_NAME);
}
protected Method generateInitialiseMethod(DefinedClass oauthAdapter, DefinedClass messageProcessor, String callbackPath) {
Method initialise = oauthAdapter.method(Modifier.PUBLIC, context.getCodeModel().VOID, Initialisable.PHASE_NAME);
if (ref(Initialisable.class).isAssignableFrom(oauthAdapter._extends())) {
initialise.body().invoke(ExpressionFactory._super(), Initialisable.PHASE_NAME);
}
Invocation domain = ExpressionFactory.invoke("get" + StringUtils.capitalize(DefaultHttpCallbackGenerator.DOMAIN_FIELD_NAME));
Invocation localPort = ExpressionFactory.invoke("get" + StringUtils.capitalize(DefaultHttpCallbackGenerator.LOCAL_PORT_FIELD_NAME));
Invocation remotePort = ExpressionFactory.invoke("get" + StringUtils.capitalize(DefaultHttpCallbackGenerator.REMOTE_PORT_FIELD_NAME));
Invocation async = ExpressionFactory.invoke("get" + StringUtils.capitalize(DefaultHttpCallbackGenerator.ASYNC_FIELD_NAME));
Invocation connector = ExpressionFactory.invoke("get" + StringUtils.capitalize(DefaultHttpCallbackGenerator.CONNECTOR_FIELD_NAME));
FieldVariable callback = oauthAdapter.fields().get(CALLBACK_FIELD_NAME);
FieldVariable muleContext = oauthAdapter.fields().get(MULE_CONTEXT_FIELD_NAME);
if (StringUtils.isEmpty(callbackPath)) {
initialise.body().assign(callback, ExpressionFactory._new(context.getClassForRole(DefaultHttpCallbackGenerator.HTTP_CALLBACK_ROLE)).
arg(ExpressionFactory._new(messageProcessor)).arg(muleContext).arg(domain).arg(localPort).arg(remotePort).arg(async).arg(connector));
} else {
initialise.body().assign(callback, ExpressionFactory._new(context.getClassForRole(DefaultHttpCallbackGenerator.HTTP_CALLBACK_ROLE)).
arg(ExpressionFactory._new(messageProcessor)).arg(muleContext).arg(domain).arg(localPort).arg(remotePort).arg(callbackPath).arg(async));
}
return initialise;
}
protected DefinedClass generateMessageProcessorInnerClass(DefinedClass oauthAdapter) throws GenerationException {
DefinedClass messageProcessor;
try {
messageProcessor = oauthAdapter._class(Modifier.PRIVATE, "OnOAuthCallbackMessageProcessor")._implements(ref(MessageProcessor.class));
} catch (ClassAlreadyExistsException e) {
throw new GenerationException(e); // This wont happen
}
Method processMethod = messageProcessor.method(Modifier.PUBLIC, ref(MuleEvent.class), "process")._throws(ref(MuleException.class));
Variable event = processMethod.param(ref(MuleEvent.class), "event");
TryStatement tryToExtractVerifier = processMethod.body()._try();
tryToExtractVerifier.body().assign(oauthAdapter.fields().get(VERIFIER_FIELD_NAME), ExpressionFactory.invoke("extractAuthorizationCode").arg(event.invoke("getMessageAsString")));
tryToExtractVerifier.body().add(ExpressionFactory.invoke("fetchAccessToken"));
CatchBlock catchBlock = tryToExtractVerifier._catch(ref(Exception.class));
Variable exceptionCaught = catchBlock.param("e");
catchBlock.body()._throw(ExpressionFactory._new(
ref(MessagingException.class)).
arg(ref(MessageFactory.class).staticInvoke("createStaticMessage").arg("Could not extract OAuth verifier")).arg(event).arg(exceptionCaught));
processMethod.body()._return(event);
Method extractMethod = messageProcessor.method(Modifier.PRIVATE, ref(String.class), "extractAuthorizationCode")._throws(ref(Exception.class));
Variable response = extractMethod.param(String.class, "response");
Variable matcher = extractMethod.body().decl(ref(Matcher.class), "matcher", oauthAdapter.fields().get(AUTH_CODE_PATTERN_FIELD_NAME).invoke("matcher").arg(response));
Conditional ifVerifierFound = extractMethod.body()._if(Op.cand(matcher.invoke("find"), Op.gte(matcher.invoke("groupCount"), ExpressionFactory.lit(1))));
Invocation group = matcher.invoke("group").arg(ExpressionFactory.lit(1));
ifVerifierFound._then()._return(ref(URLDecoder.class).staticInvoke("decode").arg(group).arg(ENCODING));
ifVerifierFound._else()._throw(ExpressionFactory._new(
ref(Exception.class)).arg(ref(String.class).staticInvoke("format").arg("OAuth authorization code could not be extracted from: %s").arg(response)));
return messageProcessor;
}
protected void muleContextField(DefinedClass oauthAdapter) {
new FieldBuilder(oauthAdapter).name(MULE_CONTEXT_FIELD_NAME).type(MuleContext.class).setter().build();
}
protected void generateOverrides(DevKitTypeElement typeElement, DefinedClass oauthAdapter, FieldVariable oauthAccessToken, FieldVariable oauthAccessTokenSecret) {
Map<String, Variable> variables = new HashMap<String, Variable>();
for (ExecutableElement executableElement : typeElement.getMethodsWhoseParametersAreAnnotatedWith(OAuthAccessToken.class)) {
Method override = oauthAdapter.method(Modifier.PUBLIC, ref(executableElement.getReturnType()), executableElement.getSimpleName().toString());
//override.annotate(Override.class);
override._throws(ref(NotAuthorizedException.class));
override.body().invoke("hasBeenAuthorized");
for (VariableElement parameter : executableElement.getParameters()) {
if (parameter.getAnnotation(OAuthAccessToken.class) != null ||
parameter.getAnnotation(OAuthAccessTokenSecret.class) != null) {
continue;
}
variables.put(
parameter.getSimpleName().toString(),
override.param(ref(parameter.asType()), parameter.getSimpleName().toString())
);
}
Invocation callSuper = ExpressionFactory._super().invoke(executableElement.getSimpleName().toString());
for (VariableElement parameter : executableElement.getParameters()) {
if (parameter.getAnnotation(OAuthAccessToken.class) != null) {
callSuper.arg(oauthAccessToken);
} else if (parameter.getAnnotation(OAuthAccessTokenSecret.class) != null) {
callSuper.arg(oauthAccessTokenSecret);
} else {
callSuper.arg(variables.get(parameter.getSimpleName().toString()));
}
}
if (ref(executableElement.getReturnType()) != context.getCodeModel().VOID) {
override.body()._return(callSuper);
} else {
override.body().add(callSuper);
}
}
}
protected void generateHasBeenAuthorizedMethod(DefinedClass oauthAdapter, FieldVariable oauthAccessToken) {
Method hasBeenAuthorized = oauthAdapter.method(Modifier.PUBLIC, context.getCodeModel().VOID, "hasBeenAuthorized");
hasBeenAuthorized._throws(ref(NotAuthorizedException.class));
Block ifAccessTokenIsNull = hasBeenAuthorized.body()._if(isNull(oauthAccessToken))._then();
ifAccessTokenIsNull.invoke("restoreAccessToken");
Block ifAccessTokenIsNull2 = ifAccessTokenIsNull._if(isNull(oauthAccessToken))._then();
Invocation newNotAuthorizedException = ExpressionFactory._new(ref(NotAuthorizedException.class));
newNotAuthorizedException.arg("This connector has not yet been authorized, please authorize by calling \"authorize\".");
ifAccessTokenIsNull2._throw(newNotAuthorizedException);
}
}