org.agorava.core.cdi.AgoravaExtension.java Source code

Java tutorial

Introduction

Here is the source code for org.agorava.core.cdi.AgoravaExtension.java

Source

/*
 * Copyright 2013 Agorava
 *
 * 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.agorava.core.cdi;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import org.agorava.core.api.*;
import org.agorava.core.api.exception.AgoravaException;
import org.agorava.core.api.oauth.OAuthAppSettings;
import org.agorava.core.oauth.OAuthSessionImpl;
import org.agorava.core.oauth.scribe.OAuthProviderScribe;
import org.apache.deltaspike.core.util.bean.BeanBuilder;
import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.*;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import static com.google.common.collect.Sets.newHashSet;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;

/**
 * Agorava CDI extension to discover existing module and configured modules
 *
 * @author Antoine Sabot-Durand
 */
public class AgoravaExtension implements Extension, Serializable {

    private static final Set<Annotation> servicesQualifiersConfigured = newHashSet();
    private static Logger log = Logger.getLogger(AgoravaExtension.class.getName());
    private static BiMap<String, Annotation> servicesToQualifier = HashBiMap.create();
    private static boolean multiSession = false;
    private Map<Annotation, Set<Type>> overridedGenericServices = new HashMap<Annotation, Set<Type>>();

    /**
     * @return the set of all service's names present in the application
     */
    public static Set<String> getSocialRelated() {
        return servicesToQualifier.keySet();
    }

    /**
     * @return a {@link BiMap} associating service annotations (annotation bearing {@link ServiceRelated} meta-annotation) and their name present in the application
     */
    public static BiMap<String, Annotation> getServicesToQualifier() {
        return servicesToQualifier;
    }

    /**
     * @return the set of all service's qualifiers present in the application
     */
    public static Set<Annotation> getServicesQualifiersAvailable() {
        return servicesToQualifier.values();
    }

    /**
     * @return a boolean indicating if the application uses multi sessions or not
     */
    public static boolean isMultiSession() {
        return multiSession;
    }

    public static void setMultiSession(boolean ms) {
        multiSession = ms;
    }

    //-------------------- Utilities -------------------------------------

    /**
     * Inspects an annotated element for any annotations with the given meta
     * annotation. This should only be used for user defined meta annotations,
     * where the annotation must be physically present.
     *
     * @param element            The element to inspect
     * @param metaAnnotationType The meta annotation to search for
     * @return The annotation instances found on this element or an empty set if
     *         no matching meta-annotation was found.
     */
    public static Set<Annotation> getAnnotationsWithMeta(Annotated element,
            final Class<? extends Annotation> metaAnnotationType) {
        Set<Annotation> annotations = new HashSet<Annotation>();
        for (Annotation annotation : element.getAnnotations()) {
            if (annotation.annotationType().isAnnotationPresent(metaAnnotationType)) {
                annotations.add(annotation);
            }
        }
        return annotations;
    }

    void injectify(Annotation qual, AnnotatedType<?> at, AnnotatedTypeBuilder<?> atb) {
        //do a loop on all field to replace annotation mark by CDI annotations
        for (AnnotatedField af : at.getFields())
            if (af.isAnnotationPresent(Injectable.class)) {
                atb.addToField(af, InjectLiteral.instance);
                if (af.isAnnotationPresent(ApplyQualifier.class))
                    atb.addToField(af, qual);
            }

        //loop on constructors to do the same
        for (AnnotatedConstructor ac : at.getConstructors()) {
            if (ac.isAnnotationPresent(Injectable.class)) {

                atb.addToConstructor(ac, InjectLiteral.instance);
                Annotation[][] pa = ac.getJavaMember().getParameterAnnotations();
                //loop on args to detect marked param
                for (int i = 0; i < pa.length; i++)
                    for (int j = 0; j < pa[i].length; j++)
                        if (pa[i][j].equals(ApplyQualifierLiteral.instance))
                            atb.addToConstructorParameter(ac.getJavaMember(), i, qual);
            }
        }

        //loop on other methods (setters)
        for (AnnotatedMethod am : at.getMethods())
            if (am.isAnnotationPresent(Injectable.class)) {
                atb.addToMethod(am, InjectLiteral.instance);
                if (am.isAnnotationPresent(ApplyQualifierLiteral.class))
                    atb.addToMethod(am, qual);
            }

    }

