/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (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/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
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(Byte.class, Byte.class);
        envEntryTypes.put(Byte.TYPE, Byte.class);

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

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

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

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

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

        envEntryTypes.put(Float.class, Float.class);
        envEntryTypes.put(Float.TYPE, 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 (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.ejb.SessionContext.class ||
                    resourceType == javax.ejb.MessageDrivenContext.class || 
                    resourceType == javax.ejb.EJBContext.class ||
                    resourceType == javax.ejb.TimerService.class) {
            descriptorInfo.descriptors =
                getJmsDestinationReferenceDescriptors
                (logicalName, rcContexts);
            descriptorInfo.dependencyType =
                DependencyType.RESOURCE_ENV_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 = getResourceReferenceDescriptors
                (logicalName, rcContexts);
        }

        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;
    }
}
