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-07-23 16:19:48 -0700 (Wed, 23 Jul 2014) $'
8
 * '$Revision: 8810 $'
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.Identifier;
39
import org.dataone.service.types.v2.Log;
40
import org.dataone.service.types.v2.LogEntry;
41
import org.dataone.service.types.v1.Event;
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), false);
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
        	if (event != null) {
143
        		
144
	        	String fieldName = event + "_count_i";
145
	        	int eventCount = 0;
146
	        	
147
	        	String docid = IdentifierManager.getInstance().getLocalId(pid.getValue());
148
	        	Log eventLog = this.getD1Report(null, null, new String[] {docid}, event, null, null, false, 0, 0);
149
	        	eventCount = eventLog.getTotal();
150
	
151
		        List<Object> values = new ArrayList<Object>();
152
				values.add(eventCount);
153
		        Map<String, List<Object>> fields = new HashMap<String, List<Object>>();
154
		        fields.put(fieldName, values);
155
		        
156
		        return fields;
157
        	}
158
	        
159
        } catch (Exception e) {
160
        	logMetacat.error("Could not update event index information on pid: " + pid.getValue() + " for event: " + event, e);
161
        }
162
        // default if we can't find the event information
163
    	return null;
164

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

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

    
485
        // order by
486
        String orderByClause = " order by entryid ";
487

    
488
        // select the count
489
        String countQuery = countClause + queryWhereClause.toString();
490
        
491
		// select the fields
492
        String pagedQuery = DatabaseService.getInstance().getDBAdapter().getPagedQuery(fieldsClause + queryWhereClause.toString() + orderByClause, start, count);
493

    
494
        DBConnection dbConn = null;
495
        int serialNumber = -1;
496
        try {
497
            // Get a database connection from the pool
498
            dbConn = DBConnectionPool.getDBConnection("EventLog.getD1Report");
499
            serialNumber = dbConn.getCheckOutSerialNumber();
500

    
501
            // Execute the query statement
502
            PreparedStatement fieldsStmt = dbConn.prepareStatement(pagedQuery);
503
            PreparedStatement countStmt = dbConn.prepareStatement(countQuery);
504

    
505
            //set the param values
506
            int parameterIndex = 1;
507
            for (String val: paramValues) {
508
            	countStmt.setString(parameterIndex, val);
509
            	fieldsStmt.setString(parameterIndex, val);
510
            	parameterIndex++;
511
            }
512
            if (startDate != null) {
513
            	countStmt.setTimestamp(parameterIndex, startDate); 
514
                fieldsStmt.setTimestamp(parameterIndex, startDate); 
515
            	parameterIndex++;
516
            }
517
            if (endDate != null) {
518
            	countStmt.setTimestamp(parameterIndex, endDate);
519
            	fieldsStmt.setTimestamp(parameterIndex, endDate);
520
            	parameterIndex++;
521
            }
522

    
523
            // for the return Log list
524
            List<LogEntry> logs = new Vector<LogEntry>();
525

    
526
            // get the fields form the query
527
            if (count != 0) {
528
	            fieldsStmt.execute();
529
	            ResultSet rs = fieldsStmt.getResultSet();
530
	            //process the result and return it            
531
	            while (rs.next()) {
532
	            	LogEntry logEntry = new LogEntry();
533
	            	logEntry.setEntryId(rs.getString(1));
534
	            	
535
	            	Identifier identifier = new Identifier();
536
	            	identifier.setValue(rs.getString(2));
537
					logEntry.setIdentifier(identifier);
538
	
539
	            	logEntry.setIpAddress(anonymous ? "N/A" : rs.getString(3));
540
	            	String userAgent = "N/A";
541
	            	if (rs.getString(4) != null) {
542
	            		userAgent = rs.getString(4);
543
	            	}
544
	            	logEntry.setUserAgent(userAgent);
545
	            	
546
	            	Subject subject = new Subject();
547
	            	subject.setValue(anonymous ? "N/A" : rs.getString(5));
548
					logEntry.setSubject(subject);
549
					
550
					String logEventString = rs.getString(6);
551
					logEntry.setEvent(logEventString);
552
					logEntry.setDateLogged(rs.getTimestamp(7));
553
					
554
					logEntry.setNodeIdentifier(memberNode);
555
					logs.add(logEntry);
556
	            }
557
	            fieldsStmt.close();
558
            }
559
            
560
            // set what we have
561
            log.setLogEntryList(logs);
562
		    log.setStart(start);
563
		    log.setCount(logs.size());
564
            			
565
			// get total for out query
566
		    int total = 0;
567
            countStmt.execute();
568
            ResultSet countRs = countStmt.getResultSet();
569
            if (countRs.next()) {
570
            	total = countRs.getInt(1);
571
            }
572
            countStmt.close();
573
		    log.setTotal(total);
574

    
575
        } catch (SQLException e) {
576
        	logMetacat.error("Error while getting log events: " + e.getMessage(), e);
577
        } finally {
578
            // Return database connection to the pool
579
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
580
        }
581
        return log;
582
    }
583
    
584
    /**
585
     * Format each returned log record as an XML structure.
586
     * 
587
     * @param entryId the identifier of the log entry
588
     * @param ipAddress the internet protocol address for the event
589
     * @param the agent making the request
590
	 * @param principal the principal for the event (a username, etc)
591
	 * @param docid the identifier of the document to which the event applies
592
	 * @param event the string code for the event
593
     * @param dateLogged the date on which the event occurred
594
     * @return String containing the formatted XML
595
     */
596
    private String generateXmlRecord(String entryId, String ipAddress, String userAgent,
597
            String principal, String docid, String event, Timestamp dateLogged)
598
    {
599
        StringBuffer rec = new StringBuffer();
600
        rec.append("<logEntry>");
601
        rec.append(generateXmlElement("entryid", entryId));
602
        rec.append(generateXmlElement("ipAddress", ipAddress));
603
        rec.append(generateXmlElement("userAgent", userAgent));
604
        rec.append(generateXmlElement("principal", principal));
605
        rec.append(generateXmlElement("docid", docid));
606
        rec.append(generateXmlElement("event", event));
607
        rec.append(generateXmlElement("dateLogged", DateTimeMarshaller.serializeDateToUTC(dateLogged)));
608
        rec.append("</logEntry>\n");
609

    
610
        return rec.toString();
611
    }
612
    
613
    /**
614
     * Return an XML formatted element for a given name/value pair.
615
     * 
616
     * @param name the name of the xml element
617
     * @param value the content of the xml element
618
     * @return the formatted XML element as a String
619
     */
620
    private String generateXmlElement(String name, String value)
621
    {
622
        return "<" + name + ">" + value + "</" + name + ">";
623
    }
624
}
(34-34/63)