/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2000-2009 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 mq/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 mq/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. 
 */

/*
 * @(#)PacketHandler.java	1.45 06/28/07
 */ 

package com.sun.messaging.jmq.jmsserver.data;

import java.io.IOException;
import java.util.Hashtable;
import java.security.Principal;
import java.security.AccessControlException;
import com.sun.messaging.jmq.io.*;
import com.sun.messaging.jmq.util.DestType;
import com.sun.messaging.jmq.util.ServiceType;
import com.sun.messaging.jmq.util.log.*;
import com.sun.messaging.jmq.jmsserver.resources.BrokerResources;
import com.sun.messaging.jmq.jmsserver.service.*;
import com.sun.messaging.jmq.jmsserver.service.imq.*;
import com.sun.messaging.jmq.jmsserver.auth.AccessController;
import com.sun.messaging.jmq.jmsserver.util.*;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.core.Destination;
import com.sun.messaging.jmq.jmsserver.GlobalProperties;
import com.sun.messaging.jmq.util.admin.MessageType;
import com.sun.messaging.jmq.jmsserver.core.DestinationUID;

/**
 * super classes which deal with handling specific
 * message types
 */
public abstract class PacketHandler 
{
    private static boolean DEBUG = false;

    public static boolean getDEBUG() {
        return DEBUG;
    }

    protected final Logger logger = Globals.getLogger();

    /**
     * method to handle processing the specific packet associated
     * with this PacketHandler
     * @returns true if the packet can be freed
     */
    public abstract boolean handle(IMQConnection con, Packet msg) throws
        BrokerException;

    public void handleForbidden(IMQConnection con, Packet msg,
                                int replyType) throws BrokerException {
        Packet reply = new Packet(con.useDirectBuffers());
        if (DEBUG) {
        logger.log(Logger.DEBUG, "handle forbidden: sending "+PacketType.getString(replyType));
        }
        reply.setPacketType(replyType);
        reply.setConsumerID(msg.getConsumerID());
        Hashtable hash = new Hashtable();
        hash.put("JMQStatus", new Integer(Status.FORBIDDEN));
        reply.setProperties(hash);
        con.sendControlMessage(reply);
    }
   
    /**
     * entry point for destination access control check
     */
    public static void checkPermission(Packet msg, IMQConnection con)
                            throws AccessControlException, IOException,
                                   ClassNotFoundException, BrokerException
    {
        int id = msg.getPacketType();
        String op = PacketType.mapOperation(id);
        if (op == null) {
            return;
        }
        Hashtable prop = msg.getProperties();

        String destination = (String)prop.get("JMQDestination");
        //all non-null op should have destination
        if (destination == null) { 
            throw new BrokerException(Globals.getBrokerResources().getString(
               BrokerResources.X_INTERNAL_EXCEPTION,
               "checkPermission() no JMQDestionation"));
        }

        Integer dtype = (Integer)prop.get("JMQDestType");
        if (dtype == null) {
            throw new BrokerException(Globals.getBrokerResources().getString(
                BrokerResources.X_INTERNAL_EXCEPTION,
                "checkPermission() no JMQDestType"));
        }
        int destTypeInt = dtype.intValue();

        checkPermission(id, op, destination, destTypeInt, con);

    }

    public static void checkPermission(int id, String op, String destination, int destTypeInt, 
                                       IMQConnection con)
                            throws AccessControlException,
                                   BrokerException
    {
        //Temporary destination should return null
        String destTypeStr = DestType.queueOrTopic(destTypeInt);
        if (destTypeStr == null) { 
            return;
        }

        Service service  = con.getService();
        int serviceType = service.getServiceType();

        if (!checkIsNonAdminDest(con, service, serviceType, destination)) {
            return;
        }

        String acdestination = destination;

        //if autocreate false, return to normal path
        if (id == PacketType.CREATE_DESTINATION) {
            if (!checkForAutoCreate(destination, destTypeInt)) { 
                return;
            }
            DestinationUID duid = DestinationUID.getUID(destination, DestType.isQueue(destTypeInt));
            if (duid == null) {
                throw new BrokerException(Globals.getBrokerResources().getString(
                    BrokerResources.X_INTERNAL_EXCEPTION,
                    "checkPermission() can't get destination uid for " + destination));
            }
            Destination d = Destination.getDestination(duid);
            if (d != null && !d.isAutoCreated()) {
                return;
            }
            acdestination = null;
        } 
        checkPermission(con, service, serviceType,
                        op, acdestination, destTypeStr, destination);
        
        // audit logging for destination authorization
        Globals.getAuditSession().destinationAuth(con.getUserName(), con.remoteHostString(),destTypeStr, acdestination, op, true);
   }

