AuditorImpl.java 9.01 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.messenger.audit.spi;

import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.audit.AuditManager;
import org.jivesoftware.messenger.audit.Auditor;
Derek DeMoro's avatar
Derek DeMoro committed
19 20 21 22
import org.xmpp.packet.Packet;
import org.xmpp.packet.Message;
import org.xmpp.packet.Presence;
import org.xmpp.packet.IQ;
Matt Tucker's avatar
Matt Tucker committed
23
import org.dom4j.io.XMLWriter;
Matt Tucker's avatar
Matt Tucker committed
24 25
import org.dom4j.Element;
import org.dom4j.DocumentFactory;
Matt Tucker's avatar
Matt Tucker committed
26

27
import java.io.*;
Matt Tucker's avatar
Matt Tucker committed
28
import java.util.Date;
29 30 31 32
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
Matt Tucker's avatar
Matt Tucker committed
33 34 35 36 37 38

public class AuditorImpl implements Auditor {

    private AuditManager auditManager;
    private File currentAuditFile;
    private Writer writer;
Matt Tucker's avatar
Matt Tucker committed
39
    private XMLWriter xmlWriter;
Matt Tucker's avatar
Matt Tucker committed
40 41
    private int maxSize;
    private long maxCount;
42
    private int logTimeout;
Matt Tucker's avatar
Matt Tucker committed
43 44
    private boolean closed = false;

45 46 47 48 49 50 51 52
    /**
     * Queue that holds the audited packets that will be later saved to an XML file.
     */
    private Queue<AuditPacket> logQueue = new LinkedBlockingQueue<AuditPacket>();

