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();
		}
	}
}