    //----------------- Before Bean discovery Phase ----------------------------------

    /**
     * This observer is started at the beginning of the extension
     *
     * @param bbd
     */
    public void launchExtension(@Observes BeforeBeanDiscovery bbd) {
        log.info("Starting Agorava Framework initialization");
    }

    //----------------- Process AnnotatedType Phase ----------------------------------

    private <T> void processGenericAnnotatedType(ProcessAnnotatedType<T> pat) {
        AnnotatedType<T> at = pat.getAnnotatedType();
        if (!at.isAnnotationPresent(GenericRoot.class)) {
            log.log(INFO, "Found a Bean of class {0} overriding generic bean", at.getBaseType());
            Set<Annotation> qualifiers = AgoravaExtension.getAnnotationsWithMeta(at, ServiceRelated.class);
            if (qualifiers.size() != 0) {
                if (qualifiers.size() > 1)
                    throw new AgoravaException("Beans with multiple Service Related Qualifier are not supported");
                Annotation qual = Iterables.getOnlyElement(qualifiers);
                if (overridedGenericServices.containsKey(qual))
                    overridedGenericServices.get(qual).addAll(at.getTypeClosure());
                else
                    overridedGenericServices.put(qual, new HashSet<Type>(at.getTypeClosure()));

                Class<T> clazz = (Class) at.getBaseType();

                AnnotatedTypeBuilder<T> atb = new AnnotatedTypeBuilder<T>().readFromType(clazz).setJavaClass(clazz);

                injectify(qual, atb.create(), atb);
                pat.setAnnotatedType(atb.create());
            }

        } else
            pat.veto();
    }

    public void processGenericOauthService(@Observes ProcessAnnotatedType<OAuthServiceImpl> pat) {
        processGenericAnnotatedType(pat);
    }

    public void processGenericOauthProvider(@Observes ProcessAnnotatedType<OAuthProviderScribe> pat) {
        processGenericAnnotatedType(pat);
    }

    public void processGenericSession(@Observes ProcessAnnotatedType<OAuthSessionImpl> pat) {
        processGenericAnnotatedType(pat);
    }

    //----------------- Process Producer Phase ----------------------------------

    /**
     * This observer decorates the produced {@link OAuthAppSettings} by injecting its own qualifier and service name
     * and build the list of Qualifiers bearing the ServiceRelated meta annotation (configured services)
     *
     * @param pp the Process producer event
     */
    public void processOAuthSettingsProducer(@Observes final ProcessProducer<?, OAuthAppSettings> pp) {
        final AnnotatedMember<OAuthAppSettings> annotatedMember = (AnnotatedMember<OAuthAppSettings>) pp
                .getAnnotatedMember();
        final Annotation qual = Iterables
                .getLast(AgoravaExtension.getAnnotationsWithMeta(annotatedMember, ServiceRelated.class));
        final Producer<OAuthAppSettings> oldProducer = pp.getProducer();

        if (annotatedMember.isAnnotationPresent(OAuthApplication.class)) {
            /* TODO:CODE below for future support of OAuthAppSettings creation via annotation
                
            final OAuthApplication app = annotatedMember.getAnnotation(OAuthApplication.class);
                
               Class<? extends OAuthAppSettingsBuilder> builderClass = app.builder();
               OAuthAppSettingsBuilder builderOAuthApp = null;
               try {
                   builderOAuthApp = builderClass.newInstance();
               } catch (Exception e) {
                   throw new AgoravaException("Unable to create Settings Builder with class " + builderClass, e);
               }
                
               final OAuthAppSettingsBuilder finalBuilderOAuthApp = builderOAuthApp; */
        }

        pp.setProducer(new OAuthAppSettingsProducerDecorator(oldProducer, qual));

        log.log(INFO, "Found settings for {0}", qual);
        servicesQualifiersConfigured.add(qual);

        //settings = builderOAuthApp.name(servicesHub.getSocialMediaName()).params(app.params()).build();
    }

