001    package org.hd.d.pg2k.clApp.uploader;
002    
003    /**
004     * Created by IntelliJ IDEA.
005     * User: DHD
006     * Date: 05-Oct-2006
007     * Time: 19:59:06
008     */
009    
010    import java.awt.datatransfer.DataFlavor;
011    import java.awt.datatransfer.Transferable;
012    import java.awt.datatransfer.UnsupportedFlavorException;
013    import java.io.ByteArrayInputStream;
014    import java.io.ByteArrayOutputStream;
015    import java.io.File;
016    import java.io.IOException;
017    import java.io.InputStream;
018    import java.io.OutputStream;
019    import java.net.URL;
020    
021    import javax.jnlp.FileContents;
022    import javax.jnlp.JNLPRandomAccessFile;
023    import javax.swing.JCheckBox;
024    import javax.swing.JComponent;
025    import javax.swing.TransferHandler;
026    import javax.swing.table.AbstractTableModel;
027    
028    import org.hd.d.pg2k.svrCore.uploader.UploadInfoBean;
029    
030    /**Class to help DnD (drag-and-drop) files into the JWS uploader.
031     * Package-visible for access by the GUI classes.
032     * <p>
033     * TODO: Extend this to accept URLs, eg dragged from a browser.
034     */
035    class FileTransferHandler extends TransferHandler
036        {
037        private static final long serialVersionUID = 1151591254166507360L;
038    
039        /**The business-logic component; never null. */
040        private final UploaderLogic logic;
041    
042        /**The model for the table so we can force a redraw; never null. */
043        final AbstractTableModel uploadTableModel;
044    
045        /**The "auto" file-guess mode check box; never null. */
046        final JCheckBox autoSuffixCheckBox;
047    
048        /**The exhibit description bean; never null. */
049        final UploadInfoBean uib;
050    
051        /**Create a new instance ready to receive files and drop them into the selected-files database. */
052        FileTransferHandler(final JCheckBox autoSuffixCheckBox,
053                            final AbstractTableModel uploadTableModel,
054                            final UploaderLogic logic,
055                            final UploadInfoBean uib)
056            {
057            if((autoSuffixCheckBox == null) ||
058               (uploadTableModel == null) ||
059               (logic == null) ||
060               (uib == null))
061                { throw new IllegalArgumentException(); }
062            this.autoSuffixCheckBox = autoSuffixCheckBox;
063            this.uploadTableModel = uploadTableModel;
064            this.logic = logic;
065            this.uib = uib;
066            }
067    
068        /**Attempt to import file lists, or http and ftp URLs.
069         */
070        @Override
071        @SuppressWarnings("unchecked")
072        public boolean importData(final JComponent c, final Transferable t)
073            {
074            // If we can't handle this data then return false.
075            if(!canImport(c, t.getTransferDataFlavors()))
076                { return(false); }
077    
078            // FIXME: Load the files in another thread so as not to block the UI.
079            try
080                {
081                if(hasFileFlavor(t.getTransferDataFlavors()))
082                    {
083                    final java.util.List<File> files =
084                        (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
085    
086                    // Wrap the files up as FileContents values...
087                    final FileContents filesFC[];
088                    // If we appear to be running in JNLP,
089                    // then do this through the ExtendedService,
090                    if(logic.exs != null)
091                        {
092                        final File f[] = new File[files.size()];
093                        files.toArray(f);
094                        filesFC = logic.exs.openFiles(f);
095                        }
096                    // else do it directly...
097                    else
098                        {
099                        filesFC = new FileContents[files.size()];
100                        for(int i = files.size(); --i >= 0; )
101                            { filesFC[i] = new NormalFileContent(files.get(i)); }
102                        }
103    
104                    // Add to the database...
105                    logic.addSelectedFiles(autoSuffixCheckBox.isSelected(),
106                                           filesFC,
107                                           uib);
108    
109                    // Get the table re-drawn with the new data.
110                    uploadTableModel.fireTableDataChanged();
111    
112                    return(true); // We got the data!
113                    }
114                else if(hasStringFlavor(t.getTransferDataFlavors()))
115                    {
116    //                System.out.println("importData: got string data flavor");
117                    final String s = (String) t.getTransferData(DataFlavor.stringFlavor);
118                    // Assume that the URL is first component;
119                    // other text may follow after whitespace.
120                    final URL u = new URL(s.split("[ \\r\\n\\t]")[0]);
121                    final String protocol = u.getProtocol();
122                    if("http".equals(protocol) || "ftp".equals(protocol))
123                        {
124                        System.out.println("importData: got URL: " + u);
125                        logic.logger.log("Selected URL: "+u);
126    
127                        // Maximum site of object that we can support upload of
128                        // if we have to buffer it up first.
129                        // Function of how much memory we have and other factors.
130                        final int maxLengthToBuffer = (int) Math.min(16*1024*1024, Runtime.getRuntime().totalMemory()/4);
131    
132                        // Create single-"file" list from the URL passed to us.
133                        final FileContents fileFC[] = new FileContents[]{
134                            new FileContents(){
135                                /**If we have read and buffered the content, this is non-null. */
136                                private byte content[];
137    
138                                public boolean canRead() throws IOException
139                                    { return(true); }
140    
141                                public boolean canWrite() throws IOException
142                                    { return(false); }
143    
144                                /**Get stream for the remote URL each time. */
145                                public InputStream getInputStream() throws IOException
146                                    {
147                                    // If we haven't buffered the data, return a stream.
148                                    if(content == null) { return(u.openStream()); }
149                                    // Return the buffered data.
150                                    return(new ByteArrayInputStream(content));
151                                    }
152    
153                                /**Get length of remote object if possible. */
154                                public long getLength() throws IOException
155                                    {
156                                    final int declLength = u.openConnection().getContentLength();
157                                    if(declLength > -1) { return(declLength); }
158                                    // If we cannot get the Content-Length
159                                    // then we may actually have to fetch the data!
160                                    System.out.println("Trying to download item to find its length: "+u);
161                                    logic.logger.log("Could not get Content-Length, so will try to download item; max length: ~"+maxLengthToBuffer);
162    
163                                    // Buffer to collect the input.
164                                    final byte buf[] = new byte[1<<13];
165                                    final ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
166                                    // Get the input.
167                                    final InputStream is = u.openStream();
168                                    while(baos.size() < maxLengthToBuffer)
169                                        {
170                                        final int n = is.read(buf);
171                                        if(n < 1)
172                                            {
173                                            content = baos.toByteArray();
174                                            return(content.length);
175                                            }
176                                        baos.write(buf, 0, n);
177                                        }
178                                    // Could not compute the input length (too large).
179                                    logic.logger.log("Could not compute item length (too large)...");
180                                    return(-1);
181                                    }
182    
183                                public long getMaxLength() throws IOException
184                                    { throw new UnsupportedOperationException(); }
185    
186                                /**Return full URL as name. */
187                                public String getName() throws IOException
188                                    { return(s); }
189    
190                                public OutputStream getOutputStream(final boolean arg0) throws IOException
191                                    { throw new UnsupportedOperationException("no output possible"); }
192    
193                                public JNLPRandomAccessFile getRandomAccessFile(final String arg0) throws IOException
194                                    { throw new UnsupportedOperationException(); }
195    
196                                public long setMaxLength(final long arg0) throws IOException
197                                    { throw new UnsupportedOperationException(); }
198                                }
199                            };
200    
201                        // Add name/contents of URL to the database...
202                        logic.addSelectedFiles(autoSuffixCheckBox.isSelected(),
203                                               fileFC,
204                                               uib);
205    
206                        // Get the table re-drawn with the new data.
207                        uploadTableModel.fireTableDataChanged();
208    
209                        System.out.println("importData: done. ");
210                        return(true); // Succeeded.
211                        }
212                    return(false); // Cannot import the data...
213                    }
214                }
215            catch(final UnsupportedFlavorException ufe)
216                {
217                System.err.println("importData: unsupported data flavor");
218                }
219            catch(final IOException ieo)
220                {
221                System.err.println("importData: I/O exception");
222                }
223            return(false); // Cannot import the data...
224            }
225    
226        /**Returns true iff we can import at least one of the specified data flavors.
227         * This will be false unless:
228         * <ul>
229         * <li>The data is presented as a file list or a String (potential URL).
230         * <li>Our uib is in a state to make a valid, unique exhibit name.
231         * </ul>
232         */
233        @Override
234        public boolean canImport(final JComponent c, final DataFlavor[] flavors)
235            {
236            // If the uib is not yet capable of generating us a good exhibit name
237            // then return false.
238            if(!uib.enoughValidUniqueInfo())
239                { return(false); }
240    
241            if(!hasFileFlavor(flavors) && !hasStringFlavor(flavors))
242                { return(false); }
243    
244            return(true); // Seems OK!
245            }
246    
247        private boolean hasFileFlavor(final DataFlavor[] flavors)
248            {
249            for(int i = flavors.length; --i >= 0; )
250                {
251                if(DataFlavor.javaFileListFlavor.equals(flavors[i]))
252                    { return(true); }
253                }
254            return(false);
255            }
256    
257        /**To accept URLs as text strings? */
258        private boolean hasStringFlavor(final DataFlavor[] flavors)
259            {
260            for(int i = flavors.length; --i >= 0; )
261                {
262                if(flavors[i].isFlavorTextType())
263    //            if(DataFlavor.stringFlavor.equals(flavors[i]))
264                    { return(true); }
265                }
266            return(false);
267            }
268        }
269