/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.enterprise.deployment.annotation.handlers;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;

import javax.annotation.Resource;

import javax.jms.Queue;
import javax.jms.Topic;

import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.deployment.ResourceReferenceDescriptor;
import com.sun.enterprise.deployment.MessageDestinationReferenceDescriptor;
import com.sun.enterprise.deployment.EnvironmentProperty;
import com.sun.enterprise.deployment.JmsDestinationReferenceDescriptor;
import com.sun.enterprise.deployment.InjectionTarget;
import com.sun.enterprise.deployment.annotation.AnnotatedElementHandler;
import com.sun.enterprise.deployment.annotation.AnnotationInfo;
import com.sun.enterprise.deployment.annotation.AnnotationProcessorException;
import com.sun.enterprise.deployment.annotation.HandlerProcessingResult;
import com.sun.enterprise.deployment.annotation.context.ResourceContainerContext;
import com.sun.enterprise.deployment.annotation.impl.AnnotationUtils;

/**
 * This handler is responsible for handling the javax.annotation.Resource
 *
 */
public class ResourceHandler extends AbstractResourceHandler {

    // Map of all @Resource types that map to env-entries and their
    // corresponding types.  
    private static final Map<Class, Class> envEntryTypes;

    static {

        envEntryTypes = new HashMap<Class, Class>();

        envEntryTypes.put(String.class, String.class);

        envEntryTypes.put(Character.class, Character.class);
        envEntryTypes.put(Character.TYPE, Character.class);
        envEntryTypes.put(char.class, Character.class);

        envEntryTypes.put(Byte.class, Byte.class);
        envEntryTypes.put(Byte.TYPE, Byte.class);
        envEntryTypes.put(byte.class, Byte.class);

        envEntryTypes.put(Short.class, Short.class);
        envEntryTypes.put(Short.TYPE, Short.class);
        envEntryTypes.put(short.class, Short.class);

        envEntryTypes.put(Integer.class, Integer.class);
        envEntryTypes.put(Integer.TYPE, Integer.class);
        envEntryTypes.put(int.class, Integer.class);

        envEntryTypes.put(Long.class, Long.class);        
        envEntryTypes.put(Long.TYPE, Long.class);        
        envEntryTypes.put(long.class, Long.class);        

        envEntryTypes.put(Boolean.class, Boolean.class);
        envEntryTypes.put(Boolean.TYPE, Boolean.class);
        envEntryTypes.put(boolean.class, Boolean.class);

        envEntryTypes.put(Double.class, Double.class);
        envEntryTypes.put(Double.TYPE, Double.class);
        envEntryTypes.put(double.class, Double.class);

        envEntryTypes.put(Float.class, Float.class);
        envEntryTypes.put(Float.TYPE, Float.class);
        envEntryTypes.put(float.class, Float.class);

    }
        
    public ResourceHandler() {
    }

    /**
     * @return the annoation type this annotation handler is handling
     */
    public Class<? extends Annotation> getAnnotationType() {
        return Resource.class;
    }

    /**
     * This entry point is used both for a single @EJB and iteratively
     * from a compound @EJBs processor.
     */
    protected HandlerProcessingResult processAnnotation(AnnotationInfo ainfo,
            ResourceContainerContext[] rcContexts)
            throws AnnotationProcessorException {

        Resource resourceAn = (Resource)ainfo.getAnnotation();
        return processResource(ainfo, rcContexts, resourceAn);
    }

