edu.umd.cs.eclipse.courseProjectManager.EclipseLaunchEventLog.java Source code

Java tutorial

Introduction

Here is the source code for edu.umd.cs.eclipse.courseProjectManager.EclipseLaunchEventLog.java

Source

/**
 * Marmoset: a student project snapshot, submission, testing and code review
 * system developed by the Univ. of Maryland, College Park
 * 
 * Developed as part of Jaime Spacco's Ph.D. thesis work, continuing effort led
 * by William Pugh. See http://marmoset.cs.umd.edu/
 * 
 * Copyright 2005 - 2011, Univ. of Maryland
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 * 
 */

package edu.umd.cs.eclipse.courseProjectManager;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.MultipartPostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.team.core.TeamException;

/**
 * @author jspacco
 * 
 *         XXX Absolutely not thread-safe in any way, shape or form.
 */
class EclipseLaunchEventLog {
    static final String LOG_NAME = ".cpmLOG";
    private static final Map<IProject, EclipseLaunchEventLog> map = new HashMap<IProject, EclipseLaunchEventLog>();

    /**
     * Only upload to server after at least this many events have been cached in
     * the file
     */
    private static final int MAX_CACHED_LAUNCH_EVENTS = Integer.getInteger("max.cached.launch.events", 10);

    private IFile file;

    static final String ECLIPSE_LAUNCH_EVENT = "eclipseLaunchEvent";

    private File getLogFile() {
        return file.getRawLocation().toFile();
    }

    private EclipseLaunchEventLog(IProject project) {
        file = project.getFile(LOG_NAME);
        Debug.print(".cpmLOG is located at " + file.getRawLocation().toString());
    }

    private static EclipseLaunchEventLog getLog(IProject project) {
        if (!map.containsKey(project)) {
            map.put(project, new EclipseLaunchEventLog(project));
        }
        return map.get(project);
    }

