package example;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.sql.*;
import org.postgresql.largeobject.*;

/**
 * This example is a small application that stores and displays images
 * held on a postgresql database.
 *
 * Before running this application, you need to create a database, and
 * on the first time you run it, select "Initialise" in the "PostgreSQL"
 * menu.
 *
 * Important note: You will notice we import the org.postgresql.largeobject
 * package, but don't import the org.postgresql package. The reason for this is
 * that importing postgresql can confuse javac (we have conflicting class names
 * in org.postgresql.* and java.sql.*). This doesn't cause any problems, as
 * long as no code imports org.postgresql.
 *
 * Under normal circumstances, code using any jdbc driver only needs to import
 * java.sql, so this isn't a problem.
 *
 * It's only if you use the non jdbc facilities, do you have to take this into
 * account.
 *
 * Note: For PostgreSQL 6.4, the driver is now Thread safe, so this example
 * application has been updated to use multiple threads, especially in the
 * image storing and retrieving methods.
 */

public class ImageViewer implements ItemListener
{
  Connection db;
  Statement stat;
  LargeObjectManager lom;
  Frame frame;
  Label label;		// Label used to display the current name
  List list;		// The list of available images
  imageCanvas canvas;	// Canvas used to display the image
  String currentImage;	// The current images name
  
  // This is a simple component to display our image
  public class imageCanvas extends Canvas
  {
    // holds the image
    private Image image;
    
    // holds the background buffer
    private Image bkg;
    
    // the size of the buffer
    private Dimension size;
    
    public imageCanvas()
    {
      image=null;
    }
    
    public void setImage(Image img)
    {
      image=img;
      repaint();
    }
    
    // This defines our minimum size
    public Dimension getMinimumSize()
    {
      return new Dimension(400,400);
    }
    
    public Dimension getPreferedSize()
    {
      return getMinimumSize();
    }
    
    public void update(Graphics g)
    {
      paint(g);
    }
    
    /**
     * Paints the image, using double buffering to prevent screen flicker
     */
    public void paint(Graphics gr)
    {
      Dimension s = getSize();
      
      if(size==null || bkg==null || !s.equals(size)) {
	size = s;
	bkg = createImage(size.width,size.height);
      }
      
      // now set the background
      Graphics g = bkg.getGraphics();
      g.setColor(Color.gray);
      g.fillRect(0,0,s.width,s.height);
      
      // now paint the image over the background
      if(image!=null)
	g.drawImage(image,0,0,this);
      
      // dispose the graphics instance
      g.dispose();
      
      // paint the image onto the component
      gr.drawImage(bkg,0,0,this);
      
    }
    
  }
  
