Project

General

Profile

1 2096 jones
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2004 Regents of the University of California and the
4
 *             National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author$'
7
 *     '$Date$'
8
 * '$Revision$'
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 2104 jones
import java.sql.PreparedStatement;
27 2111 jones
import java.sql.ResultSet;
28 2096 jones
import java.sql.SQLException;
29 2104 jones
import java.sql.Timestamp;
30 2096 jones
import java.util.Date;
31
32 2663 sgarg
import org.apache.log4j.Logger;
33
34 5015 daigle
import edu.ucsb.nceas.metacat.database.DBConnection;
35
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
36
37 2096 jones
/**
38
 * EventLog is used to intialize and store a log of events that occur in an
39
 * application. The events are registered with the logger as they occur, but
40
 * EventLog writes them to permenant storage when it is most convenient or
41
 * efficient. EventLog is a Singleton as there should always be only one object
42
 * for these logging events.
43
 *
44
 * TODO: Logging to the database needn't be synchronous with the event.
45
 * Instead, a separate thread can be launched that periodically sleeps and only
46
 * wakes periodically to see if metacat is idle.  The log event can be cached
47
 * and inserted later when the thread wakes and finds metacat idle.
48
 *
49
 * TODO: Write a function that archives a part of the log table to an
50
 * external text file so that the log table doesn't get to big.  This
51
 * function should be able to be called manually or on a schedule.
52
 *
53
 * TODO: Write an access function that returns an XML report for a
54
 * specific subset of events.  Users should be able to query on
55
 * principal, docid/rev, date, event, and possibly other fields.
56
 *
57
 * @author jones
58
 */
