package com.dbxml.db.enterprise.labrador;

/*
 * 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: PersistentSession.java,v 1.2 2004/02/12 00:17:57 bradford Exp $
 */

import com.dbxml.db.client.CollectionClient;
import com.dbxml.db.client.dbXMLClient;
import com.dbxml.db.client.xmlrpc.dbXMLClientImpl;
import com.dbxml.labrador.ID;
import com.dbxml.labrador.configuration.Configuration;
import com.dbxml.labrador.configuration.ConfigurationCallback;
import com.dbxml.labrador.configuration.ConfigurationException;
import com.dbxml.labrador.sessions.Session;
import com.dbxml.labrador.sessions.SessionException;
import com.dbxml.labrador.types.TypeConversions;
import com.dbxml.labrador.types.Types;
import com.dbxml.labrador.types.Variant;
import com.dbxml.labrador.util.Base64;
import com.dbxml.util.dbXMLException;
import com.dbxml.xml.dom.DOMHelper;
import java.util.Iterator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

/**
 * PersistentSession is a configurable Session class for Labrador
 * that can be used to intercept Session lifecycle events in order
 * to persist certain Session resources.
 */

public class PersistentSession extends Session {
   private static final String DEFAULT_DRIVER = dbXMLClientImpl.class.getName();

   private static final String DRIVER = "driver";
   private static final String COLLECTION = "collection";
   private static final String PROPERTY = "property";
   private static final String NAME = "name";
   private static final String VALUE = "value";

   private static final String SESSION = "session";
   private static final String ID = "id";
   private static final String TIMESTAMP = "timestamp";
   private static final String RESOURCE = "resource";
   private static final String KEY = "key";
   private static final String TYPE = "type";
   
   private ID sessionID;
   
   private dbXMLClient client;
   private String colName;
   private CollectionClient col;
   private boolean dirty;
   
   public PersistentSession() {
   }
   
   public void setConfig(Configuration config) throws ConfigurationException {
      super.setConfig(config);

      colName = config.getAttribute(COLLECTION);
      
      String driver = config.getAttribute(DRIVER, DEFAULT_DRIVER);
      try {
         client = (dbXMLClient)Class.forName(driver).newInstance();
         config.processChildren(PROPERTY, new ConfigurationCallback() {
            public void process(Configuration cfg) {
               String name = cfg.getAttribute(NAME);
               String value = cfg.getAttribute(VALUE);
               client.setProperty(name, value);
            }
         });
      }
      catch ( ClassNotFoundException e ) {
         throw new ConfigurationException(e.getMessage());
      }
      catch ( InstantiationException e ) {
         throw new ConfigurationException(e.getMessage());
      }
      catch ( IllegalAccessException e ) {
         throw new ConfigurationException(e.getMessage());
      }
   }
   
   private CollectionClient getCollection() throws dbXMLException {
      if ( col == null ) {
         client.connect();
         col = client.getCollection(colName);
      }
      return col;
   }

   protected void _clear() {
      dirty = true;
      super._clear();
   }

   protected void _put(Object key, Object value) {
      dirty = true;
      super._put(key, value);
   }
   
   protected Object _remove(Object key) {
      dirty = true;
      return super._remove(key);
   }

   protected void sessionCreated(ID sessionID) throws SessionException {
      this.sessionID = sessionID;
      readSession();
   }
   
   protected void sessionDestroyed() throws SessionException {
      if ( !dirty )
         return;
      
      writeSession();
   }
   
   public void readSession() throws SessionException {
      try {
         Document doc;
         
         try {
            doc = getCollection().getDocument(sessionID.toString());
            if ( doc == null )
               return;
         }
         catch ( dbXMLException e ) {
            return;
         }

         Element rootElem = doc.getDocumentElement();
         if ( !rootElem.getTagName().equals(SESSION) )
            return;
         
         String id = rootElem.getAttribute(ID);
         if ( !sessionID.equals(id) )
            return;

         super._clear();
         
         NodeList nl = rootElem.getChildNodes();
         for ( int i = 0; i < nl.getLength(); i++ ) {
            Node n = nl.item(i);
            if ( n.getNodeType() != Node.ELEMENT_NODE )
               continue;
            
            Element e = (Element)n;
            if ( !e.getTagName().equals(RESOURCE) )
               continue;
            
            String key = e.getAttribute(KEY);
            int typeNum = Types.typeNum(e.getAttribute(TYPE));
            
            if ( typeNum != Types.UNKNOWN ) {
               StringBuffer sb = new StringBuffer();
               NodeList nl2 = e.getChildNodes();
               for ( int j = 0; j < nl2.getLength(); j++ ) {
                  Node n2 = nl2.item(j);
                  if ( n2.getNodeType() == Node.TEXT_NODE )
                     sb.append(((Text)n2).getData());
               }
   
               String str = Base64.decode(sb.toString().trim());
               Variant v = new Variant(str);
               Object value = TypeConversions.toTypedValue(typeNum, v);
               
               super._put(key, value);
            }
         }
         dirty = false;
      }
      catch ( Exception e ) {
         throw new SessionException(e.getMessage());
      }
   }

   public void writeSession() throws SessionException {
      try {
         String docKey = sessionID.toString();
         
         Document doc = DOMHelper.newDocument();
         Element rootElem = doc.createElement(SESSION);
         doc.appendChild(rootElem);
         
         rootElem.setAttribute(ID, docKey);
         rootElem.setAttribute(TIMESTAMP, Long.toString(System.currentTimeMillis()));
         
         Iterator iter = _keyIterator();
         while ( iter.hasNext() ) {
            Object key = iter.next();
            Object value = _get(key);
            
            int typeNum = Types.typeOf(value);
            if ( typeNum == Types.UNKNOWN )
               continue;
                        
            String type = Types.typeName(typeNum);
            Variant v = new Variant(value);
            String str = v.getString();
            if ( str == null )
               continue;
            
            Element resource = doc.createElement(RESOURCE);
            rootElem.appendChild(resource);
            
            resource.setAttribute(KEY, key.toString());
            resource.setAttribute(TYPE, type);
            
            Text text = doc.createTextNode(Base64.encode(str).trim());
            resource.appendChild(text);
         }
         
         getCollection().setDocument(docKey, doc);
         dirty = false;
      }
      catch ( Exception e ) {
         throw new SessionException(e.getMessage());
      }
   }
   
   public void removeSession() throws SessionException {
      try {
         String docKey = sessionID.toString();
         
         getCollection().remove(docKey);
         super._clear();
         dirty = false;
      }
      catch ( Exception e ) {
         throw new SessionException(e.getMessage());
      }
   }
}



