001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014 ForgeRock AS.
015 */
016
017package org.forgerock.openig.audit.monitor;
018
019import static org.forgerock.openig.audit.Tag.*;
020
021import java.io.IOException;
022import java.util.LinkedHashMap;
023import java.util.Set;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.atomic.AtomicLong;
026
027import org.forgerock.openig.audit.AuditEvent;
028import org.forgerock.openig.audit.AuditEventListener;
029import org.forgerock.openig.audit.ConditionalAuditEventListener;
030import org.forgerock.openig.audit.Tag;
031import org.forgerock.openig.handler.GenericHandler;
032import org.forgerock.openig.handler.HandlerException;
033import org.forgerock.openig.http.Exchange;
034import org.forgerock.openig.http.Response;
035import org.forgerock.openig.util.EnumUtil;
036
037
038/**
039 * Sample statistic endpoint provider that returns JSON-formatted collected statistic values.
040 */
041public class MonitorEndpointHandler extends GenericHandler implements AuditEventListener {
042
043    private static final Set<String> STANDARD_TAG_NAMES = EnumUtil.names(Tag.class);
044
045    private ConcurrentHashMap<String, TagMetric> metrics = new ConcurrentHashMap<String, TagMetric>();
046
047    @Override
048    public void handle(final Exchange exchange) throws HandlerException, IOException {
049        Response response = new Response();
050        response.getEntity().setJson(metrics);
051        response.setStatus(200);
052        exchange.response = response;
053    }
054
055    @Override
056    public void onAuditEvent(final AuditEvent event) {
057        // Extract the set of additional tags
058        Set<String> tags = event.getTags();
059
060        // Manage counter for each of the additional tags, effectively performing correlations
061        for (String tag : tags) {
062            // Ignore tag if it is a standard one
063            if (STANDARD_TAG_NAMES.contains(tag)) {
064                continue;
065            }
066            TagMetric metric = getMetric(tag);
067            if (tags.contains(request.name())) {
068                metric.active.incrementAndGet();
069            }
070            if (tags.contains(response.name())) {
071                metric.active.decrementAndGet();
072                if (tags.contains(completed.name())) {
073                    metric.completed.incrementAndGet();
074                }
075                if (tags.contains(exception.name())) {
076                    metric.errors.incrementAndGet();
077                }
078            }
079        }
080    }
081
082    private TagMetric getMetric(final String name) {
083        TagMetric counter = metrics.get(name);
084        if (counter != null) {
085            return counter;
086        }
087        TagMetric newCounter = new TagMetric();
088        TagMetric oldCounter = metrics.putIfAbsent(name, newCounter);
089        return oldCounter != null ? oldCounter : newCounter;
090    }
091
092    /**
093     * TagMetric extends a Map implementation to benefit of the natural mapping into a JSON object.
094     * Still have 'in progress', 'completed' and 'internal errors' fields for easy programmatic access.
095     * TagMetric is thread-safe because no additional fields are put in the structure after creation.
096     */
097    private static class TagMetric extends LinkedHashMap<String, AtomicLong> {
098
099        public static final long serialVersionUID = 1L;
100
101        final AtomicLong active = new AtomicLong();
102        final AtomicLong completed = new AtomicLong();
103        final AtomicLong errors = new AtomicLong();
104
105        public TagMetric() {
106            put("in progress", active);
107            put("completed", completed);
108            put("internal errors", errors);
109        }
110    }
111
112    /**
113     * Creates and initializes a MonitorEndpointHandler in a heap environment.
114     */
115    public static class Heaplet extends ConditionalAuditEventListener.ConditionalListenerHeaplet {
116        @Override
117        protected AuditEventListener createListener() {
118            return new MonitorEndpointHandler();
119        }
120    }
121
122}