    private void log(String event) throws IOException {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(getLogFile(), true));
            writer.write(event + "\n");
            writer.flush();
            writer.close();
        } finally {
            try {
                if (writer != null)
                    writer.close();
            } catch (IOException ignore) {
                // ignore
            }
            try {
                file.refreshLocal(1, null);
            } catch (CoreException ignore) {
                // ignore
            }
        }
    }

    private boolean isEmpty() {
        Debug.print("!exists: " + !getLogFile().exists());
        Debug.print("length: " + getLogFile().length());
        return !getLogFile().exists() || getLogFile().length() == 0L;
    }

    private void clear() throws IOException {
        try {
            file.delete(true, null);
        } catch (CoreException e) {
            throw new IOException(e.getMessage());
        }
    }

    private String getEvents() throws IOException {
        if (isEmpty())
            return "";
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(file.getContents()));
            StringBuffer result = new StringBuffer();
            while (true) {
                String line = reader.readLine();
                if (line == null)
                    break;
                result.append(line + "\n");
            }
            return result.toString();
        } catch (CoreException e) {
            throw new IOException(e.getMessage());
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException ignore) {
                // ignore
            }
        }
    }

    static void postEventLogToServer(IProject project) {
        // Log to the course project manager log (visible to users).
        AutoCVSPlugin.getPlugin().getEventLog()
                .logMessage(new Date() + "\t" + "Collecting data for the Marmoset Project.");

        // Look up the EclipseLaunchEventLog for this project. This creates one
        // if it
        // doesn't already exist.
        EclipseLaunchEventLog eclipseLaunchEventLog = EclipseLaunchEventLog.getLog(project);

        // NOTE: Don't try to upload if the user has disabled AutoCVS
        // or has shut off Eclipse launch event monitoring.
        IResource submitUserFile = project.findMember(AutoCVSPlugin.SUBMITUSER);

        // Don't log anything if we don't have a submitUser file,
        // autoCVS mode is disabled, or runlogging is disabled
        if (submitUserFile == null || !AutoCVSPlugin.hasAutoCVSNature(project)
                || !AutoCVSPlugin.hasAutoRunLogNature(project)) {
            return;
        }

        Debug.print("Trying to upload directly to the SubmitServer using a .submitUser file");

        // Find the .submitProject file.
        // This file will only exist for projects controlled by Marmoset.
        IResource submitProjectFile = project.findMember(AutoCVSPlugin.SUBMITPROJECT);
        if (submitProjectFile == null) {
            Debug.print("Can't find .submit file!");
            AutoCVSPlugin.getPlugin().getEventLog()
                    .logMessage("project " + project.getName() + " does not contain a "
                            + AutoCVSPlugin.SUBMITPROJECT + " file.  This project is therefore probably not a "
                            + " Computer Science programming assignment that you checked out of CVS.  "
                            + " Thus you can ignore this message.");
            return;
        }

        try {
            Properties props = new Properties();
            FileInputStream fileInputStream = new FileInputStream(submitProjectFile.getRawLocation().toString());
            props.load(fileInputStream);
            fileInputStream.close();

            props.putAll(TurninProjectAction.getUserProperties(submitUserFile));
            Debug.print("Loaded the properties in the .submitUser file");

            // Nothing to report!
            if (eclipseLaunchEventLog.isEmpty())
                return;
            // Check for launch events that have been queued to the logfile.
            String allEvents = eclipseLaunchEventLog.getEvents();
            Debug.print("Grabbed queued events from logfile");

            Debug.print("Uploading!");

            uploadMessages(allEvents, props);

            // Now clear the .cpmLOG
            eclipseLaunchEventLog.clear();
        } catch (IOException e) {
            AutoCVSPlugin.getPlugin().getEventLog()
                    .logError("Unable to post Eclipse launch events to the server: " + e.getMessage() + "\n"
                            + "This error is related to data gathering for the Marmoset project and should not "
                            + "affect your ability to work on your project.");
            Debug.print("Unable to log eclipse launch event to the server: " + e.toString());
            // e.printStackTrace();
        }
    }

    /**
     * Uploads a String containing eclipse launch events to the server.
     * 
     * @param launchEvents
     *            the eclipse launch events to be uploaded
     * @param classAccount
     *            the class account of the user who generated the launch events
     * @param oneTimePassword
     *            the one-time password of the user who generated the launch
     *            events
     * @param url
     *            the URL of the server that will accept the launch events
     * @throws IOException
     * @throws HttpException
     */
    private static int uploadMessages(String launchEvents, Properties props) throws IOException, HttpException {
        // Replace the path with /eclipse/LogEclipseLaunchEvent.
        if (!props.containsKey("submitURL")) {
            props.put("submitURL", "https://submit.cs.umd.edu:8443/eclipse/LogEclipseLaunchEvent");
        }
        String submitURL = props.getProperty("submitURL");
        Debug.print("submitURL: " + props.getProperty("submitURL"));
        int index = submitURL.indexOf("/eclipse/");
        if (index == -1) {
            Debug.print("Cannot find submitURL in .submitUser file");
            throw new IOException("Cannot find submitURL in .submitUser file");
        }
        submitURL = submitURL.substring(0, index) + "/eclipse/LogEclipseLaunchEvent";
        String version = System.getProperties().getProperty("java.runtime.version");
        boolean useEasyHttps = version.startsWith("1.3") || version.startsWith("1.2") || version.startsWith("1.4.0")
                || version.startsWith("1.4.1") || version.startsWith("1.4.2_0") && version.charAt(7) < '5';
        if (useEasyHttps) {
            if (submitURL.startsWith("https"))
                submitURL = "easy" + submitURL;
        }
        Debug.print("submitURL: " + submitURL);
        MultipartPostMethod filePost = new MultipartPostMethod(submitURL);

        // add filepart
        byte[] bytes = launchEvents.getBytes();
        filePost.addPart(new FilePart("eclipseLaunchEvent", new ByteArrayPartSource("eclipseLaunchEvent", bytes)));

        TurninProjectAction.addAllPropertiesButSubmitURL(props, filePost);

        filePost.addParameter("clientTime", Long.toString(System.currentTimeMillis()));

        HttpClient client = new HttpClient();
        client.setConnectionTimeout(5000);

        int status = client.executeMethod(filePost);
        if (status != HttpStatus.SC_OK) {
            System.err.println(filePost.getResponseBodyAsString());
            throw new HttpException("status is: " + status);
        }
        return status;
    }

    static void logEventToFile(String eventName, IProject project) {
        // If either the project doesn't have run-logging turned on
        // or it's not an enabled/disabled event then don't log the event
        if (!eventName.equals(AutoCVSPlugin.ENABLED) && !eventName.equals(AutoCVSPlugin.DISABLED)
                && !AutoCVSPlugin.hasAutoRunLogNature(project))
            return;

        // Get projectNumber from .submit file
        // In case the Eclipse name of the project differs from the name in the
        // database
        String projectNumber = getProjectNumber(project);

        // eclipseLaunchEvent timestamp md5sum projectName event
        String event = EclipseLaunchEventLog.ECLIPSE_LAUNCH_EVENT + "\t" + System.currentTimeMillis() + "\t"
                + computeChecksumOfCVSControlledFiles(project) + "\t" + projectNumber + "\t" + eventName;

        EclipseLaunchEventLog eclipseLaunchEventLog = EclipseLaunchEventLog.getLog(project);
        try {

            // If we lack the proper .submitUser file
            // then append event to the .cpmLOG file, to be uploaded later.
            eclipseLaunchEventLog.log(event);
        } catch (IOException e) {
            AutoCVSPlugin.getPlugin().getEventLog()
                    .logError("Unable to log Eclipse launch events to a log file: " + e.getMessage() + "\n"
                            + "This error is related to data gathering for the Marmoset project and should not "
                            + "affect your ability to work on your project.");
            Debug.print(e.toString());
            // e.printStackTrace();
        }
    }

    private static Map<String, String> projectNumberMap = new HashMap<String, String>();

    private static String getProjectNumber(IProject project) {
        if (!projectNumberMap.containsKey(project.getName())) {
            IResource submitProjectFile = project.findMember(AutoCVSPlugin.SUBMITPROJECT);
            if (submitProjectFile == null) {
                // If there is no .submit file, then the projectNumber is just
                // the name
                // of the project
                projectNumberMap.put(project.getName(), project.getName());
                return project.getName();
            }
            // Else try to load the properties file
            Properties props = new Properties();
            try {
                FileInputStream fileInputStream = new FileInputStream(
                        submitProjectFile.getRawLocation().toString());
                props.load(fileInputStream);
                fileInputStream.close();
                projectNumberMap.put(project.getName(), props.getProperty("projectNumber"));
            } catch (IOException e) {
                // Can't load file for whatever reason
                projectNumberMap.put(project.getName(), project.getName());
            }
        }
        return projectNumberMap.get(project.getName());
    }

    private static String computeChecksumOfCVSControlledFiles(IProject project) {
        try {
            // Find all the files under CVS control
            Collection<IFile> fileList = TurninProjectAction.findFilesForSubmission(project);
            return md5sumAsHexString(fileList);
        } catch (NoSuchAlgorithmException e) {
            Debug.print("NoSuchAlgorithm " + e.getMessage(), e);
        } catch (IOException e) {
            Debug.print("IOException " + e.getMessage(), e);
        } catch (TeamException e) {
            Debug.print("TeamException " + e.getMessage(), e);
        } catch (CoreException e) {
            Debug.print("CoreException " + e.getMessage(), e);
        }
        return "cant_make_md5sum";
    }

    private static byte[] md5sum(Collection<IFile> fileList)
            throws NoSuchAlgorithmException, IOException, TeamException, CoreException {
        MessageDigest md5 = MessageDigest.getInstance("md5");
        byte[] bytes = new byte[2048];
        for (IFile file : fileList) {
            if (!file.exists()) {
                Debug.print("skipping " + file.getName() + " from the md5sum (this file "
                        + "was probably deleted and so exists according to CVS but is "
                        + "not physically present on the file system");
                continue;
            }
            InputStream is = file.getContents();
            try {
                int numRead;
                while ((numRead = is.read(bytes)) != -1) {
                    md5.update(bytes, 0, numRead);
                }
            } finally {
                if (is != null)
                    is.close();
            }
        }
        return md5.digest();
    }

    /**
     * Converts an array of bytes into a hexadecimal string. TODO This method
     * should go into a separate class of static utilities.
     * 
     * @param bytes
     *            the array of bytes
     * @return the hexadecimal string representation of the byte array
     */
    private static String byteArrayToHexString(byte[] bytes) {
        return new BigInteger(1, bytes).toString(16);
    }

    private static String md5sumAsHexString(Collection<IFile> fileList)
            throws NoSuchAlgorithmException, IOException, TeamException, CoreException {
        return byteArrayToHexString(md5sum(fileList));
    }
}