001package com.hfg.util.io;
002
003import java.io.*;
004import java.util.Calendar;
005import java.util.Date;
006import java.util.GregorianCalendar;
007import java.net.URL;
008import java.net.HttpURLConnection;
009import java.util.logging.Level;
010import java.util.logging.Logger;
011
012import com.hfg.util.StringUtil;
013
014
015//------------------------------------------------------------------------------
016/**
017 * Implementation of RemoteFile for HTTP files
018 *
019 * @author J. Alex Taylor, hairyfatguy.com
020 */
021//------------------------------------------------------------------------------
022// com.hfg XML/HTML Coding Library
023//
024// This library is free software; you can redistribute it and/or
025// modify it under the terms of the GNU Lesser General Public
026// License as published by the Free Software Foundation; either
027// version 2.1 of the License, or (at your option) any later version.
028//
029// This library is distributed in the hope that it will be useful,
030// but WITHOUT ANY WARRANTY; without even the implied warranty of
031// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
032// Lesser General Public License for more details.
033//
034// You should have received a copy of the GNU Lesser General Public
035// License along with this library; if not, write to the Free Software
036// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
037//
038// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
039// jataylor@hairyfatguy.com
040//------------------------------------------------------------------------------
041
042
043public class HTTPRemoteFile extends AbstractRemoteFile
044{
045
046   //###########################################################################
047   // PRIVATE FIELDS
048   //###########################################################################
049
050   private String mURL;
051   private Long   mSize;
052   private Calendar mTimestamp;
053   private int    mNumRedirects;
054   private String mUserAgentString = sDefaultUserAgentString;
055   private String mRequestedPath;
056
057   private static String sDefaultUserAgentString;
058
059   private static final int MAX_REDIRECTS = 10;
060
061   // Some places don't like to talk to or don't recognize Java. Pretend we're IE
062   private static final String USER_AGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)";
063
064   private final static Logger LOGGER = Logger.getLogger(HTTPRemoteFile.class.getName());
065
066   //###########################################################################
067   // CONSTRUCTORS
068   //###########################################################################
069
070
071   //---------------------------------------------------------------------------
072   public HTTPRemoteFile(String inURL)
073   {
074      mURL = inURL;
075   }
076
077
078
079   //###########################################################################
080   // PUBLIC METHODS
081   //###########################################################################
082
083   //---------------------------------------------------------------------------
084   public static void setDefaultUserAgentString(String inValue)
085   {
086      sDefaultUserAgentString = inValue;
087   }
088
089   //---------------------------------------------------------------------------
090   public static String getDefaultUserAgentString()
091   {
092      return sDefaultUserAgentString;
093   }
094
095
096   //---------------------------------------------------------------------------
097   public HTTPRemoteFile setUserAgentString(String inValue)
098   {
099      mUserAgentString = inValue;
100      return this;
101   }
102
103   //---------------------------------------------------------------------------
104   public String geUserAgentString()
105   {
106      return mUserAgentString;
107   }
108
109   //---------------------------------------------------------------------------
110   public static Logger getLogger()
111   {
112      return LOGGER;
113   }
114
115   //---------------------------------------------------------------------------
116   @Override
117   public String getURL()
118   {
119      return mURL;
120   }
121
122   //---------------------------------------------------------------------------
123   @Override
124   public String getName()
125   {
126      String name;
127
128      int index = mURL.lastIndexOf('/');
129      if (index > 0)
130      {
131         name = mURL.substring(index + 1);
132      }
133      else
134      {
135         name = mURL;
136      }
137
138      // Don't include arguments in the name
139      index = name.indexOf('?');
140      if (index > 0)
141      {
142         name = name.substring(0, index);
143      }
144
145      return name;
146   }
147
148   //---------------------------------------------------------------------------
149   @Override
150   public String getPath()
151   {
152      String path;
153
154      // Remove http://
155      int index = mURL.indexOf("//");
156      if (index > 0)
157      {
158         path = mURL.substring(index + 2);
159      }
160      else
161      {
162         path = mURL;
163      }
164
165      // Remove the server or server:port
166      index = path.indexOf("/");
167      if (index > 0)
168      {
169         path = path.substring(index + 1);
170      }
171
172      // Don't include arguments in the path
173      index = path.indexOf('?');
174      if (index > 0)
175      {
176         path = path.substring(0, index);
177      }
178
179
180
181      return path;
182   }
183
184   //---------------------------------------------------------------------------
185   public HTTPRemoteFile setRequestedPath(String inValue)
186   {
187      mRequestedPath = inValue;
188      return this;
189   }
190
191   //---------------------------------------------------------------------------
192   @Override
193   public String getRequestedPath()
194   {
195      return mRequestedPath != null ? mRequestedPath : getPath();
196   }
197
198   //---------------------------------------------------------------------------
199   @Override
200   public long getSize()
201   {
202      if (null == mSize)
203      {
204         getFileInfo();
205      }
206
207      return mSize != null ? mSize.longValue() : -1;
208   }
209
210   //---------------------------------------------------------------------------
211   @Override
212   public Calendar getTimestamp()
213   {
214      if (null == mTimestamp)
215      {
216         getFileInfo();
217      }
218
219      return mTimestamp;
220   }
221
222   //---------------------------------------------------------------------------
223   @Override
224   public InputStream getInputStream()
225   throws IOException
226   {
227      HttpURLConnection conn = (HttpURLConnection) new URL(mURL).openConnection();
228      conn.setRequestProperty("User-Agent", USER_AGENT);
229      conn.connect();
230
231      return new BufferedInputStream(conn.getInputStream());
232   }
233
234
235   //---------------------------------------------------------------------------
236   @Override
237   public boolean writeToLocalDir(File inLocalDir)
238         throws IOException
239   {
240      boolean result = super.writeToLocalDir(inLocalDir);
241      if (null ==  mSize
242          || mSize < 0)
243      {
244         mSize = new File(inLocalDir, getName()).length();
245      }
246
247      return result;
248   }
249
250   //---------------------------------------------------------------------------
251   @Override
252   public boolean copyLocallyPreservingPath(File inLocalDir)
253   throws IOException
254   {
255      boolean result = super.copyLocallyPreservingPath(inLocalDir);
256      if (null ==  mSize
257          || mSize < 0)
258      {
259         mSize = new File(inLocalDir, getPath()).length();
260      }
261
262      return result;
263   }
264
265   //---------------------------------------------------------------------------
266   @Override
267   public long writeToStream(OutputStream stream)
268   throws IOException
269   {
270      long fileSize = super.writeToStream(stream);
271      if (null ==  mSize
272          || mSize < 0)
273      {
274         mSize = fileSize;
275      }
276
277      return fileSize;
278   }
279
280   //---------------------------------------------------------------------------
281   @Override
282   public boolean writeToLocalFile(File inLocalFile)
283         throws IOException
284   {
285      boolean result = super.writeToLocalFile(inLocalFile);
286      if (null ==  mSize
287          || mSize < 0)
288      {
289         mSize = inLocalFile.length();
290      }
291
292      return result;
293   }
294
295   //###########################################################################
296   // PRIVATE METHODS
297   //###########################################################################
298
299   //---------------------------------------------------------------------------
300   private void getFileInfo()
301   {
302      HttpURLConnection conn = null;
303      try
304      {
305         conn = (HttpURLConnection) new URL(mURL).openConnection();
306         conn.setRequestMethod("HEAD");
307         conn.setUseCaches(false);
308         conn.setInstanceFollowRedirects(false);
309         if (StringUtil.isSet(geUserAgentString()))
310         {
311            conn.setRequestProperty("User-Agent", geUserAgentString());
312         }
313         conn.connect();
314
315         if (getLogger().isLoggable(Level.FINE))
316         {
317            LOGGER.log(Level.FINE, "RESPONSE CODE: " + conn.getResponseCode());
318            LOGGER.log(Level.FINE, "RESPONSE MSG: " + conn.getResponseMessage());
319            LOGGER.log(Level.FINE, "CONTENT LENGTH: " + conn.getContentLength());
320            LOGGER.log(Level.FINE, "LAST MODIFIED: " + conn.getLastModified());
321         }
322
323         final int responseCode = conn.getResponseCode();
324         if (   responseCode == HttpURLConnection.HTTP_MOVED_PERM
325             || responseCode == HttpURLConnection.HTTP_MOVED_TEMP
326             || responseCode == HttpURLConnection.HTTP_SEE_OTHER)
327         {
328            final String newLocation = conn.getHeaderField("Location");
329
330            final String msg = getURL() + (responseCode == HttpURLConnection.HTTP_MOVED_PERM ? " permanently" : "") + " moved to " + newLocation;
331            LOGGER.log(Level.INFO, msg);
332
333            mNumRedirects++;
334            if (mNumRedirects > MAX_REDIRECTS)
335            {
336               throw new IOException("More than " + MAX_REDIRECTS + " redirects!");
337            }
338
339            final URL newURL = new URL(new URL(getURL()), newLocation);
340
341            conn.disconnect();
342
343            mURL = newURL.toString();
344
345            getFileInfo(); // Recurse
346         }
347         else if (responseCode == HttpURLConnection.HTTP_OK)
348         {
349            mSize = new Long(conn.getContentLength());
350
351            long lastModifiedMilliseconds = conn.getLastModified();
352            if (lastModifiedMilliseconds > 0)
353            {
354               mTimestamp = new GregorianCalendar();
355               mTimestamp.setTime(new Date(conn.getLastModified()));
356            }
357         }
358      }
359      catch (IOException e)
360      {
361         throw new RuntimeException(e.toString());
362      }
363      finally
364      {
365         if (conn != null) conn.disconnect();
366      }
367   }
368}