    protected HandlerProcessingResult processResource(AnnotationInfo ainfo,
                                   ResourceContainerContext[] rcContexts, 
                                   Resource resourceAn)
        throws AnnotationProcessorException {

        ResourceReferenceDescriptor resourceRefs[] = null;

        if (ElementType.FIELD.equals(ainfo.getElementType())) {
            Field f = (Field)ainfo.getAnnotatedElement();
            String targetClassName = f.getDeclaringClass().getName();

            String logicalName = resourceAn.name();

            // applying with default 
            if (logicalName.equals("")) {
                logicalName = targetClassName + "/" + f.getName();
            }

            // If specified, beanInterface() overrides parameter type
            // NOTE that default value is Object.class, not null
            Class resourceType = (resourceAn.type() == Object.class) ?
                    f.getType() : resourceAn.type();

            DescriptorInfo descriptorInfo = getDescriptors
                (resourceType, logicalName, rcContexts);
                 
            InjectionTarget target = new InjectionTarget();
            target.setFieldName(f.getName());
            target.setClassName(targetClassName);
            
            for (EnvironmentProperty desc : descriptorInfo.descriptors) {            
                desc.addInjectionTarget(target);
                    
                if (desc.getName().length() == 0) { // a new one
                    processNewAnnotation(desc, descriptorInfo.dependencyType,
                                         descriptorInfo.resourceType,
                                         logicalName, resourceAn);
                } else if (desc.getInjectResourceType() == null) {              
                    // if the optional resource type is not set, 
                    // set it using the resource type of field/method
                    desc.setInjectResourceType(
                        descriptorInfo.resourceType.getName());
                }
            }
        } else if (ElementType.METHOD.equals(ainfo.getElementType())) {

            Method m = (Method)ainfo.getAnnotatedElement();
            String targetClassName = m.getDeclaringClass().getName();

            String logicalName = resourceAn.name();
            if( logicalName.equals("") ) {
                // Derive javabean property name.
                String propertyName = 
                        getInjectionMethodPropertyName(m, ainfo);

                // prefixing with fully qualified type name 
                logicalName = targetClassName + "/" + propertyName;
            }

            validateInjectionMethod(m, ainfo);

            Class[] params = m.getParameterTypes();
            // If specified, beanInterface() overrides parameter type
            // NOTE that default value is Object.class, not null
            Class resourceType = (resourceAn.type() == Object.class) ?
                    params[0] : resourceAn.type();


            DescriptorInfo descriptorInfo = getDescriptors
                (resourceType, logicalName, rcContexts);

            InjectionTarget target = new InjectionTarget();
            target.setMethodName(m.getName());
            target.setClassName(targetClassName);
            
            for (EnvironmentProperty desc : descriptorInfo.descriptors) {
                desc.addInjectionTarget(target);
                    
                if (desc.getName().length() == 0) { // a new one
                    processNewAnnotation(desc, 
                                         descriptorInfo.dependencyType,
                                         descriptorInfo.resourceType,
                                         logicalName, resourceAn);
                }
            }

        } else if( ElementType.TYPE.equals(ainfo.getElementType()) ) {
            // name() and type() are required for TYPE-level @Resource
            String logicalName = resourceAn.name();
            Class resourceType = resourceAn.type();

            if( "".equals(logicalName) || resourceType == Object.class ) {
                Class c = (Class) ainfo.getAnnotatedElement();
                log(Level.SEVERE, ainfo,
                    localStrings.getLocalString(
                    "enterprise.deployment.annotation.handlers.invalidtypelevelresource",                
                    "Invalid TYPE-level @Resource with name() = [{0}] and type = [{1}] in {2}. Each TYPE-level @Resource must specify both name() and type().",
                    new Object[] { logicalName, resourceType, c }));
                return getDefaultFailedResult();
            }

            DescriptorInfo descriptorInfo = getDescriptors
                (resourceType, logicalName, rcContexts);

            for (EnvironmentProperty desc : descriptorInfo.descriptors) {
                    
                if (desc.getName().length() == 0) { // a new one
                    processNewAnnotation(desc,
                                         descriptorInfo.dependencyType,
                                         descriptorInfo.resourceType,
                                         logicalName, resourceAn);
                }
            }
        } 

        return getDefaultProcessedResult();
    }

    private class DescriptorInfo {

        public EnvironmentProperty[] descriptors;
        public DependencyType dependencyType;
        public Class resourceType;
    }


    private enum DependencyType {
        ENV_ENTRY,
        RESOURCE_REF,
        MESSAGE_DESTINATION_REF,
        RESOURCE_ENV_REF
    }



