package org.dbxml.core.server.services;

/*
 * dbXML License, Version 1.0
 *
 *
 * Copyright (c) 1999-2001 The dbXML Group, L.L.C.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        The dbXML Group (http://www.dbxml.com/)."
 *    Alternately, this acknowledgment may appear in the software
 *    itself, if and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4. The names "dbXML" and "The dbXML Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    info@dbxml.com.
 *
 * 5. Products derived from this software may not be called "dbXML",
 *    nor may "dbXML" appear in their name, without prior written
 *    permission of The dbXML Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE DBXML GROUP OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: GopherServer.java,v 1.21 2001/08/03 06:34:09 tom Exp $
 */

import org.w3c.dom.*;
import org.dbxml.core.*;
import org.dbxml.core.Collection;
import org.dbxml.core.data.*;
import org.dbxml.core.request.*;
import org.dbxml.xml.*;
import org.dbxml.xml.dom.*;

import org.dbxml.server.util.*;
import org.dbxml.server.*;
import org.dbxml.server.services.*;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * Gopher Server for dbXML resources.  Ok, the first question out of
 * somebody's mouth will be "Why?" and the answer is simple: "Because."
 * Gopher was the Internet's original wide-spread information retrieval
 * protocol.  It was simple to use and implement, and presented a
 * uniform browsing mechanism.  The same can't be said about HTTP and
 * HTML.
 */

public final class GopherServer extends SocketServer implements ManagerService {
   private static final String WELCOME = "welcome";

   private static final String[] EmptyStrings = new String[0];

   private Database db = null;
   private String[] welcome = null;
   private ServiceManager serviceManager = null;

   public GopherServer() {
      port = 70;
   }

   public void setConfig(Configuration config) throws dbXMLException {
      super.setConfig(config);

      Configuration welConfig = config.getChild(WELCOME);
      if ( welConfig != null ) {
         StringTokenizer st = new StringTokenizer(welConfig.getValue(), "\r\n");
         List list = new ArrayList();
         while ( st.hasMoreTokens() ) {
            String s = st.nextToken().trim();
            list.add(s);
         }
         welcome = (String[])list.toArray(EmptyStrings);
      }
   }

   public String getProtocol() {
      return "gopher";
   }

   public void setServiceManager(ServiceManager serviceManager) {
      this.serviceManager = serviceManager;
   }

   public synchronized int initialize() {
      serverGroup = new ThreadGroup(getName());
      connectionGroup = new ThreadGroup(serverGroup, "Gopher Sessions");
      return RESULT_OK;
   }

   public int start() {
      return super.start();
   }

   public void run() {
      if ( !createServerSocket() )
         return;

      try {
         while ( true ) {
            new GopherSession(sock.accept()).start();
         }
      }
      catch ( Exception e ) {
      }
   }

   private class GopherSession extends Thread {
      private Socket sock = null;
      private String host = null;
      private String address = null;
      private PrintStream out = null;
      private DataInputStream in = null;
      private String resource = null;
      private String query = null;

      public GopherSession(Socket sock) {
         super("Gopher Session");
         setDaemon(true);
         this.sock = sock;
      }

      public void run() {
         try {
            sock.setTcpNoDelay(true);
            sock.setSoLinger(true, linger);
            sock.setSoTimeout(timeout);
         }
         catch ( Exception e ) {
         }

         address = sock.getInetAddress().getHostAddress();
         host = reverse ? sock.getInetAddress().getHostName()
                        : address;

         try {
            BufferedOutputStream bos = new BufferedOutputStream(sock.getOutputStream(), 4096);
            out = new PrintStream(bos);
            BufferedInputStream bis = new BufferedInputStream(sock.getInputStream(), 256);
            in = new DataInputStream(bis);
            resource = in.readLine();

            boolean showDBs = false;
            // If resource is empty then we need to show a list of available
            // database instances.
            if ( resource.trim().length() == 0 ) {
               displayDatabases();
               showDBs = true;
            }

            if ( ! showDBs ) {
               int dbIdx = resource.indexOf('/', 1);

               // Determine which database instance to use.
               String dbName = "";
               if (dbIdx != -1) {
                  dbName = resource.substring(1, dbIdx);
               }
               else {
                  dbName = resource.substring(1, resource.length());
               }

               db = Database.getDatabase(dbName);

               int idx = resource.indexOf("\t");
               if ( idx != -1 ) {
                  query = resource.substring(idx + 1);
                  resource = resource.substring(0, idx);
               }

               URIMapper map = db.getURIMapper(resource);

               int type = map.getObjectType();

               switch ( type ) {

                  case URIMapper.APPLICATION:
                     displayDatabase();
                     break;

                  case URIMapper.COLLECTION:
                     if ( query == null )
                        displayCollection(map.getCollection());
                     else
                        displayQuery(map.getCollection());
                     break;

                  case URIMapper.DOCUMENT:
                     Container c = map.getContainer();
                     displayDocument(c.getDocument());
                     kernel.logMessage(LogManager.LOG_GENERAL, host, "GOPHER "+c.getCanonicalName());
                     break;

                  default:
                     printInfo("No Such Object: "+resource);
                     printEOF();
                     break;

               }
               map.reclaim();
            }
            out.flush();
            sock.close();
         }
         catch ( Exception e ) {
         }
      }

