com.jolira.st4j.impl.ServerTrackerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.jolira.st4j.impl.ServerTrackerImpl.java

Source

/**
 * Copyright (c) 2011 jolira. All rights reserved. This program and the accompanying materials are made available under
 * the terms of the GNU Public License 2.0 which is available at http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 */

package com.jolira.st4j.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jolira.st4j.LogRecord;
import com.jolira.st4j.MetricStore;
import com.jolira.st4j.ServerTracker;
import com.jolira.st4j.ServerUnavailableException;

/**
 * Creates measurement and stores them in the thread-local field and send measurement to the remote server.
 * 
 * @author jfk
 * @date Aug 12, 2011 9:09:31 PM
 * @since 1.0
 * 
 */
@Singleton
public class ServerTrackerImpl implements ServerTracker {
    private static final String CLASS_NAME = ServerTrackerImpl.class.getName();
    static final Logger LOG = LoggerFactory.getLogger(ServerTrackerImpl.class);

    private final static String hostname;
    private static final int LOGS_BATCH_SIZE = 100;

    static {
        try {
            final java.net.InetAddress host = java.net.InetAddress.getLocalHost();

            hostname = host.getHostName();
        } catch (final java.net.UnknownHostException ex) {
            throw new Error("unable to determine the hostname", ex);
        }
    }

    private final Collection<Map<String, Object>> events = new LinkedList<Map<String, Object>>();

    private final Collection<Object> logs = new LinkedList<Object>();

    private final Executor executor;

    private final ClientFactory factory;

    private final MetricStore store;

    private long lastSubmit = 0;

    private final int SUMBIT_INTERVAL = 1000;

    /**
     * Create a new instance.
     * 
     * @param server
     *            the name or names of the remote measurements servers. If there is more than one, the different names
     *            have to be comma delimited. Every name can include a port number (separated from the servername with a
     *            column, such as "localhost:3080").
     * @param timeout
     *            used as the connect timeout
     * @param store
     *            the store to be used
     * @param executor
     *            the executor for uploading the measurements and logs in the background
     * @throws IllegalArgumentException
     *             thrown if the server cannot be used to form a valid URL
     */
    @Inject
    public ServerTrackerImpl(@Named("ServerTrackerServer") final String server,
            @Named("ServerTrackerTimeout") final int timeout, final MetricStore store, final Executor executor)
            throws IllegalArgumentException {
        this.store = store;
        this.executor = executor;
        factory = new ClientFactory(server, timeout) {
            @Override
            protected void postMeasurment(final Object measurement) {
                ServerTrackerImpl.this.postMeasurment(measurement);
            }
        };
    }

    private void addEvent(final Map<String, Object> event) {
        synchronized (events) {
            events.add(event);
        }

        transmit();
    }

    private void addLog(final Map<String, Object> log) {
        synchronized (logs) {
            logs.add(log);
        }
    }

    private void merge(final Map<String, Object> source, final Map<String, Object> target) {
        final Collection<Entry<String, Object>> entries = source.entrySet();

        for (final Entry<String, Object> entry : entries) {
            final String key = entry.getKey();

            if (target.containsKey(key)) {
                continue;
            }

            final Object value = entry.getValue();

            target.put(key, value);

        }
    }

    @Override
    public void post(final LogRecord record) {
        if (CLASS_NAME.equals(record.sourceClassName)) {
            return;
        }

        synchronized (logs) {
            logs.add(record);
        }

        transmit();
    }

    @Override
    public void postMeasurment(final Object measurement) {
        store.postThreadLocalMeasurement(null, measurement, true);
    }

    @Override
    public void postMeasurment(final String name, final Object measurement) {
        store.postThreadLocalMeasurement(null, measurement, true);
    }

    @Override
    public void postMeasurment(final String name, final Object measurement, final boolean unique) {
        store.postThreadLocalMeasurement(null, measurement, unique);
    }

    @Override
    public Collection<Map<String, Object>> proxyEvent(final Map<String, Object> eventInfo,
            final InputStream content) throws IllegalArgumentException {
        final GsonBuilder gsonBuilder = new GsonBuilder();

        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());

        final Gson parser = gsonBuilder.create();
        final Object _payload = parser.fromJson(new InputStreamReader(content), Object.class);

