Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2004 Regents of the University of California and the
4
 *             National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: leinfelder $'
7
 *     '$Date: 2014-01-07 13:56:58 -0800 (Tue, 07 Jan 2014) $'
8
 * '$Revision: 8464 $'
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23
 */
24
package edu.ucsb.nceas.metacat;
25

    
26
import java.sql.PreparedStatement;
27
import java.sql.ResultSet;
28
import java.sql.SQLException;
29
import java.sql.Timestamp;
30
import java.util.ArrayList;
31
import java.util.Date;
32
import java.util.HashMap;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.Vector;
36

    
37
import org.apache.log4j.Logger;
38
import org.dataone.service.types.v1.Event;
39
import org.dataone.service.types.v1.Identifier;
40
import org.dataone.service.types.v1.Log;
41
import org.dataone.service.types.v1.LogEntry;
42
import org.dataone.service.types.v1.NodeReference;
43
import org.dataone.service.types.v1.Subject;
44
import org.dataone.service.util.DateTimeMarshaller;
45

    
46
import edu.ucsb.nceas.metacat.database.DBConnection;
47
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
48
import edu.ucsb.nceas.metacat.database.DatabaseService;
49
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
50
import edu.ucsb.nceas.metacat.properties.PropertyService;
51
import edu.ucsb.nceas.metacat.util.DocumentUtil;
52
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
53

    
54
/**
55
 * EventLog is used to intialize and store a log of events that occur in an
56
 * application. The events are registered with the logger as they occur, but
57
 * EventLog writes them to permenant storage when it is most convenient or
58
 * efficient. EventLog is a Singleton as there should always be only one object
59
 * for these logging events.
60
 * 
61
 * TODO: Logging to the database needn't be synchronous with the event.  
62
 * Instead, a separate thread can be launched that periodically sleeps and only
63
 * wakes periodically to see if metacat is idle.  The log event can be cached
64
 * and inserted later when the thread wakes and finds metacat idle.
65
 * 
66
 * TODO: Write a function that archives a part of the log table to an 
67
 * external text file so that the log table doesn't get to big.  This 
68
 * function should be able to be called manually or on a schedule. 
69
 * 
70
 * TODO: Write an access function that returns an XML report for a
71
 * specific subset of events.  Users should be able to query on
72
 * principal, docid/rev, date, event, and possibly other fields.
73
 * 
74
 * @author jones
75
 */