      public void displayDatabases() {
         if ( welcome != null ) {
            for ( int i = 0; i < welcome.length; i++ )
               printInfo(welcome[i]);
            printInfo("");
         }

         try {
            printInfo("dbXML Server:");
            printInfo("");

            String[] dbs = Database.listDatabases();
            for ( int i = 0; i < dbs.length; i++ ) {
               printItem(dbs[i], "/" + dbs[i]);
            }
            printEOF();
         }
         catch ( Exception e ) {
            org.dbxml.Debug.printStackTrace(e);
         }
      }

      public void displayDatabase() {
         if ( welcome != null ) {
            for ( int i = 0; i < welcome.length; i++ )
               printInfo(welcome[i]);
            printInfo("");
         }

         try {
            printInfo("dbXML Server: "+db.getCanonicalName());
            printInfo("");

            String[] cols = db.listCollections();
            for ( int i = 0; i < cols.length; i++ ) {
               Collection c = db.getCollection(cols[i]);
               printItem(c.getName(), c.getCanonicalName());
            }
            printEOF();
         }
         catch ( Exception e ) {
            org.dbxml.Debug.printStackTrace(e);
         }
      }

      public void displayCollection(Collection col) {
         try {
            String colName = col.getCanonicalName();

            printInfo("Collection: "+colName);
            printInfo("");

            if ( col.getParentCollection() != null ) {
               Collection c = col.getParentCollection();
               printItem("["+c.getName()+"]", c.getCanonicalName());
            }
            else
               printItem("["+db.getName()+"]", db.getCanonicalName());

            printSearch("[XPath Query]", col.getCanonicalName());
            String[] collections = col.listCollections();
            for ( int i = 0; i < collections.length; i++ ) {
               Collection c = col.getCollection(collections[i]);
               printItem(c.getName(), c.getCanonicalName());
            }
            if ( col.getFiler() != null ) {
               RecordSet recs = col.getFiler().getRecordSet();
               displayRecordSet(col, recs);
            }
            printEOF();
         }
         catch ( dbXMLException e ) {
            org.dbxml.Debug.printStackTrace(e);
         }
      }

      public void displayQuery(Collection col) {
         try {
            String colName = col.getCanonicalName();

            printInfo("Search Results: ["+colName+"] - "+query);
            printInfo("");

            printItem("["+col.getName()+"]", col.getCanonicalName());
            printSearch("[XPath Query Again]", col.getCanonicalName());
            NodeSet ns = col.queryCollection("XPath", query, null);
            if ( ns != null && ns.hasMoreNodes() ) {
               StringBuffer sb = new StringBuffer("\n");
               while ( ns.hasMoreNodes() ) {
                  sb.append(TextWriter.toString(ns.getNextNode()));
                  sb.append("\n");
               }

               try {
                  DataInputStream dis = new DataInputStream(new ByteArrayInputStream(sb.toString().getBytes()));
                  while ( dis.available() > 0 ) {
                     String ln = dis.readLine();
                     printInfo(ln);
                  }
               }
               catch ( Exception e ) {
               }

            }
            else {
               printInfo("");
               printInfo("No Document Found: "+query);
            }
            printEOF();
         }
         catch ( dbXMLException e ) {
            org.dbxml.Debug.printStackTrace(e);
         }
      }

      public void displayDocument(Document doc) {
         try {
            //org.dbxml.Stopwatch s = new org.dbxml.Stopwatch("TextWriter", true);
            TextWriter.write(doc, out);
            //s.stop();
         }
         catch ( Exception e ) {
            org.dbxml.Debug.printStackTrace(e);
         }
      }

      public void displayRecordSet(Collection col, RecordSet set) {
         if ( set != null ) {
            try {
               while ( set.hasMoreRecords() ) {
                  Key key = set.getNextKey();
                  printFile(key, col.getCanonicalName());
               }
            }
            catch ( Exception e ) {
               printInfo("Error displaying RecordSet");
            }
         }
      }

      public void printInfo(String msg) {
         msg = msg.replace('\t', ' ');
         out.print('i');
         StringTokenizer st = new StringTokenizer(msg, "<>&", true);
         while ( st.hasMoreTokens() ) {
            String t = st.nextToken();
            if ( t.equals("<") )
               out.print("&lt;");
            else if ( t.equals(">") )
               out.print("&gt;");
            else if ( t.equals("&") )
               out.print("&amp;");
            else
               out.print(t);
         }
         out.print("\tinfo\t1\r\n");
      }

      public void printServer() {
         out.print('\t');
         out.print(hostname);
         out.print('\t');
         out.print(port);
         out.print("\r\n");
      }

      public void printItem(String item, String location) {
         out.print('1');
         out.print(item);
         out.print('\t');
         out.print(location);
         printServer();
      }

      public void printSearch(String prompt, String location) {
         out.print('7');
         out.print(prompt);
         out.print('\t');
         out.print(location);
         printServer();
      }

      public void printFile(Key file, String location) {
         String val;
         String name = file.toString();

         out.print('0');
         out.print(name);
         out.print('\t');
         out.print(location);
         out.print('/');
         out.print(name);
         printServer();
      }

      public void printEOF() {
         out.print(".\r\n");
      }
   }
}