        LOG.info("proxying {}", _payload);

        if (_payload == null || !(_payload instanceof Map)) {
            throw new IllegalArgumentException("not a map: " + _payload);
        }

        @SuppressWarnings("unchecked")
        final Map<String, Object> payload = (Map<String, Object>) _payload;

        @SuppressWarnings("unchecked")
        final Collection<Map<String, Object>> _events = (Collection<Map<String, Object>>) payload.get("events");
        @SuppressWarnings("unchecked")
        final Collection<Map<String, Object>> _logs = (Collection<Map<String, Object>>) payload.get("logs");

        payload.remove("events");
        payload.remove("logs");
        merge(eventInfo, payload);

        if (_logs != null) {
            for (final Map<String, Object> log : _logs) {
                merge(payload, log);
                addLog(log);
            }
        }

        final Collection<Map<String, Object>> result = new ArrayList<Map<String, Object>>(1);

        if (_events != null) {
            for (final Map<String, Object> event : _events) {
                merge(payload, event);
                addEvent(event);
                result.add(event);
            }
        }

        return result;
    }

    private Collection<Map<String, Object>> retrieveCollectedCycles() {
        synchronized (events) {
            final int size = events.size();

            if (size < 1) {
                return null;
            }

            final Collection<Map<String, Object>> pending = new ArrayList<Map<String, Object>>(size);

            pending.addAll(events);
            events.clear();

            return pending;
        }
    }

    private Collection<Object> retrieveCollectedLogs(final boolean willTransmitEvents) {
        final int limit = willTransmitEvents ? 1 : LOGS_BATCH_SIZE;

        synchronized (logs) {
            final int size = logs.size();

            if (size < limit) {
                return null;
            }

            final Collection<Object> pending = new ArrayList<Object>(size);

            pending.addAll(logs);
            logs.clear();

            return pending;
        }
    }

    private Map<String, Object> retrievePending() {
        final Collection<Map<String, Object>> _events = retrieveCollectedCycles();
        final Collection<Object> _logs = retrieveCollectedLogs(_events != null);

        if (_events == null && _logs == null) {
            return null;
        }

        final Map<String, Object> pending = new HashMap<String, Object>();

        if (_events != null) {
            pending.put("events", _events);
        }

        if (_logs != null) {
            pending.put("logs", _logs);
        }

        final long now = System.currentTimeMillis();

        pending.put("source", hostname);
        pending.put("timestamp", Long.valueOf(now));

        return pending;
    }

    @Override
    public void submit(final Map<String, Object> eventInfo) {
        final Map<String, Object> measurements = store.getAndResetThreadLocalMetrics();
        final int size = measurements.size();

        if (size < 1) {
            return;
        }

        final Map<String, Object> event = new HashMap<String, Object>();

        event.put("measurements", measurements);
        merge(eventInfo, event);
        addEvent(event);
    }

    private synchronized void transmit() {
        final long time = System.currentTimeMillis();

        if (time - lastSubmit < SUMBIT_INTERVAL) {
            return;
        }

        final Map<String, Object> _pending = retrievePending();

        if (_pending == null) {
            return;
        }

        final ClientFactory f = factory;
        final Gson gson = new Gson();

        lastSubmit = time;

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    transmit(gson, _pending, f);
                } catch (final Throwable e) {
                    LOG.warn("error while submitting data", e);
                }
            }
        });
    }

    /**
     * Post to the remote server. A template method that allows subclasses to pre-process JSON before submitting it to
     * the server.
     * 
     * @param gson
     *            the GSON object to be used to encode the contents
     * @param pending
     *            the events to be encoded
     * @param f
     *            the factory to be used to create the client
     * 
     * @throws IOException
     *             could not transmit
     */
    protected void transmit(final Gson gson, final Map<String, Object> pending, final ClientFactory f)
            throws IOException {
        final Client client = f.makeClient();
        final OutputStream os = client.getOutputStream();
        final OutputStreamWriter wr = new OutputStreamWriter(os);

        try {
            gson.toJson(pending, wr);
        } finally {
            wr.close();
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("sending {}", gson.toJson(pending));
        }

        try {
            client.transmit();
        } catch (final ServerUnavailableException e) {
            LOG.error("no server for {}", gson.toJson(pending));
        }
    }
}