59
public class EventLog
60
{
61
    /**
62
     * The single instance of the event log that is always returned.
63
     */
64
    private static EventLog self = null;
65 2663 sgarg
    private Logger logMetacat = Logger.getLogger(EventLog.class);
66 2096 jones
67
    /**
68
     * A private constructor that initializes the class when getInstance() is
69
     * called.
70
     */
71
    private EventLog()
72
    {
73
    }
74
75
    /**
76
     * Return the single instance of the event log after initializing it if it
77
     * wasn't previously initialized.
78
     *
79
     * @return the single EventLog instance
80
     */
81
    public static EventLog getInstance()
82
    {
83
        if (self == null) {
84
            self = new EventLog();
85
        }
86
        return self;
87
    }
88
89
    /**
90
     * Log an event of interest to the application. The information logged can
91
     * include basic identification information about the principal or computer
92
     * that initiated the event.
93
     *
94 2099 jones
     * @param ipAddress the internet protocol address for the event
95 6542 leinfelder
     * @param userAgent the agent making the request
96 2099 jones
	 * @param principal the principal for the event (a username, etc)
97
	 * @param docid the identifier of the document to which the event applies
98
	 * @param event the string code for the event
99 2096 jones
     */
100 6542 leinfelder
    public void log(String ipAddress, String userAgent, String principal, String docid, String event) {
101
        EventLogData logData = new EventLogData(ipAddress, principal, docid, event);
102 2096 jones
        insertLogEntry(logData);
103
    }
104 2099 jones
105 2096 jones
    /**
106
     * Insert a single log event record to the database.
107
     *
108
     * @param logData the data to be logged when an event occurs
109
     */
110
    private void insertLogEntry(EventLogData logData)
111
    {
112
        String insertString = "insert into access_log"
113 6542 leinfelder
                + "(ip_address, user_agent, principal, docid, "
114 2096 jones
                + "event, date_logged) "
115
                + "values ("
116
                + "'" + logData.getIpAddress() + "', "
117 6542 leinfelder
                + "'" + logData.getUserAgent() + "', "
118 2096 jones
                + "'" + logData.getPrincipal() + "', "
119
                + "'" + logData.getDocid() + "', "
120
                + "'" + logData.getEvent() + "', "
121 2104 jones
                + " ? " + ")";
122 2096 jones
123
        DBConnection dbConn = null;
124
        int serialNumber = -1;
125
        try {
126
            // Get a database connection from the pool
127 2111 jones
            dbConn = DBConnectionPool.getDBConnection("EventLog.insertLogEntry");
128 2096 jones
            serialNumber = dbConn.getCheckOutSerialNumber();
129
130
            // Execute the insert statement
131 2104 jones
            PreparedStatement stmt = dbConn.prepareStatement(insertString);
132
            stmt.setTimestamp(1, new Timestamp(new Date().getTime()));
133
            stmt.executeUpdate();
134 2096 jones
            stmt.close();
135
        } catch (SQLException e) {
136 2663 sgarg
        	logMetacat.error("Error while logging event to database: "
137
                    + e.getMessage());
138 2096 jones
        } finally {
139
            // Return database connection to the pool
140
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
141
        }
142
    }
143 2110 jones
144
    /**
145
     * Get a report of the log events that match a set of filters.  The
146 2111 jones
     * filter parameters can be null; log records are subset based on
147 2110 jones
     * non-null filter parameters.
148
     *
149
     * @param ipAddress the internet protocol address for the event
150
	 * @param principal the principal for the event (a username, etc)
151
	 * @param docid the identifier of the document to which the event applies
152
	 * @param event the string code for the event
153 2111 jones
	 * @param startDate beginning of date range for query
154
	 * @param endDate end of date range for query
155
	 * @return an XML-formatted report of the access log entries
156 2110 jones
     */
157 2111 jones
    public String getReport(String[] ipAddress, String[] principal, String[] docid,
158 5693 leinfelder
            String[] event, Timestamp startDate, Timestamp endDate, boolean anonymous)
159 2110 jones
    {
160 2111 jones
        StringBuffer resultDoc = new StringBuffer();
161
        StringBuffer query = new StringBuffer();
162 6542 leinfelder
        query.append("select entryid, ip_address, user_agent, principal, docid, "
163 2111 jones
            + "event, date_logged from access_log");
164
//                        + ""
165
//                        + "event, date_logged) " + "values (" + "'"
166
//                        + logData.getIpAddress() + "', " + "'"
167
//                        + logData.getPrincipal() + "', " + "'"
168
//                        + logData.getDocid() + "', " + "'" + logData.getEvent()
169
//                        + "', " + " ? " + ")";
170
        if (ipAddress != null || principal != null || docid != null
171
                        || event != null || startDate != null || endDate != null) {
172
            query.append(" where ");
173
        }
174
        boolean clauseAdded = false;
175
        int startIndex = 0;
176
        int endIndex = 0;
177
178
        if (ipAddress != null) {
179 2113 jones
            query.append(generateSqlClause(clauseAdded, "ip_address", ipAddress));
180 2111 jones
            clauseAdded = true;
181
        }
182
        if (principal != null) {
183 2113 jones
            query.append(generateSqlClause(clauseAdded, "principal", principal));
184 2111 jones
            clauseAdded = true;
185
        }
186
        if (docid != null) {
187 2113 jones
            query.append(generateSqlClause(clauseAdded, "docid", docid));
188 2111 jones
            clauseAdded = true;
189
        }
190
        if (event != null) {
191 2113 jones
            query.append(generateSqlClause(clauseAdded, "event", event));
192 2111 jones
            clauseAdded = true;
193
        }
194
        if (startDate != null) {
195
            if (clauseAdded) {
196
                query.append(" and ");
197
            }
198
            query.append("date_logged > ?");
199
            clauseAdded = true;
200
            startIndex++;
201
        }
202
        if (endDate != null) {
203
            if (clauseAdded) {
204
                query.append(" and ");
205
            }
206
            query.append("date_logged < ?");
207
            clauseAdded = true;
208
            endIndex = startIndex + 1;
209
        }
210
        DBConnection dbConn = null;
211
        int serialNumber = -1;
212
        try {
213
            // Get a database connection from the pool
214
            dbConn = DBConnectionPool.getDBConnection("EventLog.getReport");
215
            serialNumber = dbConn.getCheckOutSerialNumber();
216
217 2113 jones
            // Execute the query statement
218 2111 jones
            PreparedStatement stmt = dbConn.prepareStatement(query.toString());
219
            if (startIndex > 0) {
220
                stmt.setTimestamp(startIndex, startDate);
221
            }
222
            if (endIndex > 0) {
223
                stmt.setTimestamp(endIndex, endDate);
224
            }
225
            stmt.execute();
226
            ResultSet rs = stmt.getResultSet();
227 2113 jones
            //process the result and return it as an XML document
228 2111 jones
            resultDoc.append("<?xml version=\"1.0\"?>\n");
229
            resultDoc.append("<log>\n");
230
            while (rs.next()) {
231 5693 leinfelder
                resultDoc.append(
232
                		generateXmlRecord(
233
                				rs.getString(1), //id
234
                				anonymous ? "" : rs.getString(2), //ip
235 6542 leinfelder
                				rs.getString(3), //userAgent
236
                				anonymous ? "" : rs.getString(4), //principal
237 5693 leinfelder
                                rs.getString(5),
238 6542 leinfelder
                                rs.getString(6),
239
                                rs.getTimestamp(7)));
240 2111 jones
            }
241
            resultDoc.append("</log>");
242
            stmt.close();
243
        } catch (SQLException e) {
244 2663 sgarg
        	logMetacat.info("Error while logging event to database: "
245
                            + e.getMessage());
246 2111 jones
        } finally {
247
            // Return database connection to the pool
248
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
249
        }
250
        return resultDoc.toString();
251 2110 jones
    }
252 2111 jones
253
    /**
254
     * Utility method to help build a SQL query from an array of values.  For each
255
     * value in the array an 'OR' clause is constructed.
256
     *
257
     * @param addOperator a flag indicating whether to add an 'AND' operator
258
     *                    to the clause
259
     * @param column the name of the column to filter against
260
     * @param values the values to match in the SQL query
261
     * @return a String representation of the SQL query clause
262
     */
263 2113 jones
    private String generateSqlClause(boolean addOperator, String column,
264
            String[] values)
265 2111 jones
    {
266
        StringBuffer clause = new StringBuffer();
267
        if (addOperator) {
268
            clause.append(" and ");
269
        }
270
        clause.append("(");
271
        for (int i = 0; i < values.length; i++) {
272
            if (i > 0) {
273
                clause.append(" or ");
274
            }
275
            clause.append(column);
276
            clause.append(" like '");
277
            clause.append(values[i]);
278
            clause.append("'");
279
        }
280
        clause.append(")");
281
        return clause.toString();
282
    }
283
284
    /**
285
     * Format each returned log record as an XML structure.
286
     *
287
     * @param entryId the identifier of the log entry
288
     * @param ipAddress the internet protocol address for the event
289 6542 leinfelder
     * @param the agent making the request
290 2111 jones
	 * @param principal the principal for the event (a username, etc)
291
	 * @param docid the identifier of the document to which the event applies
292
	 * @param event the string code for the event
293
     * @param dateLogged the date on which the event occurred
294
     * @return String containing the formatted XML
295
     */
296 6542 leinfelder
    private String generateXmlRecord(String entryId, String ipAddress, String userAgent,
297 2113 jones
            String principal, String docid, String event, Timestamp dateLogged)
298 2111 jones
    {
299
        StringBuffer rec = new StringBuffer();
300
        rec.append("<logEntry>");
301 2113 jones
        rec.append(generateXmlElement("entryid", entryId));
302
        rec.append(generateXmlElement("ipAddress", ipAddress));
303 6542 leinfelder
        rec.append(generateXmlElement("userAgent", userAgent));
304 2113 jones
        rec.append(generateXmlElement("principal", principal));
305
        rec.append(generateXmlElement("docid", docid));
306
        rec.append(generateXmlElement("event", event));
307
        rec.append(generateXmlElement("dateLogged", dateLogged.toString()));
308 2111 jones
        rec.append("</logEntry>\n");
309
310
        return rec.toString();
311
    }
312
313
    /**
314
     * Return an XML formatted element for a given name/value pair.
315
     *
316
     * @param name the name of the xml element
317
     * @param value the content of the xml element
318
     * @return the formatted XML element as a String
319
     */
320 2113 jones
    private String generateXmlElement(String name, String value)
321 2111 jones
    {
322
        return "<" + name + ">" + value + "</" + name + ">";
323
    }
324 2096 jones
}