package com.dbxml.db.core.trigger;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: TriggerManager.java,v 1.4 2004/02/12 00:17:57 bradford Exp $
 */

import com.dbxml.db.core.ClassResolver;
import com.dbxml.db.core.Collection;
import com.dbxml.db.core.DBException;
import com.dbxml.db.core.FaultCodes;
import com.dbxml.db.core.data.Key;
import com.dbxml.db.core.security.Access;
import com.dbxml.db.core.security.SecurityException;
import com.dbxml.db.core.transaction.Transaction;
import com.dbxml.util.Configuration;
import com.dbxml.util.ConfigurationCallback;
import com.dbxml.util.SimpleConfigurable;
import com.dbxml.util.dbXMLException;
import java.util.Map;
import java.util.TreeMap;
import java.util.Collections;

/**
 * TriggerManager manages Trigger instances
 */

public class TriggerManager extends SimpleConfigurable {
   private static final Trigger[] EmptyTriggers = new Trigger[0];
   private static final String[] EmptyStrings = new String[0];

   private static final String TRIGGER = "trigger";
   private static final String NAME = "name";
   private static final String CLASS = "class";

   private Map triggers = Collections.synchronizedMap(new TreeMap()); // String to Trigger
   private Trigger[] triggerList = EmptyTriggers;
   private Collection collection;

   public TriggerManager(Collection collection) {
      this.collection = collection;
   }

   public void setConfig(Configuration config) throws dbXMLException {
      super.setConfig(config);
      config.processChildren(TRIGGER,
         new ConfigurationCallback() {
            public void process(Configuration cfg) {
               String className = cfg.getAttribute(CLASS);
               try {
                  register(ClassResolver.get(className), cfg);
               }
               catch ( Exception e ) {
                  e.printStackTrace(System.err);
               }
            }
         });
   }

   private void checkAccess(int permissions) throws SecurityException {
      collection.getDatabase().getSecurityManager().access(collection.getCanonicalName(), permissions);
   }

   /**
    * list returns a list of the Triggers that this TriggerManager has
    * registered.
    *
    * @return An array containing the Trigger names
    */
   public String[] list() {
      return (String[])triggers.keySet().toArray(EmptyStrings);
   }

   /**
    * drop physically removes the specified Trigger and any
    * associated system resources that the Trigger uses.
    *
    * @param name The Trigger to drop
    * @return Whether or not the Trigger was dropped
    */
   public boolean drop(final String name) throws DBException {
      checkAccess(Access.CREATE);

      Trigger trg = get(name);
      if ( trg == null )
         throw new TriggerException(FaultCodes.TRG_TRIGGER_NOT_FOUND, "Trigger '"+name+"' does not exist");

      unregister(name);
      config.processChildren(TRIGGER,
         new ConfigurationCallback() {
            public void process(Configuration cfg) {
               try {
                  if ( cfg.getAttribute(NAME).equals(name) )
                     cfg.delete();
               }
               catch ( Exception e ) {
                  e.printStackTrace(System.err);
               }
            }
         });
      collection.getDatabase().flushConfig();
      return true;
   }

   /**
    * create creates a new Trigger object and any associated
    * system resources that the Trigger will need.
    *
    * @param cfg The Trigger's configuration
    * @return The Trigger that was created
    */
   public Trigger create(Configuration cfg) throws DBException {
      checkAccess(Access.CREATE);

      String className = cfg.getAttribute(CLASS);
      try {
         // Check for duplicates
         String name = cfg.getAttribute(NAME);
         Configuration[] cfgs = config.getChildren();
         for ( int i = 0; i < cfgs.length; i++ )
            if ( cfgs[i].getAttribute(NAME).equals(name) )
               throw new DuplicateTriggerException("Duplicate Trigger '" + name + "'");

         Class c = ClassResolver.get(className);
         Trigger trigger = register(c, cfg);

         config.add(cfg);

         collection.getDatabase().flushConfig();
         return trigger;
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         throw new CannotCreateException("Cannot create Trigger", e);
      }
   }

   private Trigger register(Class c, Configuration cfg) throws DBException {
      String name = null;
      try {
         Trigger trigger = (Trigger)c.newInstance();
         initialize(trigger, cfg);
         name = trigger.getName();

         if ( name == null || name.trim().equals("") )
            throw new CannotCreateException("No name specified");

         triggers.put(name, trigger);

         triggerList = (Trigger[])triggers.values().toArray(EmptyTriggers);
         return trigger;
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         throw new CannotCreateException("'" + name + "' is not a valid Trigger", e);
      }
   }

   private void unregister(String name) {
      triggers.remove(name);
      triggerList = (Trigger[])triggers.values().toArray(EmptyTriggers);
   }

   private void initialize(Trigger trigger, Configuration cfg) throws dbXMLException {
      trigger.setConfig(cfg);
      trigger.setCollection(collection);
   }

   /**
    * get retrieves an Trigger by name, and will also do the work of
    * instantiation and pool-retrieval.
    *
    * @param name The Trigger name
    * @return The Trigger
    */
   public Trigger get(String name) throws DBException {
      return (Trigger)triggers.get(name);
   }

   public String getCanonicalName(String name) {
      return collection.getCanonicalName() + "/" + name;
   }

   /**
    * fireBeforeInsert fires beforeInsert events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param newObj the new Object
    * @return The modified or original new Object
    */
   public Object fireBeforeInsert(Transaction tx, Key key, Object newObj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         newObj = triggerList[i].beforeInsert(tx, key, newObj);
      return newObj;
   }

   /**
    * fireAfterInsert fires afterInsert events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param newObj The new Object
    */
   public void fireAfterInsert(Transaction tx, Key key, Object newObj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         triggerList[i].afterInsert(tx, key, newObj);
   }

   /**
    * fireBeforeUpdate fires beforeUpdate events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param oldObj The old Object
    * @param newObj The new Object
    * @return The modified or original new Object*
    */
   public Object fireBeforeUpdate(Transaction tx, Key key, Object oldObj, Object newObj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         newObj = triggerList[i].beforeUpdate(tx, key, oldObj, newObj);
      return newObj;
   }

   /**
    * fireAfterUpdate fires afterUpdate events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param oldObj The old Object
    * @param newObj The new Object
    */
   public void fireAfterUpdate(Transaction tx, Key key, Object oldObj, Object newObj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         triggerList[i].afterUpdate(tx, key, oldObj, newObj);
   }

   /**
    * fireBeforeDelete fires beforeDelete events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param oldObj The old Object
    */
   public void fireBeforeDelete(Transaction tx, Key key, Object oldObj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         triggerList[i].beforeDelete(tx, key, oldObj);
   }

   /**
    * fireAfterDelete fires afterDelete events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param oldObj The old Object
    */
   public void fireAfterDelete(Transaction tx, Key key, Object oldObj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         triggerList[i].afterDelete(tx, key, oldObj);
   }

   /**
    * fireBeforeGet fires beforeGet events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    */
   public void fireBeforeGet(Transaction tx, Key key) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         triggerList[i].beforeGet(tx, key);
   }

   /**
    * fireAfterGet fires afterGet events to all of the registered
    * Triggers, unless a DBException is thrown.
    *
    * @param tx The controlling Transaction
    * @param key The Object Key
    * @param obj The Object
    */
   public void fireAfterGet(Transaction tx, Key key, Object obj) throws DBException {
      for ( int i = 0; i < triggerList.length; i++ )
         triggerList[i].afterGet(tx, key, obj);
   }
}