  public ImageViewer(Frame f,String url,String user,String password) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException
  {
    frame = f;
    
    MenuBar mb = new MenuBar();
    Menu m;
    MenuItem i;
    
    f.setMenuBar(mb);
    mb.add(m = new Menu("PostgreSQL"));
    m.add(i= new MenuItem("Initialise"));
    i.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	ImageViewer.this.init();
      }
    });
    
    m.add(i= new MenuItem("Exit"));
    ActionListener exitListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	ImageViewer.this.close();
      }
    };
    m.addActionListener(exitListener);
    
    mb.add(m = new Menu("Image"));
    m.add(i= new MenuItem("Import"));
    ActionListener importListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	ImageViewer.this.importImage();
      }
    };
    i.addActionListener(importListener);
    
    m.add(i= new MenuItem("Remove"));
    ActionListener removeListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	ImageViewer.this.removeImage();
      }
    };
    i.addActionListener(removeListener);
    
    // To the north is a label used to display the current images name
    f.add("North",label = new Label());
    
    // We have a panel to the south of the frame containing the controls
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    Button b;
    p.add(b=new Button("Refresh List"));
    b.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	ImageViewer.this.refreshList();
      }
    });
    p.add(b=new Button("Import new image"));
    b.addActionListener(importListener);
    p.add(b=new Button("Remove image"));
    b.addActionListener(removeListener);
    p.add(b=new Button("Quit"));
    b.addActionListener(exitListener);
    f.add("South",p);
    
    // And a panel to the west containing the list of available images
    f.add("West",list=new List());
    list.addItemListener(this);
    
    // Finally the centre contains our image
    f.add("Center",canvas = new imageCanvas());
    
    // Load the driver
    Class.forName("org.postgresql.Driver");
    
    // Connect to database
    db = DriverManager.getConnection(url, user, password);
    
    // Create a statement
    stat = db.createStatement();
    
    // Also, get the LargeObjectManager for this connection
    lom = ((org.postgresql.Connection)db).getLargeObjectAPI();
    
    // Now refresh the image selection list
    refreshList();
  }
  
  
  /**
   * This method initialises the database by creating a table that contains
   * the image names, and Large Object OID's
   */
  public void init()
  {
    try {
      //db.setAutoCommit(true);
      stat.executeUpdate("create table images (imgname name,imgoid oid)");
      label.setText("Initialised database");
      db.commit();
    } catch(SQLException ex) {
      label.setText(ex.toString());
    }
    
    // This must run outside the previous try{} catch{} segment
    //try {
    //db.setAutoCommit(true);
    //} catch(SQLException ex) {
    //label.setText(ex.toString());
    //}
  }
  
  /**
   * This closes the connection, and ends the application
   */
  public void close()
  {
    try {
      db.close();
    } catch(SQLException ex) {
      System.err.println(ex.toString());
    }
    System.exit(0);
  }
  
  /**
   * This imports an image into the database, using a Thread to do this in the
   * background.
   */
  public void importImage()
  {
    FileDialog d = new FileDialog(frame,"Import Image",FileDialog.LOAD);
    d.setVisible(true);
    String name = d.getFile();
    String dir = d.getDirectory();
    d.dispose();
    
    // now start the true importer
    Thread t = new importer(db,name,dir);
    //t.setPriority(Thread.MAX_PRIORITY);
    t.start();
  }
  
  /**
   * This is an example of using a thread to import a file into a Large Object.
   * It uses the Large Object extension, to write blocks of the file to the
   * database.
   */
  class importer extends Thread
  {
    String name,dir;
    Connection db;
    
    public importer(Connection db,String name,String dir) {
      this.db = db;
      this.name = name;
      this.dir = dir;
    }
    
    public void run() {
      
      // Now the real import stuff
      if(name!=null && dir!=null) {
	Statement stat = null;
	
	try {
	  // fetch the large object manager
	  LargeObjectManager lom = ((org.postgresql.Connection)db).getLargeObjectAPI();
	  
	  db.setAutoCommit(false);
	  
	  // A temporary buffer - this can be as large as you like
	  byte buf[] = new byte[2048];
	  
	  // Open the file
	  FileInputStream fis = new FileInputStream(new File(dir,name));
	  
	  // Now create the large object
	  int oid = lom.create();
	  LargeObject blob = lom.open(oid);
	  
	  // Now copy the file into the object.
	  //
	  // Note: we dont use write(buf), as the last block is rarely the same
	  // size as our buffer, so we have to use the amount read.
	  int s,t=0;
	  while((s=fis.read(buf,0,buf.length))>0) {
	    t+=s;
	    blob.write(buf,0,s);
	  }
	  
	  // Close the object
	  blob.close();
	  
	  // Now store the entry into the table
	  
	  // As we are a different thread to the other window, we must use
	  // our own thread
	  stat = db.createStatement();
	  stat.executeUpdate("insert into images values ('"+name+"',"+oid+")");
	  db.commit();
	  db.setAutoCommit(false);
	  
	  // Finally refresh the names list, and display the current image
	  ImageViewer.this.refreshList();
	  ImageViewer.this.displayImage(name);
	} catch(Exception ex) {
	  label.setText(ex.toString());
	} finally {
	  // ensure the statement is closed after us
	  try {
	    if(stat != null)
	      stat.close();
	  } catch(SQLException se) {
	    System.err.println("closing of Statement failed");
	  }
	}
      }
    }
  }
  
  /**
   * This refreshes the list of available images
   */
  public void refreshList()
  {
    try {
      // First, we'll run a query, retrieving all of the image names
      ResultSet rs = stat.executeQuery("select imgname from images order by imgname");
      if(rs!=null) {
	list.removeAll();
	while(rs.next())
	  list.addItem(rs.getString(1));
	rs.close();
      }
    } catch(SQLException ex) {
      label.setText(ex.toString()+" Have you initialised the database?");
    }
  }
  
  /**
   * This removes an image from the database
   *
   * Note: With postgresql, this is the only way of deleting a large object
   * using Java.
   */
  public void removeImage()
  {
    try {
      //
      // Delete any large objects for the current name
      //
      // Note: We don't need to worry about being in a transaction
      // here, because we are not opening any blobs, only deleting
      // them
      //
      ResultSet rs = stat.executeQuery("select imgoid from images where imgname='"+currentImage+"'");
      if(rs!=null) {
	// Even though there should only be one image, we still have to
	// cycle through the ResultSet
	while(rs.next()) {
	  lom.delete(rs.getInt(1));
	}
      }
      rs.close();
      
      // Finally delete any entries for that name
      stat.executeUpdate("delete from images where imgname='"+currentImage+"'");
      
      label.setText(currentImage+" deleted");
      currentImage=null;
      refreshList();
    } catch(SQLException ex) {
      label.setText(ex.toString());
    }
  }
  
  /**
   * This displays an image from the database.
   *
   * For images, this is the easiest method.
   */
  public void displayImage(String name)
  {
    try {
      //
      // Now as we are opening and reading a large object we must
      // turn on Transactions. This includes the ResultSet.getBytes()
      // method when it's used on a field of type oid!
      //
      db.setAutoCommit(false);
      
      ResultSet rs = stat.executeQuery("select imgoid from images where imgname='"+name+"'");
      if(rs!=null) {
	// Even though there should only be one image, we still have to
	// cycle through the ResultSet
	while(rs.next()) {
	  canvas.setImage(canvas.getToolkit().createImage(rs.getBytes(1)));
	  label.setText(currentImage = name);
	}
      }
      rs.close();
    } catch(SQLException ex) {
      label.setText(ex.toString());
    } finally {
	try {
	  db.setAutoCommit(true);
	} catch(SQLException ex2) {
	}
    }
  }
  
  public void itemStateChanged(ItemEvent e) {
    displayImage(list.getItem(((Integer)e.getItem()).intValue()));
  }
  
  /**
   * This is the command line instructions
   */
  public static void instructions()
  {
    System.err.println("java example.ImageViewer jdbc-url user password");
    System.err.println("\nExamples:\n");
    System.err.println("java -Djdbc.driver=org.postgresql.Driver example.ImageViewer jdbc:postgresql:test postgres password\n");
    
    System.err.println("This example tests the binary large object api of the driver.\nBasically, it will allow you to store and view images held in the database.");
    System.err.println("Note: If you are running this for the first time on a particular database,\nyou have to select \"Initialise\" in the \"PostgreSQL\" menu.\nThis will create a table used to store image names.");
  }
  
  /**
   * This is the application entry point
   */
  public static void main(String args[])
  {
    if(args.length!=3) {
      instructions();
      System.exit(1);
    }
    
    try {
      Frame frame = new Frame("PostgreSQL ImageViewer v7.0 rev 1");
      frame.setLayout(new BorderLayout());
      ImageViewer viewer = new ImageViewer(frame,args[0],args[1],args[2]);
      frame.pack();
      frame.setLocation(0,50);
      frame.setVisible(true);
    } catch(Exception ex) {
      System.err.println("Exception caught.\n"+ex);
      ex.printStackTrace();
    }
  }
}