    /**
     * @return true if need access control check on create
     *         false if no need access control check
     */
    private static boolean checkForAutoCreate(String destination, int destType) { 
        if (DestType.isQueue(destType)) {
            if (!GlobalProperties.getGlobalProperties().AUTOCREATE_QUEUE) {
                return false;
            }
        }
        else if (DestType.isTopic(destType)) {
            if (!GlobalProperties.getGlobalProperties().AUTOCREATE_TOPIC) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return true destination is not JMQ_ADMIN_DEST
     *         false ADMIN service access JMQ_ADMIN_DEST
     * @exception non ADMIN service access JMQ_ADMIN_DEST
     * @exception restricted ADMIN service access non JMQ_ADMIN_DEST
     */
    private static boolean checkIsNonAdminDest(IMQConnection con,
                    Service service, int serviceType, String destination)
                            throws AccessControlException, BrokerException {

        if (!destination.equals(MessageType.JMQ_ADMIN_DEST)) {
            if (serviceType == ServiceType.ADMIN 
                && con.getAccessController().isRestrictedAdmin()) {
            String emsg = Globals.getBrokerResources().getKString(
                        BrokerResources.X_RESTRICTED_ADMIN_NON_JMQADMINDEST,
                        destination, service.getName());
            Globals.getLogger().log(Logger.WARNING, emsg);
            throw new AccessControlException(emsg);
            }
            if (!destination.equals(MessageType.JMQ_BRIDGE_ADMIN_DEST)) {
                 return true;
            }
        }
        /*
         * Protect JMQ_ADMIN_DEST to ADMIN service only
         * ADMIN service (when get here the connection has been
         * authenticated and service type connection access control
         * has been applied) should automatically to be allowed to
         * access JMQ_ADMIN_DEST
         */
        if (serviceType == ServiceType.ADMIN) {
            return false;
        }
        String name = "";
        Principal pp = con.getAccessController().getAuthenticatedName();
        if (pp!= null) {
            name = pp.getName();
        }
        String[] args = {name, service.getName(),
                         ServiceType.getServiceTypeString(serviceType)};
        String emsg = Globals.getBrokerResources().getKString(
                       BrokerResources.X_FORBIDDEN_JMQ_ADMIN_DEST, args);
        Globals.getLogger().log(Logger.WARNING, emsg);
        throw new AccessControlException(emsg);
    }

    /**
     * delegate to AccessController.checkDestinationPermission
     *
     * @param con connection
     * @param service
     * @param serviceType
     * @param op operation 
     * @param destination null if op = create otherwise = dest
     * @param destTypeStr 
     * @param dest the destination as JMQDestination property
     */
    private static void checkPermission(IMQConnection con, 
                                 Service service, 
                                 int serviceType,
                                 String op, 
                                 String destination,
                                 String destType,
                                 String dest) 
                                 throws AccessControlException {
        try {

        con.getAccessController().checkDestinationPermission(
                      service.getName(),
                      ServiceType.getServiceTypeString(serviceType),
                      op, destination, destType);

        } catch (AccessControlException e) {

        if (destination !=  null) {
        String[] args = {op, destType, destination};
        String emsg = Globals.getBrokerResources().getKString(
                         BrokerResources.W_DESTINATION_ACCESS_DENIED, args);
        Globals.getLogger().log(Logger.WARNING, emsg + " - " + e.getMessage(), e);
        }
        else { //AC_DESTCREATE
        String[] args = {op, destType, dest};
        String emsg = Globals.getBrokerResources().getKString(
                         BrokerResources.W_DESTINATION_CREATE_DENIED, args);
        Globals.getLogger().log(Logger.WARNING, emsg + " - " + e.getMessage(), e);
        }

        throw e;
        }
    }

    public void checkServiceRestriction(Packet msg, IMQConnection con)
        throws BrokerException, IOException, ClassNotFoundException {

        Service service = con.getService();
        ServiceRestriction[] srs = service.getServiceRestrictions();
        if (srs == null) return;
        ServiceRestriction sr = null;
        for (int i = 0; i < srs.length; i++) {
            sr = srs[i];  
            if (sr == ServiceRestriction.NO_SYNC_WITH_MASTERBROKER) {
                if (service.getServiceType() != ServiceType.NORMAL) return;
                int id = msg.getPacketType();
                if (id != PacketType.CREATE_DESTINATION && 
                    id != PacketType.ADD_CONSUMER && id != PacketType.ADD_PRODUCER) {
                    return;
                }
                Hashtable prop = msg.getProperties();
                String dest = (String)prop.get("JMQDestination");
                int dtype = ((Integer)prop.get("JMQDestType")).intValue();
                if (id == PacketType.CREATE_DESTINATION) {
                    if (!checkForAutoCreate(dest, dtype)) return;
                    DestinationUID duid = DestinationUID.getUID(dest, DestType.isQueue(dtype));
                    Destination d = Destination.getDestination(duid);
                    if (d != null) return;
                    if (DestType.isQueue(dtype) && DestType.isTemporary(dtype)) return;
                    String[] args = {dest, service.toString(), sr.toString()};
                    throw new ServiceRestrictionException(
                          Globals.getBrokerResources().getKString(
                          BrokerResources.X_SERVICE_RESTRICTION_AUTO_CREATE_DEST, args),
                          Status.UNAVAILABLE);
                } else if (DestType.isTopic(dtype)) {
                    if (id == PacketType.ADD_PRODUCER) {
                        String[] args = {dest, service.toString(), sr.toString()};
                        throw new ServiceRestrictionException(
                              Globals.getBrokerResources().getKString(
                              BrokerResources.X_SERVICE_RESTRICTION_TOPIC_PRODUCER, args),
                              Status.UNAVAILABLE);
                    } else {
                        String[] args = {dest, service.toString(), sr.toString()};
                        throw new ServiceRestrictionException(
                              Globals.getBrokerResources().getKString(
                              BrokerResources.X_SERVICE_RESTRICTION_TOPIC_CONSUMER, args),
                              Status.UNAVAILABLE);
                    }
                }

            } else {
                throw new BrokerException(Globals.getBrokerResources().getString(
                BrokerResources.E_INTERNAL_BROKER_ERROR, 
                "Unknown service restriction "+sr+" on service "+service));
            }
        }
    }
}