    //----------------- Process Bean Phase ----------------------------------

    private void CommonsProcessRemoteService(ProcessBean<RemoteApi> pb, BeanManager beanManager) {
        CreationalContext ctx = beanManager.createCreationalContext(null);
        Annotated annotated = pb.getAnnotated();
        Set<Annotation> qualifiers = AgoravaExtension.getAnnotationsWithMeta(annotated, ServiceRelated.class);
        if (qualifiers.size() != 1)
            throw new AgoravaException(
                    "A RemoteService bean should have one and only one service related Qualifier : "
                            + pb.getAnnotated().toString());
        Annotation qual = Iterables.getOnlyElement(qualifiers);
        log.log(INFO, "Found new service related qualifier : {0}", qual);

        Bean<?> beanSoc = pb.getBean();

        final RemoteApi smah = (RemoteApi) beanManager.getReference(beanSoc, RemoteApi.class, ctx);
        String name = smah.getServiceName();
        servicesToQualifier.put(name, qual);

        ctx.release();
    }

    public void processRemoteServiceRoot(@Observes ProcessBean<RemoteApi> pb, BeanManager beanManager) {
        CommonsProcessRemoteService(pb, beanManager);
    }

    public void processRemoteServiceRoot(@Observes ProcessProducerMethod<RemoteApi, ?> pb,
            BeanManager beanManager) {
        CommonsProcessRemoteService((ProcessBean<RemoteApi>) pb, beanManager);
    }

    //----------------- Process After Bean Discovery Phase ----------------------------------

    private <T> void beanRegisterer(Class<T> clazz, Annotation qual, Class<? extends Annotation> scope,
            AfterBeanDiscovery abd, BeanManager beanManager) {

        if (!(overridedGenericServices.containsKey(qual) && overridedGenericServices.get(qual).contains(clazz))) {

            AnnotatedType<T> at = beanManager.createAnnotatedType(clazz);
            AnnotatedTypeBuilder<T> atb = new AnnotatedTypeBuilder<T>().readFromType(clazz).setJavaClass(clazz);

            injectify(qual, at, atb);

            BeanBuilder<T> providerBuilder = new BeanBuilder<T>(beanManager).readFromType(atb.create())
                    .addQualifier(qual).scope(scope);

            Bean<T> bean = providerBuilder.create();

            abd.addBean(bean);
        }
    }

    /**
     * After all {@link OAuthAppSettings} were discovered we get their bean to retrieve the actual name of Social Media
     * and associates it with the corresponding Qualifier
     *
     * @param abd
     * @param beanManager
     */
    public void registerGenericBeans(@Observes AfterBeanDiscovery abd, BeanManager beanManager) {

        for (Annotation qual : servicesQualifiersConfigured) {

            beanRegisterer(OAuthProviderScribe.class, qual, ApplicationScoped.class, abd, beanManager);
            beanRegisterer(OAuthSessionImpl.class, qual, Dependent.class, abd, beanManager);
            beanRegisterer(OAuthServiceImpl.class, qual, ApplicationScoped.class, abd, beanManager);
        }

        if (servicesQualifiersConfigured.size() != servicesToQualifier.size())
            log.log(WARNING,
                    "Some Service modules present in the application are not configured so won't be available"); //TODO:list the service without config
    }

    //--------------------- After Deployment validation phase

    public void endOfExtension(@Observes AfterDeploymentValidation adv) {
        log.info("Agorava initialization complete");
    }

}