    private DescriptorInfo getDescriptors(Class resourceType,
        String logicalName, ResourceContainerContext[] rcContexts) {
            
        DescriptorInfo descriptorInfo = new DescriptorInfo();
        descriptorInfo.dependencyType = DependencyType.RESOURCE_REF;
        descriptorInfo.resourceType = resourceType;

        if( (resourceType == javax.jms.Queue.class) ||
            (resourceType == javax.jms.Topic.class) ) {
            descriptorInfo.descriptors = 
                getMessageDestinationReferenceDescriptors
                (logicalName, rcContexts);
            descriptorInfo.dependencyType = 
                DependencyType.MESSAGE_DESTINATION_REF;
        } else if ( resourceType == javax.sql.DataSource.class ||
                    resourceType == javax.jms.ConnectionFactory.class || 
                    resourceType == javax.jms.QueueConnectionFactory.class ||
                    resourceType == javax.jms.TopicConnectionFactory.class || 
                    resourceType == javax.xml.ws.WebServiceContext.class || 
                    resourceType == javax.mail.Session.class || 
                    resourceType == java.net.URL.class || 
                    resourceType == javax.resource.cci.ConnectionFactory.class 
                    || resourceType == org.omg.CORBA_2_3.ORB.class || 
                    resourceType == org.omg.CORBA.ORB.class || 
                    resourceType == javax.jms.XAConnectionFactory.class || 
                    resourceType == javax.jms.XAQueueConnectionFactory.class ||
                    resourceType == javax.jms.XATopicConnectionFactory.class ) {
            descriptorInfo.descriptors = getResourceReferenceDescriptors
                (logicalName, rcContexts);
            descriptorInfo.dependencyType = DependencyType.RESOURCE_REF;
        } else if( envEntryTypes.containsKey(resourceType) ) {
            descriptorInfo.descriptors = getEnvironmentPropertyDescriptors
                (logicalName, rcContexts);
            descriptorInfo.dependencyType = DependencyType.ENV_ENTRY;
            // Get corresponding class type.  This does the appropriate
            // mapping for primitives.  For everything, the type is
            // unchanged.
            descriptorInfo.resourceType = envEntryTypes.get(resourceType);
        } else {
            descriptorInfo.descriptors =
                getJmsDestinationReferenceDescriptors
                (logicalName, rcContexts);
            descriptorInfo.dependencyType = DependencyType.RESOURCE_ENV_REF;
        }
        return descriptorInfo;
    }

    /**
     * Return ResourceReferenceDescriptors with given name if exists or a new
     * one without name being set.
     * @param logicalName
     * @param rcContexts
     * @return an array of ResourceReferenceDescriptor
     */
    private ResourceReferenceDescriptor[] getResourceReferenceDescriptors(
            String logicalName, ResourceContainerContext[] rcContexts) {
        ResourceReferenceDescriptor resourceRefs[] =
                new ResourceReferenceDescriptor[rcContexts.length];
        for (int i = 0; i < rcContexts.length; i++) {
            ResourceReferenceDescriptor resourceRef =
                rcContexts[i].getResourceReference(logicalName);
            if (resourceRef == null) {
                resourceRef = new ResourceReferenceDescriptor();
                rcContexts[i].addResourceReferenceDescriptor(resourceRef);
            }
            resourceRefs[i] = resourceRef;
        }

        return resourceRefs;
    }

    /**
     * Return MessageDestinationReferenceDescriptors with given name 
     * if exists or a new one without name being set.
     * @param logicName
     * @param rcContexts
     * @return an array of message destination reference descriptors
     */
    private MessageDestinationReferenceDescriptor[] 
        getMessageDestinationReferenceDescriptors
        (String logicName, ResourceContainerContext[] rcContexts) {
            
        MessageDestinationReferenceDescriptor msgDestRefs[] =
                new MessageDestinationReferenceDescriptor[rcContexts.length];
        for (int i = 0; i < rcContexts.length; i++) {
            MessageDestinationReferenceDescriptor msgDestRef =
                rcContexts[i].getMessageDestinationReference(logicName);
            if (msgDestRef == null) {
               msgDestRef = new MessageDestinationReferenceDescriptor();
               rcContexts[i].addMessageDestinationReferenceDescriptor(
                   msgDestRef);
            }
            msgDestRefs[i] = msgDestRef;
        }

        return msgDestRefs;
    }

