Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that implements org.xml.sax.EntityResolver interface
4
 *             for resolving external entities
5
 *  Copyright: 2000 Regents of the University of California and the
6
 *             National Center for Ecological Analysis and Synthesis
7
 *    Authors: Jivka Bojilova, Matt Jones
8
 *
9
 *   '$Author: daigle $'
10
 *     '$Date: 2009-08-04 14:32:58 -0700 (Tue, 04 Aug 2009) $'
11
 * '$Revision: 5015 $'
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 2 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program; if not, write to the Free Software
25
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
 */
27

    
28
package edu.ucsb.nceas.metacat;
29

    
30
import org.apache.log4j.Logger;
31
import org.xml.sax.*;
32
import org.xml.sax.helpers.DefaultHandler;
33

    
34
import edu.ucsb.nceas.metacat.database.DBConnection;
35
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
36
import edu.ucsb.nceas.metacat.util.SystemUtil;
37
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
38

    
39
import java.sql.*;
40
import java.io.File;
41
import java.io.Reader;
42
import java.io.BufferedReader;
43
import java.io.BufferedInputStream;
44
import java.io.FileWriter;
45
import java.io.BufferedWriter;
46
import java.io.InputStream;
47
import java.io.IOException;
48
import java.net.URL;
49
import java.net.MalformedURLException;
50

    
51
/**
52
 * A database aware Class implementing EntityResolver interface for the SAX
53
 * parser to call when processing the XML stream and intercepting any
54
 * external entities (including the external DTD subset and external
55
 * parameter entities, if any) before including them.
56
 */
