/* * Copyright (C) 2011 Red Hat, Inc. and/or its affiliates. * * 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.jboss.errai.enterprise.rebind; import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs; import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf; import static org.jboss.errai.codegen.util.Stmt.castTo; import static org.jboss.errai.codegen.util.Stmt.declareFinalVariable; import static org.jboss.errai.codegen.util.Stmt.invokeStatic; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.enterprise.event.Observes; import javax.enterprise.inject.Any; import org.jboss.errai.bus.client.ErraiBus; import org.jboss.errai.bus.client.api.Subscription; import org.jboss.errai.codegen.Context; import org.jboss.errai.codegen.Parameter; import org.jboss.errai.codegen.Statement; import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder; import org.jboss.errai.codegen.builder.BlockBuilder; import org.jboss.errai.codegen.builder.ContextualStatementBuilder; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaMethod; import org.jboss.errai.codegen.meta.MetaParameter; import org.jboss.errai.codegen.util.Refs; import org.jboss.errai.codegen.util.Stmt; import org.jboss.errai.config.rebind.EnvUtil; import org.jboss.errai.enterprise.client.cdi.AbstractCDIEventCallback; import org.jboss.errai.enterprise.client.cdi.EventQualifierSerializer; import org.jboss.errai.enterprise.client.cdi.JsTypeEventObserver; import org.jboss.errai.enterprise.client.cdi.api.CDI; import org.jboss.errai.ioc.client.api.CodeDecorator; import org.jboss.errai.ioc.client.container.Factory; import org.jboss.errai.ioc.rebind.ioc.bootstrapper.InjectUtil; import org.jboss.errai.ioc.rebind.ioc.extension.IOCDecoratorExtension; import org.jboss.errai.ioc.rebind.ioc.injector.api.Decorable; import org.jboss.errai.ioc.rebind.ioc.injector.api.FactoryController; import jsinterop.annotations.JsType; /** * Generates the boiler plate for @Observes annotations use in GWT clients.<br/> * Basically creates a subscription for a CDI event type that invokes on the annotated method. * * @author Heiko Braun <hbraun@redhat.com> * @author Mike Brock <cbrock@redhat.com> * @author Christian Sadilek <csadilek@redhat.com> * @author Max Barkley <mbarkley@redhat.com> */ @CodeDecorator public class ObservesExtension extends IOCDecoratorExtension<Observes> { public ObservesExtension(final Class<Observes> decoratesWith) { super(decoratesWith); } @Override public void generateDecorator(final Decorable decorable, final FactoryController controller) { if (!EventQualifierSerializer.isSet()) { NonGwtEventQualifierSerializerGenerator.loadAndSetEventQualifierSerializer(); } final Context ctx = decorable.getCodegenContext(); final MetaParameter parm = decorable.getAsParameter(); final MetaMethod method = (MetaMethod) parm.getDeclaringMember(); controller.ensureMemberExposed(parm); final MetaClass eventType = parm.getType().asBoxed(); final String parmClassName = eventType.getFullyQualifiedName(); final List<Annotation> annotations = InjectUtil.extractQualifiers(parm); final Annotation[] qualifiers = annotations.toArray(new Annotation[annotations.size()]); final Set<String> qualifierNames = new HashSet<>(CDI.getQualifiersPart(qualifiers)); final boolean isEnclosingTypeDependent = enclosingTypeIsDependentScoped(decorable); if (qualifierNames.contains(Any.class.getName())) { qualifierNames.remove(Any.class.getName()); } final MetaClass callBackType = parameterizedAs(AbstractCDIEventCallback.class, typeParametersOf(eventType)); AnonymousClassStructureBuilder callBack = Stmt.newObject(callBackType).extend(); BlockBuilder<AnonymousClassStructureBuilder> callBackBlock; if (!qualifierNames.isEmpty()) { callBackBlock = callBack.initialize(); for (final String qualifierName : qualifierNames) { callBackBlock.append(Stmt.loadClassMember("qualifierSet").invoke("add", qualifierName)); } callBack = callBackBlock.finish(); } final List<Statement> callbackStatements = new ArrayList<>(); if (!isEnclosingTypeDependent) { callbackStatements .add(declareFinalVariable("instance", decorable.getDecorableDeclaringType(), castTo(decorable.getEnclosingInjectable().getInjectedType(), invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt())))); } callbackStatements.add(decorable.call(Refs.get("event"))); callBackBlock = callBack.publicOverridesMethod("fireEvent", Parameter.finalOf(eventType, "event")) .appendAll(callbackStatements) .finish() .publicOverridesMethod("toString") ._(Stmt.load("Observer: " + parmClassName + " " + Arrays.toString(qualifiers)).returnValue()); final List<Statement> initStatements = new ArrayList<>(); final List<Statement> destroyStatements = new ArrayList<>(); final String subscrVar = method.getName() + "Subscription"; final String subscribeMethod; if (eventType.isAnnotationPresent(JsType.class)) { subscribeMethod = "subscribeJsType"; callBackBlock = getJsTypeSubscriptionCallback(decorable, controller); } else if (EnvUtil.isPortableType(eventType) && !EnvUtil.isLocalEventType(eventType)) { subscribeMethod = "subscribe"; callBackBlock = getSubscriptionCallback(decorable, controller); } else { subscribeMethod = "subscribeLocal"; callBackBlock = getSubscriptionCallback(decorable, controller); } final Statement subscribeStatement = Stmt.create(ctx).invokeStatic(CDI.class, subscribeMethod, parmClassName, callBackBlock.finish().finish()); if (isEnclosingTypeDependent) { initStatements.add(controller.setReferenceStmt(subscrVar, subscribeStatement)); destroyStatements.add(controller.getReferenceStmt(subscrVar, Subscription.class).invoke("remove")); } else { initStatements.add(subscribeStatement); } for (final Class<?> cls : EnvUtil.getAllPortableConcreteSubtypes(eventType.asClass())) { if (!EnvUtil.isLocalEventType(cls)) { final ContextualStatementBuilder routingSubStmt = Stmt.invokeStatic(ErraiBus.class, "get").invoke("subscribe", CDI.getSubjectNameByType(cls.getName()), Stmt.loadStatic(CDI.class, "ROUTING_CALLBACK")); if (isEnclosingTypeDependent) { final String subscrHandle = subscrVar + "For" + cls.getSimpleName(); initStatements.add(controller.setReferenceStmt(subscrHandle, routingSubStmt)); destroyStatements.add( Stmt.nestedCall(controller.getReferenceStmt(subscrHandle, Subscription.class)).invoke("remove")); } else { initStatements.add(routingSubStmt); } } } if (isEnclosingTypeDependent) { controller.addInitializationStatements(initStatements); controller.addDestructionStatements(destroyStatements); } else { controller.addFactoryInitializationStatements(initStatements); } } private boolean enclosingTypeIsDependentScoped(final Decorable decorable) { return decorable.isEnclosingTypeDependent(); } private BlockBuilder<AnonymousClassStructureBuilder> getSubscriptionCallback(final Decorable decorable, final FactoryController controller) { final MetaParameter parm = decorable.getAsParameter(); final MetaClass eventType = parm.getType().asBoxed(); final String parmClassName = eventType.getFullyQualifiedName(); final List<Annotation> annotations = InjectUtil.extractQualifiers(parm); final Annotation[] qualifiers = annotations.toArray(new Annotation[annotations.size()]); final Set<String> qualifierNames = new HashSet<>(CDI.getQualifiersPart(qualifiers)); final MetaClass callBackType = parameterizedAs(AbstractCDIEventCallback.class, typeParametersOf(eventType)); AnonymousClassStructureBuilder callBack = Stmt.newObject(callBackType).extend(); BlockBuilder<AnonymousClassStructureBuilder> callBackBlock; if (!qualifierNames.isEmpty()) { callBackBlock = callBack.initialize(); for (final String qualifierName : qualifierNames) { callBackBlock.append(Stmt.loadClassMember("qualifierSet").invoke("add", qualifierName)); } callBack = callBackBlock.finish(); } final List<Statement> fireEventStmts = new ArrayList<>(); if (!decorable.isEnclosingTypeDependent()) { fireEventStmts.add(Stmt.declareFinalVariable("instance", decorable.getEnclosingInjectable().getInjectedType(), Stmt.invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt()))); } fireEventStmts.add(decorable.call(Refs.get("event"))); callBackBlock = callBack.publicOverridesMethod("fireEvent", Parameter.finalOf(eventType, "event")) .appendAll(fireEventStmts) .finish() .publicOverridesMethod("toString") ._(Stmt.load("Observer: " + parmClassName + " " + Arrays.toString(qualifiers)).returnValue()); return callBackBlock; } private BlockBuilder<AnonymousClassStructureBuilder> getJsTypeSubscriptionCallback(final Decorable decorable, final FactoryController controller) { final MetaParameter parm = decorable.getAsParameter(); final MetaClass eventType = parm.getType().asBoxed(); final String parmClassName = eventType.getFullyQualifiedName(); final MetaClass callBackType = parameterizedAs(JsTypeEventObserver.class, typeParametersOf(eventType)); final AnonymousClassStructureBuilder callBack = Stmt.newObject(callBackType).extend(); BlockBuilder<AnonymousClassStructureBuilder> callBackBlock; final List<Statement> fireEventStmts = new ArrayList<>(); if (!decorable.isEnclosingTypeDependent()) { fireEventStmts.add(Stmt.declareFinalVariable("instance", decorable.getEnclosingInjectable().getInjectedType(), Stmt.invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt()))); } fireEventStmts.add(decorable.call(Refs.get("event"))); callBackBlock = callBack.publicOverridesMethod("onEvent", Parameter.finalOf(eventType, "event")) .appendAll(fireEventStmts) .finish() .publicOverridesMethod("toString") ._(Stmt.load("JsTypeObserver: " + parmClassName).returnValue()); return callBackBlock; } }