76
public class EventLog
77
{
78
    /**
79
     * The single instance of the event log that is always returned.
80
     */
81
    private static EventLog self = null;
82
    private Logger logMetacat = Logger.getLogger(EventLog.class);
83

    
84
    /**
85
     * A private constructor that initializes the class when getInstance() is
86
     * called.
87
     */
88
    private EventLog()
89
    {
90
    }
91

    
92
    /**
93
     * Return the single instance of the event log after initializing it if it
94
     * wasn't previously initialized.
95
     * 
96
     * @return the single EventLog instance
97
     */
98
    public static EventLog getInstance()
99
    {
100
        if (self == null) {
101
            self = new EventLog();
102
        }
103
        return self;
104
    }
105

    
106
    /**
107
     * Log an event of interest to the application. The information logged can
108
     * include basic identification information about the principal or computer
109
     * that initiated the event.
110
     * 
111
     * @param ipAddress the internet protocol address for the event
112
     * @param userAgent the agent making the request
113
	 * @param principal the principal for the event (a username, etc)
114
	 * @param docid the identifier of the document to which the event applies
115
	 * @param event the string code for the event
116
     */
117
    public void log(String ipAddress, String userAgent, String principal, String docid, String event) {
118
        EventLogData logData = new EventLogData(ipAddress, principal, docid, event);
119
        insertLogEntry(logData);
120
        
121
        // update the event information in the index
122
        try {
123
	        String localId = DocumentUtil.getSmartDocId(docid);
124
			int rev = DocumentUtil.getRevisionFromAccessionNumber(docid);
125
			
126
	        String guid = IdentifierManager.getInstance().getGUID(localId, rev);
127
	        Identifier pid = new Identifier();
128
	        pid.setValue(guid);
129
	        
130
	        // submit for indexing
131
	        MetacatSolrIndex.getInstance().submit(pid, null, this.getIndexFields(pid, event));
132
	        
133
        } catch (Exception e) {
134
        	logMetacat.error("Could not update event index information", e);
135
        }
136
    }
137
    
138
    public Map<String, List<Object>> getIndexFields(Identifier pid, String event) {
139
    	// update the search index for the event
140
        try {
141
        	
142
        	Event d1Event = Event.convert(event);
143
        	String fieldName = d1Event.xmlValue() + "_count_i";
144
        	int eventCount = 0;
145
        	
146
        	String docid = IdentifierManager.getInstance().getLocalId(pid.getValue());
147
        	Log eventLog = this.getD1Report(null, null, new String[] {docid}, d1Event, null, null, false, 0, 0);
148
        	eventCount = eventLog.getTotal();
149

    
150
	        List<Object> values = new ArrayList<Object>();
151
			values.add(eventCount);
152
	        Map<String, List<Object>> fields = new HashMap<String, List<Object>>();
153
	        fields.put(fieldName, values);
154
	        
155
	        return fields;
156
	        
157
        } catch (Exception e) {
158
        	logMetacat.error("Could not update event index information", e);
159
        	return null;
160
        }
161
    }
162
    
163
    /**
164
     * Insert a single log event record to the database.
165
     * 
166
     * @param logData the data to be logged when an event occurs
167
     */
168
    private void insertLogEntry(EventLogData logData)
169
    {
170
        String insertString = "insert into access_log"
171
                + "(ip_address, user_agent, principal, docid, "
172
                + "event, date_logged) "
173
                + "values ( ?, ?, ?, ?, ?, ? )";
174
 
175
        DBConnection dbConn = null;
176
        int serialNumber = -1;
177
        try {
178
            // Get a database connection from the pool
179
            dbConn = DBConnectionPool.getDBConnection("EventLog.insertLogEntry");
180
            serialNumber = dbConn.getCheckOutSerialNumber();
181
            
182
            // Execute the insert statement
183
            PreparedStatement stmt = dbConn.prepareStatement(insertString);
184
            
185
            stmt.setString(1, logData.getIpAddress());
186
            stmt.setString(2, logData.getUserAgent());
187
            stmt.setString(3, logData.getPrincipal());
188
            stmt.setString(4, logData.getDocid());
189
            stmt.setString(5, logData.getEvent());
190
            stmt.setTimestamp(6, new Timestamp(new Date().getTime()));
191
            stmt.executeUpdate();
192
            stmt.close();
193
        } catch (SQLException e) {
194
        	logMetacat.error("Error while logging event to database: " 
195
                    + e.getMessage());
196
        } finally {
197
            // Return database connection to the pool
198
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
199
        }
200
    }
201
    
202
    /**
203
     * Get a report of the log events that match a set of filters.  The
204
     * filter parameters can be null; log records are subset based on
205
     * non-null filter parameters.
206
     * 
207
     * @param ipAddress the internet protocol address for the event
208
	 * @param principal the principal for the event (a username, etc)
209
	 * @param docid the identifier of the document to which the event applies
210
	 * @param event the string code for the event
211
	 * @param startDate beginning of date range for query
212
	 * @param endDate end of date range for query
213
	 * @return an XML-formatted report of the access log entries
214
     */
215
    public String getReport(String[] ipAddress, String[] principal, String[] docid,
216
            String[] event, Timestamp startDate, Timestamp endDate, boolean anonymous)
217
    {
218
        StringBuffer resultDoc = new StringBuffer();
219
        StringBuffer query = new StringBuffer();
220
        query.append("select entryid, ip_address, user_agent, principal, docid, "
221
            + "event, date_logged from access_log");
222
//                        + ""
223
//                        + "event, date_logged) " + "values (" + "'"
224
//                        + logData.getIpAddress() + "', " + "'"
225
//                        + logData.getPrincipal() + "', " + "'"
226
//                        + logData.getDocid() + "', " + "'" + logData.getEvent()
227
//                        + "', " + " ? " + ")";
228
        if (ipAddress != null || principal != null || docid != null
229
                        || event != null || startDate != null || endDate != null) {
230
            query.append(" where ");
231
        }
232
        boolean clauseAdded = false;
233
        int startIndex = 0;
234
        int endIndex = 0;
235
        
236
        List<String> paramValues = new ArrayList<String>();
237
        if (ipAddress != null) {
238
        	query.append("ip_address in (");
239
        	for (int i = 0; i < ipAddress.length; i++) {
240
        		if (i > 0) {
241
            		query.append(", ");
242
        		}
243
        		query.append("?");
244
        		paramValues.add(ipAddress[i]);
245
        	}
246
        	query.append(") ");
247
            clauseAdded = true;
248
        }
249
        if (principal != null) {
250
        	if (clauseAdded) {
251
                query.append(" and ");
252
            }
253
        	query.append("principal in (");
254
        	for (int i = 0; i < principal.length; i++) {
255
        		if (i > 0) {
256
            		query.append(", ");
257
        		}
258
        		query.append("?");
259
        		paramValues.add(principal[i]);
260
        	}
261
        	query.append(") ");
262
            clauseAdded = true;
263
        }
264
        if (docid != null) {
265
        	if (clauseAdded) {
266
                query.append(" and ");
267
            }
268
        	query.append("docid in (");
269
        	for (int i = 0; i < docid.length; i++) {
270
        		if (i > 0) {
271
            		query.append(", ");
272
        		}
273
        		query.append("?");
274
        		String fullDocid = docid[i];
275
        		// allow docid without revision - look up latest version
276
        		try {
277
        			fullDocid = DocumentUtil.appendRev(fullDocid);
278
        		} catch (Exception e) {
279
					// just warn about this
280
        			logMetacat.warn("Could not check docid for revision: " + fullDocid, e);
281
				}
282
        		paramValues.add(fullDocid);
283
        	}
284
        	query.append(") ");
285
            clauseAdded = true;
286
        }
287
        if (event != null) {
288
        	if (clauseAdded) {
289
                query.append(" and ");
290
            }
291
        	query.append("event in (");
292
        	for (int i = 0; i < event.length; i++) {
293
        		if (i > 0) {
294
            		query.append(", ");
295
        		}
296
        		query.append("?");
297
        		paramValues.add(event[i]);
298
        	}
299
        	query.append(") ");
300
            clauseAdded = true;
301
        }
302
        if (startDate != null) {
303
            if (clauseAdded) {
304
                query.append(" and ");
305
            }
306
            query.append("date_logged >= ?");
307
            clauseAdded = true;
308
            startIndex++;
309
        }
310
        if (endDate != null) {
311
            if (clauseAdded) {
312
                query.append(" and ");
313
            }
314
            query.append("date_logged < ?");
315
            clauseAdded = true;
316
            endIndex = startIndex + 1;
317
        }
318
        DBConnection dbConn = null;
319
        int serialNumber = -1;
320
        try {
321
            // Get a database connection from the pool
322
            dbConn = DBConnectionPool.getDBConnection("EventLog.getReport");
323
            serialNumber = dbConn.getCheckOutSerialNumber();
324

    
325
            // Execute the query statement
326
            PreparedStatement stmt = dbConn.prepareStatement(query.toString());
327
            //set the param values
328
            int parameterIndex = 1;
329
            for (String val: paramValues) {
330
            	stmt.setString(parameterIndex++, val);
331
            }
332
            if (startDate != null) {
333
                stmt.setTimestamp(parameterIndex++, startDate); 
334
            }
335
            if (endDate != null) {
336
            	stmt.setTimestamp(parameterIndex++, endDate);
337
            }
338
            stmt.execute();
339
            ResultSet rs = stmt.getResultSet();
340
            //process the result and return it as an XML document
341
            resultDoc.append("<?xml version=\"1.0\"?>\n");
342
            resultDoc.append("<log>\n");
343
            while (rs.next()) {
344
                resultDoc.append(
345
                		generateXmlRecord(
346
                				rs.getString(1), //id
347
                				anonymous ? "" : rs.getString(2), //ip
348
                				rs.getString(3), //userAgent	
349
                				anonymous ? "" : rs.getString(4), //principal
350
                                rs.getString(5), 
351
                                rs.getString(6), 
352
                                rs.getTimestamp(7)));
353
            }
354
            resultDoc.append("</log>");
355
            stmt.close();
356
        } catch (SQLException e) {
357
        	logMetacat.info("Error while logging event to database: "
358
                            + e.getMessage());
359
        } finally {
360
            // Return database connection to the pool
361
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
362
        }
363
        return resultDoc.toString();
364
    }
365
    
366
    
367
    
368
    public Log getD1Report(String[] ipAddress, String[] principal, String[] docid,
369
            Event event, Timestamp startDate, Timestamp endDate, boolean anonymous, Integer start, Integer count)
370
    {
371
        
372
        Log log = new Log();
373
    	
374
    	NodeReference memberNode = new NodeReference();
375
        String nodeId = "localhost";
376
        try {
377
            nodeId = PropertyService.getProperty("dataone.nodeId");
378
        } catch (PropertyNotFoundException e1) {
379
            // TODO Auto-generated catch block
380
            e1.printStackTrace();
381
        }
382
        memberNode.setValue(nodeId);
383
        
384
        String countClause = "select count(*) ";
385
        String fieldsClause = "select " +
386
        		"entryid, " +
387
        		"id.guid as identifier, " +
388
        		"ip_address, " +
389
        		"user_agent, " +
390
        		"principal, " +
391
        		"case " +
392
        		"	when event = 'insert' then 'create' " +
393
        		"	else event " +
394
        		"end as event, " +
395
        		"date_logged ";
396
        
397
        StringBuffer queryWhereClause = new StringBuffer();
398
        queryWhereClause.append(		 
399
        		"from access_log al, identifier id " +
400
        		"where al.docid = id.docid||'.'||id.rev "
401
        );
402
        
403
        boolean clauseAdded = true;
404
        
405
        List<String> paramValues = new ArrayList<String>();
406
        if (ipAddress != null) {
407
        	if (clauseAdded) {
408
                queryWhereClause.append(" and ");
409
            }
410
        	queryWhereClause.append("ip_address in (");
411
        	for (int i = 0; i < ipAddress.length; i++) {
412
        		if (i > 0) {
413
            		queryWhereClause.append(", ");
414
        		}
415
        		queryWhereClause.append("?");
416
        		paramValues.add(ipAddress[i]);
417
        	}
418
        	queryWhereClause.append(") ");
419
            clauseAdded = true;
420
        }
421
        if (principal != null) {
422
        	if (clauseAdded) {
423
                queryWhereClause.append(" and ");
424
            }
425
        	queryWhereClause.append("principal in (");
426
        	for (int i = 0; i < principal.length; i++) {
427
        		if (i > 0) {
428
            		queryWhereClause.append(", ");
429
        		}
430
        		queryWhereClause.append("?");
431
        		paramValues.add(principal[i]);
432
        	}
433
        	queryWhereClause.append(") ");
434
            clauseAdded = true;
435
        }
436
        if (docid != null) {
437
        	if (clauseAdded) {
438
                queryWhereClause.append(" and ");
439
            }
440
        	queryWhereClause.append("al.docid in (");
441
        	for (int i = 0; i < docid.length; i++) {
442
        		if (i > 0) {
443
            		queryWhereClause.append(", ");
444
        		}
445
        		queryWhereClause.append("?");
446
        		paramValues.add(docid[i]);
447
        	}
448
        	queryWhereClause.append(") ");
449
            clauseAdded = true;
450
        }
451
        if (event != null) {
452
        	if (clauseAdded) {
453
                queryWhereClause.append(" and ");
454
            }
455
        	queryWhereClause.append("event in (");
456
    		queryWhereClause.append("?");
457
    		String eventString = event.xmlValue();
458
    		if (event.equals(Event.CREATE)) {
459
    			eventString = "insert";
460
    		}
461
    		paramValues.add(eventString);
462
        	queryWhereClause.append(") ");
463
            clauseAdded = true;
464
        }
465
        else {
466
	        if (clauseAdded) {
467
	            queryWhereClause.append(" and ");
468
	        }
469
	    	queryWhereClause.append("event in (");
470
	    	for (int i = 0; i < Event.values().length; i++) {
471
	    		if (i > 0) {
472
	        		queryWhereClause.append(", ");
473
	    		}
474
	    		queryWhereClause.append("?");
475
	    		Event e = Event.values()[i];
476
	    		String eventString = e.xmlValue();
477
	    		if (e.equals(Event.CREATE)) {
478
	    			eventString = "insert";
479
	    		}
480
	    		paramValues.add(eventString);
481
	    	}
482
	    	queryWhereClause.append(") ");
483
	        clauseAdded = true;
484
        }
485
        if (startDate != null) {
486
            if (clauseAdded) {
487
                queryWhereClause.append(" and ");
488
            }
489
            queryWhereClause.append("date_logged >= ?");
490
            clauseAdded = true;
491
        }
492
        if (endDate != null) {
493
            if (clauseAdded) {
494
                queryWhereClause.append(" and ");
495
            }
496
            queryWhereClause.append("date_logged < ?");
497
            clauseAdded = true;
498
        }
499

    
500
        // order by
501
        String orderByClause = " order by entryid ";
502

    
503
        // select the count
504
        String countQuery = countClause + queryWhereClause.toString();
505
        
506
		// select the fields
507
        String pagedQuery = DatabaseService.getInstance().getDBAdapter().getPagedQuery(fieldsClause + queryWhereClause.toString() + orderByClause, start, count);
508

    
509
        DBConnection dbConn = null;
510
        int serialNumber = -1;
511
        try {
512
            // Get a database connection from the pool
513
            dbConn = DBConnectionPool.getDBConnection("EventLog.getD1Report");
514
            serialNumber = dbConn.getCheckOutSerialNumber();
515

    
516
            // Execute the query statement
517
            PreparedStatement fieldsStmt = dbConn.prepareStatement(pagedQuery);
518
            PreparedStatement countStmt = dbConn.prepareStatement(countQuery);
519

    
520
            //set the param values
521
            int parameterIndex = 1;
522
            for (String val: paramValues) {
523
            	countStmt.setString(parameterIndex, val);
524
            	fieldsStmt.setString(parameterIndex, val);
525
            	parameterIndex++;
526
            }
527
            if (startDate != null) {
528
            	countStmt.setTimestamp(parameterIndex, startDate); 
529
                fieldsStmt.setTimestamp(parameterIndex, startDate); 
530
            	parameterIndex++;
531
            }
532
            if (endDate != null) {
533
            	countStmt.setTimestamp(parameterIndex, endDate);
534
            	fieldsStmt.setTimestamp(parameterIndex, endDate);
535
            	parameterIndex++;
536
            }
537

    
538
            // for the return Log list
539
            List<LogEntry> logs = new Vector<LogEntry>();
540

    
541
            // get the fields form the query
542
            if (count != 0) {
543
	            fieldsStmt.execute();
544
	            ResultSet rs = fieldsStmt.getResultSet();
545
	            //process the result and return it            
546
	            while (rs.next()) {
547
	            	LogEntry logEntry = new LogEntry();
548
	            	logEntry.setEntryId(rs.getString(1));
549
	            	
550
	            	Identifier identifier = new Identifier();
551
	            	identifier.setValue(rs.getString(2));
552
					logEntry.setIdentifier(identifier);
553
	
554
	            	logEntry.setIpAddress(anonymous ? "N/A" : rs.getString(3));
555
	            	String userAgent = "N/A";
556
	            	if (rs.getString(4) != null) {
557
	            		userAgent = rs.getString(4);
558
	            	}
559
	            	logEntry.setUserAgent(userAgent);
560
	            	
561
	            	Subject subject = new Subject();
562
	            	subject.setValue(anonymous ? "N/A" : rs.getString(5));
563
					logEntry.setSubject(subject);
564
					
565
					String logEventString = rs.getString(6);
566
					Event logEvent = Event.convert(logEventString );
567
					if (logEvent == null) {
568
						logMetacat.info("Skipping uknown DataONE Event type: " + logEventString);
569
						continue;
570
					}
571
					logEntry.setEvent(logEvent);
572
					logEntry.setDateLogged(rs.getTimestamp(7));
573
					
574
					logEntry.setNodeIdentifier(memberNode);
575
					logs.add(logEntry);
576
	            }
577
	            fieldsStmt.close();
578
            }
579
            
580
            // set what we have
581
            log.setLogEntryList(logs);
582
		    log.setStart(start);
583
		    log.setCount(logs.size());
584
            			
585
			// get total for out query
586
		    int total = 0;
587
            countStmt.execute();
588
            ResultSet countRs = countStmt.getResultSet();
589
            if (countRs.next()) {
590
            	total = countRs.getInt(1);
591
            }
592
            countStmt.close();
593
		    log.setTotal(total);
594

    
595
        } catch (SQLException e) {
596
        	logMetacat.error("Error while getting log events: " + e.getMessage(), e);
597
        } finally {
598
            // Return database connection to the pool
599
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
600
        }
601
        return log;
602
    }
603
    
604
    /**
605
     * Format each returned log record as an XML structure.
606
     * 
607
     * @param entryId the identifier of the log entry
608
     * @param ipAddress the internet protocol address for the event
609
     * @param the agent making the request
610
	 * @param principal the principal for the event (a username, etc)
611
	 * @param docid the identifier of the document to which the event applies
612
	 * @param event the string code for the event
613
     * @param dateLogged the date on which the event occurred
614
     * @return String containing the formatted XML
615
     */
616
    private String generateXmlRecord(String entryId, String ipAddress, String userAgent,
617
            String principal, String docid, String event, Timestamp dateLogged)
618
    {
619
        StringBuffer rec = new StringBuffer();
620
        rec.append("<logEntry>");
621
        rec.append(generateXmlElement("entryid", entryId));
622
        rec.append(generateXmlElement("ipAddress", ipAddress));
623
        rec.append(generateXmlElement("userAgent", userAgent));
624
        rec.append(generateXmlElement("principal", principal));
625
        rec.append(generateXmlElement("docid", docid));
626
        rec.append(generateXmlElement("event", event));
627
        rec.append(generateXmlElement("dateLogged", DateTimeMarshaller.serializeDateToUTC(dateLogged)));
628
        rec.append("</logEntry>\n");
629

    
630
        return rec.toString();
631
    }
632
    
633
    /**
634
     * Return an XML formatted element for a given name/value pair.
635
     * 
636
     * @param name the name of the xml element
637
     * @param value the content of the xml element
638
     * @return the formatted XML element as a String
639
     */
640
    private String generateXmlElement(String name, String value)
641
    {
642
        return "<" + name + ">" + value + "</" + name + ">";
643
    }
644
}
(34-34/63)