    /**
     * Return JmsDestinationReferenceDescriptors with given name
     * if exists or a new one without name being set.
     * @param logicName
     * @param rcContexts
     * @return an array of resource env reference descriptors
     */
    private JmsDestinationReferenceDescriptor[]
        getJmsDestinationReferenceDescriptors
        (String logicName, ResourceContainerContext[] rcContexts) {

        JmsDestinationReferenceDescriptor jmsDestRefs[] =
                new JmsDestinationReferenceDescriptor[rcContexts.length];
        for (int i = 0; i < rcContexts.length; i++) {
            JmsDestinationReferenceDescriptor jmsDestRef =
                rcContexts[i].getJmsDestinationReference(logicName);
            if (jmsDestRef == null) {
               jmsDestRef = new JmsDestinationReferenceDescriptor();
               rcContexts[i].addJmsDestinationReferenceDescriptor(
                   jmsDestRef);
            }
            jmsDestRefs[i] = jmsDestRef;
        }

        return jmsDestRefs;
    }

    /**
     * Return EnvironmentProperty descriptors with given name 
     * if exists or a new one without name being set.
     * @param logicalName
     * @param rcContexts
     * @return an array of EnvironmentProperty descriptors
     */
    private EnvironmentProperty[] getEnvironmentPropertyDescriptors
        (String logicalName, ResourceContainerContext[] rcContexts) {
            
        Set<EnvironmentProperty> envEntriesSet = 
            new HashSet<EnvironmentProperty>();

        for (int i = 0; i < rcContexts.length; i++) {
            EnvironmentProperty envEntry =
                rcContexts[i].getEnvEntry(logicalName);
            // For @Resource declarations that map to env-entries, if there
            // is no corresponding deployment descriptor entry that has a
            // value, it's treated as if the declaration doesn't exist.  
            // A common case is that the @Resource is applied to a field
            // with a default value which was not overridden by the deployer.
            if ((envEntry != null) && (envEntry.hasAValue()) ) {
                envEntriesSet.add(envEntry);
            }
        }

        return envEntriesSet.toArray(new EnvironmentProperty[] {});
    }

    private void processNewAnnotation(EnvironmentProperty desc,
                                      DependencyType dependencyType,
                                      Class resourceType, 
                                      String logicalName, Resource annotation){

        desc.setName(logicalName);
        desc.setDescription(annotation.description());
        
        if( dependencyType == DependencyType.ENV_ENTRY ) {

            desc.setType(resourceType.getName());
            desc.setMappedName(annotation.mappedName());

        } else if( dependencyType == DependencyType.MESSAGE_DESTINATION_REF ) {

            MessageDestinationReferenceDescriptor msgDestRef =
                (MessageDestinationReferenceDescriptor) desc;
            msgDestRef.setDestinationType(resourceType.getName());
            msgDestRef.setMappedName(annotation.mappedName());
        } else if( dependencyType == DependencyType.RESOURCE_ENV_REF ) {

            JmsDestinationReferenceDescriptor jmsDestRef =
                (JmsDestinationReferenceDescriptor) desc;
            jmsDestRef.setRefType(resourceType.getName());
            jmsDestRef.setMappedName(annotation.mappedName());
        } else if( dependencyType == DependencyType.RESOURCE_REF ) {
            
            desc.setType(resourceType.getName());
        
            ResourceReferenceDescriptor resRef = (ResourceReferenceDescriptor)
                desc;

            String authType = 
                (annotation.authenticationType() ==
                 Resource.AuthenticationType.CONTAINER) ?
                ResourceReferenceDescriptor.CONTAINER_AUTHORIZATION :
                ResourceReferenceDescriptor.APPLICATION_AUTHORIZATION;
            
            resRef.setAuthorization(authType);
            
            String sharable = annotation.shareable() ?
                ResourceReferenceDescriptor.RESOURCE_SHAREABLE :
                ResourceReferenceDescriptor.RESOURCE_UNSHAREABLE;
            
            resRef.setSharingScope(sharable);
            resRef.setMappedName(annotation.mappedName());
        }
        
        return;
    }
}
