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