    /**
     * Timer to save queued logs to the XML file.
     */
53
    private Timer timer = new Timer("Auditor");
54 55
    private SaveQueuedPacketsTask saveQueuedPacketsTask;

56
    public AuditorImpl(AuditManager manager) {
Matt Tucker's avatar
Matt Tucker committed
57 58 59
        auditManager = manager;
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
60
    public void audit(Packet packet, Session session) {
Matt Tucker's avatar
Matt Tucker committed
61 62
        if (auditManager.isEnabled()) {
            if (packet instanceof Message) {
63
                if (auditManager.isAuditMessage()) {
Gaston Dombiak's avatar
Gaston Dombiak committed
64
                    writePacket(packet, session);
Matt Tucker's avatar
Matt Tucker committed
65 66 67
                }
            }
            else if (packet instanceof Presence) {
68
                if (auditManager.isAuditPresence()) {
Gaston Dombiak's avatar
Gaston Dombiak committed
69
                    writePacket(packet, session);
Matt Tucker's avatar
Matt Tucker committed
70 71 72
                }
            }
            else if (packet instanceof IQ) {
73
                if (auditManager.isAuditIQ()) {
Gaston Dombiak's avatar
Gaston Dombiak committed
74
                    writePacket(packet, session);
Matt Tucker's avatar
Matt Tucker committed
75 76 77 78 79
                }
            }
        }
    }

80 81 82 83 84 85
    public void stop() {
        // Stop the scheduled task for saving queued packets to the XML file
        timer.cancel();
        // Save all remaining queued packets to the XML file
        saveQueuedPackets();
        close();
Matt Tucker's avatar
Matt Tucker committed
86 87
    }

88
    private void close() {
Matt Tucker's avatar
Matt Tucker committed
89
        if (xmlWriter != null) {
Matt Tucker's avatar
Matt Tucker committed
90
            try {
Matt Tucker's avatar
Matt Tucker committed
91 92
                xmlWriter.flush();
                writer.write("</jive>");
93
                xmlWriter.close();
Matt Tucker's avatar
Matt Tucker committed
94 95 96 97 98 99 100 101
                writer = null;
            }
            catch (Exception e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
102
    private void writePacket(Packet packet, Session session) {
Matt Tucker's avatar
Matt Tucker committed
103
        if (!closed) {
104
            // Add to the logging queue this new entry that will be saved later
Gaston Dombiak's avatar
Gaston Dombiak committed
105
            logQueue.add(new AuditPacket(packet.createCopy(), session));
106
        }
Matt Tucker's avatar
Matt Tucker committed
107 108
    }

Matt Tucker's avatar
Matt Tucker committed
109
    private void prepareAuditFile() throws IOException {
Matt Tucker's avatar
Matt Tucker committed
110 111 112 113 114 115
        if (currentAuditFile == null || currentAuditFile.length() > maxSize) {
            rotateFiles();
        }
    }

    protected void setMaxValues(int size, int count) {
Matt Tucker's avatar
Matt Tucker committed
116
        maxSize = size * 1024*1024;
Matt Tucker's avatar
Matt Tucker committed
117 118 119
        maxCount = count;
    }

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    public void setLogTimeout(int newTimeout) {
        // Cancel any existing task because the timeout has changed
        if (saveQueuedPacketsTask != null) {
            saveQueuedPacketsTask.cancel();
        }
        this.logTimeout = newTimeout;
        // Create a new task and schedule it with the new timeout
        saveQueuedPacketsTask = new SaveQueuedPacketsTask();
        timer.schedule(saveQueuedPacketsTask, logTimeout, logTimeout);

    }

    public int getQueuedPacketsNumber() {
        return logQueue.size();
    }

Matt Tucker's avatar
Matt Tucker committed
136
    private void rotateFiles() throws IOException {
Matt Tucker's avatar
Matt Tucker committed
137 138 139 140
        close();
        int i;
        // Find the next available log file name
        for (i = 0; maxCount < 1 || i < maxCount; i++) {
141 142
            currentAuditFile = new File(JiveGlobals.getMessengerHome() + File.separator + "logs",
                    "jive.audit-" + i + ".log");
Matt Tucker's avatar
Matt Tucker committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
            if (!currentAuditFile.exists()) {
                break;
            }
        }
        // Two edge cases, i == 0 (no log files exist) and i == MAX_FILE_COUNT
        // If i == 0 then the loop above has already set currentAuditFile to
        // the correct file name, so we only need to setup a file name if i != 0
        if (i != 0) {
            if (i == maxCount) {
                // We need to delete the last in the series to make room for the next file
                // the currentAuditFile should be pointing at the last legitimate
                // file name in the series (i < MAX_FILE_COUNT) so we just delete it
                // so the previous file can be rotated to it
                currentAuditFile.delete();
            }
            // Rotate the files
            for (i--; i >= 0; i--) {
                String previousName = "jive.audit-" + i + ".log";
161 162
                File previousFile = new File(JiveGlobals.getMessengerHome() + File.separator + "logs",
                        previousName);
Matt Tucker's avatar
Matt Tucker committed
163
                previousFile.renameTo(currentAuditFile);
164 165
                currentAuditFile = new File(JiveGlobals.getMessengerHome() + File.separator + "logs",
                        previousName);
Matt Tucker's avatar
Matt Tucker committed
166 167 168
            }
        }

169
        writer = new OutputStreamWriter(new FileOutputStream(currentAuditFile), "UTF-8");
Matt Tucker's avatar
Matt Tucker committed
170 171
        writer.write("<jive xmlns=\"http://www.jivesoftware.org\">");
        xmlWriter = new XMLWriter(writer);
Matt Tucker's avatar
Matt Tucker committed
172
    }
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

    /**
     * Saves the queued entries to an XML file.
     */
    private class SaveQueuedPacketsTask extends TimerTask {
        public void run() {
            try {
                saveQueuedPackets();
            }
            catch (Throwable e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
        }
    }

    private void saveQueuedPackets() {
        int batchSize = logQueue.size();
        for (int index = 0; index < batchSize; index++) {
Matt Tucker's avatar
Matt Tucker committed
191 192
            AuditPacket auditPacket = logQueue.poll();
            if (auditPacket != null) {
193 194
                try {
                    prepareAuditFile();
Matt Tucker's avatar
Matt Tucker committed
195
                    xmlWriter.write(auditPacket.getElement());
196 197 198 199
                }
                catch (IOException e) {
                    Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
                    // Add again the entry to the queue to save it later
Matt Tucker's avatar
Matt Tucker committed
200
                    logQueue.add(auditPacket);
201 202 203
                }
            }
        }
Matt Tucker's avatar
Matt Tucker committed
204
        try {
Gaston Dombiak's avatar
Gaston Dombiak committed
205 206 207
            if (xmlWriter != null) {
                xmlWriter.flush();
            }
Matt Tucker's avatar
Matt Tucker committed
208 209 210 211
        }
        catch (IOException ioe) {

        }
212 213 214
    }

    /**
Matt Tucker's avatar
Matt Tucker committed
215 216
     * Wrapper on a Packet with information about the packet's status at the moment
     * when the message was queued.<p>
217
     *
Matt Tucker's avatar
Matt Tucker committed
218 219
     * The idea is to wrap every packet that is needed to be audited and then add the
     * wrapper to a queue that will be later processed (i.e. saved to the XML file).
220
     */
Matt Tucker's avatar
Matt Tucker committed
221
    private static class AuditPacket {
Matt Tucker's avatar
Matt Tucker committed
222

Matt Tucker's avatar
Matt Tucker committed
223
        private static DocumentFactory docFactory = DocumentFactory.getInstance();
224

Matt Tucker's avatar
Matt Tucker committed
225 226
        private Element element;

Gaston Dombiak's avatar
Gaston Dombiak committed
227
        public AuditPacket(Packet packet, Session session) {
Matt Tucker's avatar
Matt Tucker committed
228
            element = docFactory.createElement("packet", "http://www.jivesoftware.org");
Gaston Dombiak's avatar
Gaston Dombiak committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
            if (session.getStreamID() != null) {
                element.addAttribute("streamID", session.getStreamID().toString());
            }
            switch (session.getStatus()) {
                case Session.STATUS_AUTHENTICATED:
                    element.addAttribute("status", "auth");
                    break;
                case Session.STATUS_CLOSED:
                    element.addAttribute("status", "closed");
                    break;
                case Session.STATUS_CONNECTED:
                    element.addAttribute("status", "connected");
                    // This is a workaround. Since we don't want to have an incorrect FROM attribute
                    // value we need to clean up the FROM attribute. The FROM attribute will contain
                    // an incorrect value since we are setting a fake JID until the user actually
                    // authenticates with the server.
                    packet.setFrom((String) null);
                    break;
                case Session.STATUS_STREAMING:
                    element.addAttribute("status", "stream");
                    break;
                default:
                    element.addAttribute("status", "unknown");
                    break;
253
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
254
            element.addAttribute("timestamp", new Date().toString());
Matt Tucker's avatar
Matt Tucker committed
255
            element.add(packet.getElement());
256 257
        }

Matt Tucker's avatar
Matt Tucker committed
258 259 260 261 262 263 264
        /**
         * Returns the Element associated with this audit packet.
         *
         * @return the Element.
         */
        public Element getElement() {
            return element;
265 266
        }
    }
Matt Tucker's avatar
Matt Tucker committed
267
}