57
public class DBEntityResolver implements EntityResolver
58
{
59
  private DBConnection connection = null;
60
  private DefaultHandler handler = null;
61
  private String docname = null;
62
  private String doctype = null;
63
  private String systemid = null;
64
  private Reader dtdtext = null;
65
  private static Logger logMetacat = Logger.getLogger(DBEntityResolver.class);
66
  
67
  /**
68
   * Construct an instance of the DBEntityResolver class
69
   *
70
   * @param conn the JDBC connection to which information is written
71
   */
72
  public DBEntityResolver(DBConnection conn)
73
  {
74
    this.connection= conn;
75
  }
76
  /**
77
   * Construct an instance of the DBEntityResolver class
78
   *
79
   * @param conn the JDBC connection to which information is written
80
   * @param handler the SAX handler to determine parsing context
81
   * @param dtd Reader of new dtd to be uploaded on server's file system
82
   */
83
  public DBEntityResolver(DBConnection conn, DefaultHandler handler, Reader dtd)
84
  {
85
    this.connection = conn;
86
    this.handler = handler;
87
    this.dtdtext = dtd;
88
  }
89

    
90
  /**
91
   * The Parser call this method before opening any external entity
92
   * except the top-level document entity (including the external DTD subset,
93
   * external entities referenced within the DTD, and external entities
94
   * referenced within the document element)
95
   */
96
  public InputSource resolveEntity (String publicId, String systemId)
97
                     throws SAXException
98
  {
99
    logMetacat.debug("in DBEntityResolver.resolveEntity");
100
    String dbSystemID;
101
    String doctype = null;
102

    
103
    // Won't have a handler under all cases
104
    if ( handler != null ) {
105
      if ( handler instanceof DBSAXHandler ) {
106
        DBSAXHandler dhandler = null;
107
        dhandler = (DBSAXHandler)handler;
108
        if ( dhandler.processingDTD() ) {
109
         
110
          // public ID is doctype
111
          if (publicId != null) {
112
            doctype = publicId;
113
            logMetacat.info("in get type from publicId and doctype is: "
114
                                     +doctype);
115
          // assume public ID (doctype) is docname
116
          } else if (systemId != null) {
117
            doctype = dhandler.getDocname();
118
          }
119
        }
120
      } else if ( handler instanceof AccessControlList ) {
121
        AccessControlList ahandler = null;
122
        ahandler = (AccessControlList)handler;
123
        //if ( ahandler.processingDTD() ) {
124
          // public ID is doctype
125
          if (publicId != null) {
126
            doctype = publicId;
127
          // assume public ID (doctype) is docname
128
          } else if (systemId != null) {
129
            doctype = ahandler.getDocname();
130
          }
131
        //}
132
      }
133
    }
134

    
135
    // get System ID for doctype
136
    if (doctype != null) {
137
      // look at db XML Catalog for System ID
138
      logMetacat.info("get systemId from doctype: "+doctype);
139
      dbSystemID = getDTDSystemID(doctype);
140
      logMetacat.info("The Systemid is: "+dbSystemID);
141
      // check that it is accessible on our system before getting too far
142
      try {
143
    	  InputStream in = checkURLConnection(dbSystemID);
144
	  } catch (Exception e) {
145
		  // after an upgrade, the dtd will not exist on disk, but it is in xml catalog.  The db system id may be pointing 
146
		  // back at this system  Try and download it from the original system id and see if we still have a problem
147
		  // checking the URL connection.
148
		  logMetacat.warn("dtd for doc type " + doctype + " existed in xml catalog, but not on disk.  Uploading from: " + systemId);
149
		  InputStream istream = checkURLConnection(systemId);
150
		  uploadDTDFromURL(istream, systemId);
151
		  try {
152
			  checkURLConnection(dbSystemID);
153
		  } catch (Exception e2) {
154
			  logMetacat.error("still could not find dtd for doc type " + doctype + " at " 
155
					  + dbSystemID + " : " + e2.getMessage());
156
			  dbSystemID = null;
157
		  }
158
	  }
159
      boolean doctypeIsInDB = true;
160
      // no System ID found in db XML Catalog
161
      if (dbSystemID == null) {
162
        doctypeIsInDB = false;
163
        // use the provided System ID
164
        if (systemId != null) {
165
          dbSystemID = systemId;
166
        }
167
        logMetacat.info("If above Systemid is null and then get "
168
                                 +" system id from file" + dbSystemID);
169
      }
170
      // there are dtd text provided; try to upload on Metacat
171
      if ( dtdtext != null ) {
172
        dbSystemID = uploadDTD(dbSystemID);
173
      }
174

    
175
      // open URLConnection to check first
176
      InputStream istream = checkURLConnection(dbSystemID);
177

    
178
      // need to register System ID in db XML Catalog if not yet
179
      if ( !doctypeIsInDB ) {
180
        // new DTD from outside URL location; try to upload on Metacat
181
        if ( dtdtext == null ) {
182
          dbSystemID = uploadDTDFromURL(istream, dbSystemID);
183
        }
184
        registerDTD(doctype, dbSystemID);
185
      }
186
      // return a byte-input stream for use
187
      InputSource is = new InputSource(dbSystemID);
188

    
189
      // close and open URLConnection again
190
      try {
191
        istream.close();
192
      } catch (IOException e) {
193
        throw new SAXException
194
        ("DBEntityResolver.resolveEntity - I/O issue when resolving entity: " + e.getMessage());
195
      }
196
      istream = checkURLConnection(dbSystemID);
197
      is.setByteStream(istream);
198
      return is;
199
    } else {
200
      // use provided systemId for the other cases
201
      logMetacat.info("doctype is null and using system id from file");
202
      InputStream istream = checkURLConnection(systemId);
203
      return null;
204

    
205
    }
206

    
207
  }
208

    
209
  /**
210
   * Look at db XML Catalog to get System ID (if any) for @doctype.
211
   * Return null if there are no System ID found for @doctype
212
   */
213
  public static String getDTDSystemID( String doctype )
214
                 throws SAXException
215
  {
216
    String systemid = null;
217
    Statement stmt = null;
218
    DBConnection conn = null;
219
    int serialNumber = -1;
220
    try {
221
      //check out DBConnection
222
      conn=DBConnectionPool.getDBConnection("DBEntityResolver.getDTDSystemID");
223
      serialNumber=conn.getCheckOutSerialNumber();
224

    
225
      stmt = conn.createStatement();
226
      stmt.execute("SELECT system_id FROM xml_catalog " +
227
                   "WHERE entry_type = 'DTD' AND public_id = '" +
228
                   doctype + "'");
229
      ResultSet rs = stmt.getResultSet();
230
      boolean tableHasRows = rs.next();
231
      if (tableHasRows) {
232
        systemid = rs.getString(1);
233
        // system id may not have server url on front.  Add it if not.
234
        if (!systemid.startsWith("http://")) {
235
        	systemid = SystemUtil.getContextURL() + systemid;
236
        }
237
      }
238
      stmt.close();
239
    } catch (SQLException e) {
240
      throw new SAXException
241
      ("DBEntityResolver.getDTDSystemID - SQL error when getting DTD system ID: " + e.getMessage());
242
    } catch (PropertyNotFoundException pnfe) {
243
        throw new SAXException
244
        ("DBEntityResolver.getDTDSystemID - Property error when getting DTD system ID:  " + pnfe.getMessage());
245
      }
246
    finally
247
    {
248
      try
249
      {
250
        stmt.close();
251
      }//try
252
      catch (SQLException sqlE)
253
      {
254
        logMetacat.error("SQL error in DBEntityReolver.getDTDSystemId: "
255
                                  +sqlE.getMessage());
256
      }//catch
257
      finally
258
      {
259
        DBConnectionPool.returnDBConnection(conn, serialNumber);
260
      }//finally
261
    }//finally
262

    
263
    // return the selected System ID
264
    return systemid;
265
  }
266

    
267
  /**
268
   * Register new DTD identified by @systemId in Metacat XML Catalog
269
   * . make a reference with @systemId for @doctype in Metacat DB
270
   */
271
  private void registerDTD ( String doctype, String systemId )
272
                 throws SAXException
273
  {
274
	  String existingSystemId = getDTDSystemID(doctype);
275
	  if (existingSystemId != null && existingSystemId.equals(systemId)) {
276
		  logMetacat.warn("doctype/systemId already registered in DB: " + doctype);
277
		  return;
278
	  }
279
    //DBConnection conn = null;
280
    //int serialNumber = -1;
281
    PreparedStatement pstmt = null;
282
    // make a reference in db catalog table with @systemId for @doctype
283
    try {
284
      //check out DBConnection
285
      //conn=DBConnectionPool.getDBConnection("DBEntityResolver.registerDTD");
286
      //serialNumber=conn.getCheckOutSerialNumber();
287

    
288

    
289
      pstmt = connection.prepareStatement(
290
             "INSERT INTO xml_catalog " +
291
             "(entry_type, public_id, system_id) " +
292
             "VALUES ('DTD', ?, ?)");
293
      // Increase usage count
294
      connection.increaseUsageCount(1);
295
      // Bind the values to the query
296
      pstmt.setString(1, doctype);
297
      pstmt.setString(2, systemId);
298
      // Do the insertion
299
      pstmt.execute();
300
      int updateCnt = pstmt.getUpdateCount();
301
      logMetacat.debug("DBEntityReolver.registerDTD: DTDs registered: " + updateCnt);
302
      pstmt.close();
303
    } catch (SQLException e) {
304
      throw new SAXException
305
      ("DBEntityResolver.registerDTD - SQL issue when registering DTD: " + e.getMessage());
306
    }
307
    finally
308
    {
309
      try
310
      {
311
        pstmt.close();
312
      }//try
313
      catch (SQLException sqlE)
314
      {
315
        logMetacat.error("SQL error in DBEntityReolver.registerDTD: "
316
                                    +sqlE.getMessage());
317
      }//catch
318
      //DBConnectionPool.returnDBConnection(conn, serialNumber);
319
    }//finally
320

    
321
  }
322

    
323
  /**
324
	 * Upload new DTD text identified by
325
	 * 
326
	 * @systemId to Metacat file system
327
	 */
328
	private String uploadDTD(String systemId) throws SAXException {
329
		String dtdPath = null;
330
		String dtdURL = null;
331
		try {
332
			dtdPath = SystemUtil.getContextDir() + "/dtd/";
333
			dtdURL = SystemUtil.getContextURL() + "/dtd/";
334
		} catch (PropertyNotFoundException pnfe) {
335
			throw new SAXException("DBEntityResolver.uploadDTD: " + pnfe.getMessage());
336
		}
337

    
338
		// get filename from systemId
339
		String filename = systemId;
340
		int slash = Math.max(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
341
		if (slash > -1) {
342
			filename = filename.substring(slash + 1);
343
		}
344

    
345
		// writing dtd text on Metacat file system as filename
346
		try {
347
			// create a buffering character-input stream
348
			// that uses a default-sized input buffer
349
			BufferedReader in = new BufferedReader(dtdtext);
350

    
351
			// open file writer to write the input into it
352
			// String dtdPath = "/opt/tomcat/webapps/bojilova/dtd/";
353
			File f = new File(dtdPath, filename);
354
			synchronized (f) {
355
				try {
356
					if (f.exists()) {
357
						throw new IOException("File already exist: "
358
								+ f.getCanonicalFile());
359
						// if ( f.exists() && !f.canWrite() ) {
360
						// throw new IOException("Not writable: " +
361
						// f.getCanonicalFile());
362
					}
363
				} catch (SecurityException se) {
364
					// if a security manager exists,
365
					// its checkRead method is called for f.exist()
366
					// or checkWrite method is called for f.canWrite()
367
					throw se;
368
				}
369
				// create a buffered character-output stream
370
				// that uses a default-sized output buffer
371
				FileWriter fw = new FileWriter(f);
372
				BufferedWriter out = new BufferedWriter(fw);
373

    
374
				// read the input and write into the file writer
375
				String inputLine;
376
				while ((inputLine = in.readLine()) != null) {
377
					out.write(inputLine, 0, inputLine.length());
378
					out.newLine(); // instead of out.write('\r\n');
379
				}
380

    
381
				// the input and the output streams must be closed
382
				in.close();
383
				out.flush();
384
				out.close();
385
				fw.close();
386
			} // end of synchronized
387
		} catch (MalformedURLException e) {
388
			throw new SAXException("DBEntityResolver.uploadDTD() - Malformed URL when uploading DTD: " + e.getMessage());
389
		} catch (IOException e) {
390
			throw new SAXException("DBEntityResolver.uploadDTD - I/O issue when uploading DTD: " + e.getMessage());
391
		} catch (SecurityException e) {
392
			throw new SAXException("DBEntityResolver.uploadDTD() - Security issue when uploading DTD: " + e.getMessage());
393
		}
394

    
395
		// String dtdURL = "http://dev.nceas.ucsb.edu/bojilova/dtd/";
396
		return dtdURL + filename;
397
	}
398

    
399

    
400
  /**
401
	 * Upload new DTD located at outside URL to Metacat file system
402
	 */
403
	private String uploadDTDFromURL(InputStream istream, String systemId)
404
			throws SAXException {
405
		String dtdPath = null;
406
		String dtdURL = null;
407
		try {
408
			dtdPath = SystemUtil.getContextDir() + "/dtd/";
409
			dtdURL = SystemUtil.getContextURL() + "/dtd/";
410
		} catch (PropertyNotFoundException pnfe) {
411
			throw new SAXException("DBEntityResolver.uploadDTDFromURL - Property issue when uploading DTD from URL: "
412
					+ pnfe.getMessage());
413
		}
414

    
415
		// get filename from systemId
416
		String filename = systemId;
417
		int slash = Math.max(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
418
		if (slash > -1) {
419
			filename = filename.substring(slash + 1);
420
		}
421

    
422
		// writing dtd text on Metacat file system as filename
423
		try {
424
			// create a buffering character-input stream
425
			// that uses a default-sized input buffer
426
			BufferedInputStream in = new BufferedInputStream(istream);
427

    
428
			// open file writer to write the input into it
429
			//String dtdPath = "/opt/tomcat/webapps/bojilova/dtd/";
430
			File f = new File(dtdPath, filename);
431
			synchronized (f) {
432
				try {
433
					if (f.exists()) {
434
						logMetacat.warn("File already exist, overwriting: "
435
								+ f.getCanonicalFile());
436
						//return dtdURL + filename;
437
						//throw new IOException("File already exist: "
438
						//		+ f.getCanonicalFile());
439
						//if ( f.exists() && !f.canWrite() ) {
440
						//  throw new IOException("Not writable: " + f.getCanonicalFile());
441
					}
442
				} catch (SecurityException se) {
443
					// if a security manager exists,
444
					// its checkRead method is called for f.exist()
445
					// or checkWrite method is called for f.canWrite()
446
					throw se;
447
				}
448
				// create a buffered character-output stream
449
				// that uses a default-sized output buffer
450
				FileWriter fw = new FileWriter(f);
451
				BufferedWriter out = new BufferedWriter(fw);
452

    
453
				// read the input and write into the file writer
454
				int inputByte;
455
				while ((inputByte = in.read()) != -1) {
456
					out.write(inputByte);
457
					//out.newLine(); //instead of out.write('\r\n');
458
				}
459

    
460
				// the input and the output streams must be closed
461
				in.close();
462
				out.flush();
463
				out.close();
464
				fw.close();
465
			} // end of synchronized
466
		} catch (MalformedURLException e) {
467
			throw new SAXException("DBEntityResolver.uploadDTDFromURL - Malformed URL when uploading DTD from URL: "
468
					+ e.getMessage());
469
		} catch (IOException e) {
470
			throw new SAXException("DBEntityResolver.uploadDTDFromURL - I/O issue when uploading DTD from URL:  "
471
					+ e.getMessage());
472
		} catch (SecurityException e) {
473
			throw new SAXException("DBEntityResolver.uploadDTDFromURL - Security issue when uploading DTD from URL:  "
474
					+ e.getMessage());
475
		}
476

    
477
		//String dtdURL = "http://dev.nceas.ucsb.edu/bojilova/dtd/";
478
		return dtdURL + filename;
479
	}
480

    
481
	/**
482
	 * Check URL Connection for @systemId, and return an InputStream
483
	 * that can be used to read from the systemId URL.  The parser ends
484
	 * up using this via the InputSource to read the DTD.
485
	 *
486
	 * @param systemId a URI (in practice URL) to be checked and opened
487
	 */
488
	public static InputStream checkURLConnection(String systemId) throws SAXException {
489
		try {
490
			return (new URL(systemId).openStream());
491

    
492
		} catch (MalformedURLException e) {
493
			throw new SAXException("DBEntityResolver.checkURLConnection - Malformed URL when checking URL Connection: "
494
					+ e.getMessage());
495
		} catch (IOException e) {
496
			throw new SAXException("DBEntityResolver.checkURLConnection - I/O issue when checking URL Connection: "
497
					+ e.getMessage());
498
		}
499
	}
500
}
(20-20/63)