incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bdelacre...@apache.org
Subject svn commit: r1553100 - in /sling/trunk/samples/mail-archive: ./ stats/ stats/src/ stats/src/main/ stats/src/main/java/ stats/src/main/java/org/ stats/src/main/java/org/apache/ stats/src/main/java/org/apache/sling/ stats/src/main/java/org/apache/sling/m...
Date Mon, 23 Dec 2013 12:13:36 GMT
Author: bdelacretaz
Date: Mon Dec 23 12:13:35 2013
New Revision: 1553100

URL: http://svn.apache.org/r1553100
Log:
SLING-3297 - add my (work in progress) stats module

Added:
    sling/trunk/samples/mail-archive/stats/   (with props)
    sling/trunk/samples/mail-archive/stats/pom.xml
    sling/trunk/samples/mail-archive/stats/src/
    sling/trunk/samples/mail-archive/stats/src/main/
    sling/trunk/samples/mail-archive/stats/src/main/java/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
    sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
    sling/trunk/samples/mail-archive/stats/src/main/resources/
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
  (with props)
    sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
  (with props)
Modified:
    sling/trunk/samples/mail-archive/pom.xml

Modified: sling/trunk/samples/mail-archive/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/pom.xml?rev=1553100&r1=1553099&r2=1553100&view=diff
==============================================================================
--- sling/trunk/samples/mail-archive/pom.xml (original)
+++ sling/trunk/samples/mail-archive/pom.xml Mon Dec 23 12:13:35 2013
@@ -11,5 +11,6 @@
     <modules>
         <module>server</module>
         <module>ui</module>
+        <module>stats</module>
     </modules>
 </project>

Propchange: sling/trunk/samples/mail-archive/stats/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Dec 23 12:13:35 2013
@@ -0,0 +1,13 @@
+target
+bin
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+
+
+

Added: sling/trunk/samples/mail-archive/stats/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/pom.xml?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/pom.xml (added)
+++ sling/trunk/samples/mail-archive/stats/pom.xml Mon Dec 23 12:13:35 2013
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>18</version>
+    </parent>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>org.apache.sling.mailarchive.stats</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling Mail Archive Server Stats</name>
+    <inceptionYear>2013</inceptionYear>
+    
+    <description>
+        Stats module for the mail archive server
+    </description>
+
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Sling-Initial-Content>
+                            initial-content; uninstall:=true
+                        </Sling-Initial-Content>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.6.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.4.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling.mailarchiveserver</groupId>
+            <artifactId>server</artifactId>
+            <version>0.1.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mime4j-core</artifactId>
+            <version>0.8.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mime4j-mbox-iterator</artifactId>
+            <version>0.8.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mime4j-dom</artifactId>
+            <version>0.8.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+     </dependencies>
+</project>

Added: sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,17 @@
+package org.apache.sling.mailarchive.stats;
+
+import java.util.Date;
+
+/** Compute and store stats */
+public interface MailStatsProcessor {
+    /**
+     * @param date Message timestamp
+     * @param from "From "address of the message
+     * @param to Optional "to" addresses
+     * @param cc Option "Cc" addresses
+     */
+    void computeStats(Date d, String from, String [] to, String [] cc);
+    
+    /** Flush in-memory data to permanent storage */
+    void flush();
+}

Added: sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,6 @@
+package org.apache.sling.mailarchive.stats;
+
+/** Maps an email address to an organization name */
+public interface OrgMapper {
+    String mapToOrg(String email); 
+}

Added: sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,210 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.mailarchive.stats.MailStatsProcessor;
+import org.apache.sling.mailarchive.stats.OrgMapper;
+import org.apache.sling.mailarchiveserver.api.MessageProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Service
+/** Computes stats of how often a given organization writes to
+ *  another one in a given time period which is defined by
+ *  a Date formatter. Using a formatter that uses only year and
+ *  month, for example, yields per-month data.
+ */
+public class MailStatsProcessorImpl implements MailStatsProcessor, MessageProcessor {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    
+    @Reference
+    private OrgMapper orgMapper;
+    
+    @Reference
+    private ResourceResolverFactory resourceResolverFactory;
+    
+    // TODO configurable format
+    final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM");
+    
+    // TODO configurable root path
+    private static final String ROOT_PATH = "/content/mailarchiveserver/stats"; 
+    
+    public static final String DEFAULT_RESOURCE_TYPE = "mailserver/stats";
+    public static final String DESTINATION_RESOURCE_TYPE = "mailserver/stats/destination";
+    public static final String DATA_RESOURCE_TYPE = "mailserver/stats/data";
+    public static final String PERIOD_PROP = "period";
+    private static final String [] EMPTY_STRING_ARRAY = new String[]{};
+    static final String SOURCE_PROP_PREFIX = "FROM_";
+    
+    // We need to count the number of messages to a destination, 
+    // per formatted timestamp and source
+    private final Map<String, DataRecord> data = new HashMap<String, DataRecord>();
+    
+    class DataRecord {
+        final String destination;
+        final Map<String, Integer> sourceCounts = new HashMap<String, Integer>();
+        final String timestampPath;
+        
+        DataRecord(Date d, String destination) {
+            this.destination = orgMapper.mapToOrg(destination);
+            synchronized (dateFormat) {
+                this.timestampPath = dateFormat.format(d); 
+            }
+        }
+        
+        Map<String, Integer> getSourceCounts() {
+            return sourceCounts;
+        }
+        
+        void increment(String source) {
+            source = SOURCE_PROP_PREFIX + orgMapper.mapToOrg(source);
+            Integer count = sourceCounts.get(source);
+            if(count == null) {
+                count = 1;
+            } else {
+                count = count.intValue() + 1;
+            }
+            sourceCounts.put(source, count);
+        }
+        
+        public String getOrgPath() {
+            return ROOT_PATH + "/" + destination;
+        }
+        
+        public String getPath() {
+            return getOrgPath() + "/" + timestampPath;
+        }
+        
+        @Override
+        public String toString() {
+            return new StringBuilder()
+            .append(getClass().getSimpleName())
+            .append(' ')
+            .append(timestampPath)
+            .append(' ')
+            .append(destination)
+            .append(' ')
+            .append(sourceCounts)
+            .toString();
+        }
+        
+        String getKey() {
+            return new StringBuilder()
+            .append(timestampPath)
+            .append('#')
+            .append(destination)
+            .toString();
+        }
+    }
+    
+    public void computeStats(Date d, String from, String [] to, String [] cc) {
+        if(to != null) {
+            for(String dest : to) {
+                addRecord(d, from, dest);
+            }
+        }
+        if(cc != null) {
+            for(String dest : cc) {
+                addRecord(d, from, dest);
+            }
+        }
+    }
+    
+    private void addRecord(Date d, String from, String to) {
+        final DataRecord r = new DataRecord(d, to);
+        synchronized (data) {
+            final DataRecord existing = data.get(r.getKey());
+            if(existing == null) {
+                r.increment(from);
+                data.put(r.getKey(), r);
+            } else {
+                existing.increment(from);
+            }
+        }
+    }
+
+    /** Called by the mail archive server store before storing messages - 
+     *  we hook into this to compute our stats.
+     */
+    @Override
+    public void processMessage(Message m) {
+        log.debug("Processing {}", m);
+        final String [] to = toArray(m.getTo());
+        final String [] cc = toArray(m.getCc());
+        for(String from : MailStatsProcessorImpl.toArray(m.getFrom())) {
+            computeStats(m.getDate(), from.toString(), to, cc);
+        }
+        
+        // TODO call this async and less often?
+        flush();
+    }
+
+    /** Flush in-memory data to permanent storage */
+    public void flush() {
+        try {
+            ResourceResolver resolver = null;
+            try {
+                resolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
+                for(DataRecord r : data.values()) {
+                    // Each org gets its own resource under our root
+                    log.info("Storing {} at path {}", r, r.getPath());
+                    ResourceUtil.getOrCreateResource(resolver, ROOT_PATH, DEFAULT_RESOURCE_TYPE,
DEFAULT_RESOURCE_TYPE, false);
+                    ResourceUtil.getOrCreateResource(resolver, r.getOrgPath(), DESTINATION_RESOURCE_TYPE,
DEFAULT_RESOURCE_TYPE, false);
+                    
+                    // Properties are the message counts per source for this destination
+                    final Map<String, Object> data = new HashMap<String, Object>();
+                    for(Map.Entry<String, Integer> e : r.getSourceCounts().entrySet())
{
+                        data.put(e.getKey(), e.getValue());
+                    }
+                    data.put(PERIOD_PROP, r.timestampPath);
+                    data.put("sling:resourceType", DATA_RESOURCE_TYPE);
+                    
+                    // TODO for now this overwrites existing values,
+                    // need to combine existing and new
+                    ResourceUtil.getOrCreateResource(resolver, r.getPath(), data, DEFAULT_RESOURCE_TYPE,
false);
+                    
+                }
+                data.clear();
+            } finally {
+                if(resolver != null) {
+                    resolver.commit();
+                    resolver.close();
+                }
+            }
+        } catch(Exception e) {
+            log.warn("Exception in flush()", e);
+        }
+    }
+    
+    // TODO don't we have a utility for that?
+    static String makeJcrFriendly(String s) {
+        return s.replaceAll("[\\s\\.-]", "_").replaceAll("\\W", "").replaceAll("\\_", " ").trim().replaceAll("
", "_");
+    }
+
+    static String [] toArray(AbstractList<?> list) {
+        if(list == null) {
+            return null;
+        }
+        final List<String> result = new ArrayList<String>();
+        for(Object o : list) {
+            result.add(o.toString());
+        }
+        return result.toArray(EMPTY_STRING_ARRAY);
+    }
+
+}
\ No newline at end of file

Added: sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,18 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.mailarchive.stats.OrgMapper;
+
+@Component
+@Service
+public class OrgMapperImpl implements OrgMapper {
+
+    @Override
+    public String mapToOrg(String email) {
+        if(email.contains("@")) {
+            return email.split("@")[1];
+        }
+        return email;
+    }
+}

Added: sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,124 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.servlet.ServletException;
+
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.io.JSONWriter;
+
+@SuppressWarnings("serial")
+@SlingServlet(
+        resourceTypes = "mailserver/stats/destination",
+        methods = "GET",
+        extensions="json",
+        selectors="statsdata")
+/** Generates json data for the destination.esp script.
+ * 
+ *  Produces output like 
+ *  <pre>
+ *  
+ *  var statsData = [
+ *      { "period": "2012/01",
+ *        "senders" : {
+ *          "netenviron.com" : 1,
+ *          "builds.apache.org" : 12
+ *      }},
+ *      { "period": "2013/09",
+ *        "senders" : {
+ *          "yahoo.com" : 32,
+ *          "netenviron.com" : 6,
+ *          "builds.apache.org" : 9
+ *      }}
+ *  ];
+ *  var layers = [
+ *      "yahoo.com",
+ *      "netenviron.com",
+ *      "builds.apache.org"
+ *  ];
+ *  
+ *  </pre>
+ *  
+ */
+public class StatsDataServlet extends SlingSafeMethodsServlet {
+
+    @Override
+    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)

+            throws ServletException, IOException {
+        
+        response.setCharacterEncoding("UTF-8");
+        final PrintWriter out = response.getWriter();
+        
+        try {
+            out.write("// data provided by " + getClass().getName() + "\n");
+            final SortedSet<String> layers = new TreeSet<String>();
+            
+            // Visit our child resources and build the statsData object
+            // from those that have the stats data resource type
+            {
+                final JSONWriter w = new JSONWriter(response.getWriter());
+                w.setTidy(true);
+                out.write("var statsData = ");
+                w.array();
+                dumpStatsData(request.getResource(), w, layers);
+                w.endArray();
+                out.flush();
+                out.write(";\n");
+            }
+            
+            // Output the layers array in JSON
+            {
+                final JSONWriter w = new JSONWriter(response.getWriter());
+                w.setTidy(true);
+                out.write("var layers = ");
+                w.array();
+                for(String layer : layers) {
+                    w.value(layer);
+                };
+                w.endArray();
+                out.write(";");
+            }
+            
+        } catch(JSONException je) {
+            throw new ServletException("JSONException in doGet", je);
+        }
+    }
+    
+    /** Dump stats data to w if r is a stats data resource,
+     *  and recurse into children
+     */
+    private void dumpStatsData(Resource r, JSONWriter w, Set<String> layers) throws
JSONException {
+        if(MailStatsProcessorImpl.DATA_RESOURCE_TYPE.equals(r.getResourceType())) {
+            final ValueMap m = r.adaptTo(ValueMap.class);
+            if(m != null) {
+                w.object();
+                w.key("period").value(m.get(MailStatsProcessorImpl.PERIOD_PROP, "NO_PERIOD"));
+                w.key("senders");
+                w.object();
+                for(String key : m.keySet()) {
+                    if(key.startsWith(MailStatsProcessorImpl.SOURCE_PROP_PREFIX)) {
+                        final String source = key.substring(MailStatsProcessorImpl.SOURCE_PROP_PREFIX.length());
+                        layers.add(source);
+                        w.key(source).value(m.get(key));
+                    }
+                }
+                w.endObject();
+                w.endObject();
+            }
+        }
+        
+        for(Resource child : r.getChildren()) {
+            dumpStatsData(child, w, layers);
+        }
+    }
+}
\ No newline at end of file

Added: sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,76 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Iterator;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.mailarchive.stats.MailStatsProcessor;
+import org.apache.sling.mailarchiveserver.api.MboxParser;
+
+@SuppressWarnings("serial")
+@SlingServlet(
+        resourceTypes = "mailarchiveserver/import",
+        methods = {"POST", "PUT"},
+        selectors="stats")
+/** Test the stats import with
+ *   curl -u admin:admin -XPOST -Fmboxfile=@jackrabbit-dev-201201.mbox http://localhost:8080/content/mailarchiveserver/import.stats.txt
+ */
+public class StatsTestServlet extends SlingAllMethodsServlet {
+
+    private static final String IMPORT_FILE_ATTRIB_NAME = "mboxfile";
+
+    @Reference
+    private MboxParser parser;
+    
+    @Reference
+    private MailStatsProcessor processor;
+
+    @Override
+    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)

+            throws ServletException, IOException {
+        final RequestParameter param = request.getRequestParameter(IMPORT_FILE_ATTRIB_NAME);
+        if(param == null) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing required parameter
" + IMPORT_FILE_ATTRIB_NAME);
+            return;
+        }
+        
+        InputStream is = null;
+        final PrintWriter pw = response.getWriter();
+        response.setContentType("text/plain");
+        response.setCharacterEncoding("UTF-8");
+        
+        try {
+            is = param.getInputStream();
+            pw.println("Creating stats from supplied mbox file...");
+            int counter=0;
+            final Iterator<Message> it = parser.parse(is);
+            while(it.hasNext()) {
+                final Message m = it.next();
+                final String [] to = MailStatsProcessorImpl.toArray(m.getTo());
+                final String [] cc = MailStatsProcessorImpl.toArray(m.getCc());
+                for(String from : MailStatsProcessorImpl.toArray(m.getFrom())) {
+                    processor.computeStats(m.getDate(), from.toString(), to, cc);
+                }
+                counter++;
+            }
+            pw.println(counter + " messages parsed");
+        } finally {
+            processor.flush();
+            pw.flush();
+            if(is != null) {
+                is.close();
+            }
+        }
+    }
+}
\ No newline at end of file

Added: sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+
+<%
+// d3js.org rendering of a destination's stats 
+// based on the http://bl.ocks.org/mbostock/3943967 example
+
+var title = resource.path.replace(/.*\/(.*)/g,"$1")
+%>
+
+<title><%= title %></title>
+
+<style>
+
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  margin: auto;
+  position: relative;
+  width: 960px;
+}
+
+text {
+  font: 10px sans-serif;
+}
+
+.axis path,
+.axis line {
+  fill: none;
+  stroke: #000;
+  shape-rendering: crispEdges;
+}
+
+form {
+  position: absolute;
+  right: 10px;
+  top: 10px;
+}
+
+</style>
+
+<script src="http://d3js.org/d3.v3.min.js"></script>
+</head>
+
+<body>
+<h1><%= title %></h1>
+<form>
+  <label><input type="radio" name="mode" value="grouped"> Grouped</label>
+  <label><input type="radio" name="mode" value="stacked" checked> Stacked</label>
+</form>
+<div id="graph"/>
+
+<script>
+<% sling.include(resource.path + ".statsdata.json"); %>
+</script>
+
+<script>
+
+// number of layers and samples per layer
+var n = layers.length;
+var m = statsData.length;
+
+var stack = d3.layout.stack(),
+    layers = stack(d3.range(n).map(function(n) { return layerData(m, n); })),
+    yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return
d.y; }); }),
+    yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return
d.y0 + d.y; }); });
+
+var margin = {top: 40, right: 10, bottom: 20, left: 10},
+    width = 960 - margin.left - margin.right,
+    height = 500 - margin.top - margin.bottom;
+
+var x = d3.scale.ordinal()
+    .domain(d3.range(m))
+    .rangeRoundBands([0, width], .08);
+
+var y = d3.scale.linear()
+    .domain([0, yStackMax])
+    .range([height, 0]);
+
+var color = d3.scale.linear()
+    .domain([0, n - 1])
+    .range(["#aad", "#556"]);
+
+var xAxis = d3.svg.axis()
+    .scale(x)
+    .tickSize(0)
+    .tickPadding(6)
+    .orient("bottom")
+    .tickFormat(xTickFormat);
+
+var svg = d3.select("#graph").append("svg")
+    .attr("width", width + margin.left + margin.right)
+    .attr("height", height + margin.top + margin.bottom)
+  .append("g")
+    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+var layer = svg.selectAll(".layer")
+    .data(layers)
+    .enter().append("g")
+    .attr("class", "layer")
+    .style("fill", function(d, i) { return color(i); });
+
+var rect = layer.selectAll("rect")
+    .data(function(d) { return d; })
+  .enter().append("rect")
+    .attr("x", function(d) { return x(d.x); })
+    .attr("y", height)
+    .attr("width", x.rangeBand())
+    .attr("height", 0);
+
+rect.transition()
+    .delay(function(d, i) { return i * 10; })
+    .attr("y", function(d) { return y(d.y0 + d.y); })
+    .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); });
+
+svg.append("g")
+     .attr("class", "x axis")
+     .attr("transform", "translate(0," + height + ")")
+     .call(xAxis);
+
+d3.selectAll("input").on("change", change);
+
+var timeout = setTimeout(function() {
+  d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
+}, 2000);
+
+function change() {
+  clearTimeout(timeout);
+  if (this.value === "grouped") transitionGrouped();
+  else transitionStacked();
+}
+
+function transitionGrouped() {
+  y.domain([0, yGroupMax]);
+
+  rect.transition()
+      .duration(500)
+      .delay(function(d, i) { return i * 10; })
+      .attr("x", function(d, i, j) { return x(d.x) + x.rangeBand() / n * j; })
+      .attr("width", x.rangeBand() / n)
+    .transition()
+      .attr("y", function(d) { return y(d.y); })
+      .attr("height", function(d) { return height - y(d.y); });
+}
+
+function transitionStacked() {
+  y.domain([0, yStackMax]);
+
+  rect.transition()
+      .duration(500)
+      .delay(function(d, i) { return i * 10; })
+      .attr("y", function(d) { return y(d.y0 + d.y); })
+      .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
+    .transition()
+      .attr("x", function(d) { return x(d.x); })
+      .attr("width", x.rangeBand());
+}
+
+// Format our tick values
+function xTickFormat(index) {
+    return statsData[index].period;
+}
+
+function layerValue(layerIndex, itemIndex) {
+    var layerName = layers[layerIndex];
+    var senders = statsData[itemIndex].senders;
+    if(senders[layerName]) {
+        return senders[layerName];
+    }
+    return -1;
+}
+
+// Generate the data of a given layer
+function layerData(nElements, layerIndex) {
+    var a = [];
+    for (i = 0; i < nElements; ++i) a[i] = i;
+    return a.map(function(d,i) { return { x:i, y: layerValue(layerIndex,i) }});
+}
+
+</script>
+</body>
\ No newline at end of file

Propchange: sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
------------------------------------------------------------------------------
    svn:executable = *

Added: sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
URL: http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
(added)
+++ sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+
+<%
+var title = "Mail archive stats prototype"
+%>
+
+<head>
+<meta charset="utf-8">
+<title><%= title %></title>
+</head>
+
+<body>
+<h1><%= title %></h1>
+
+Statistics are available for the following destinations:
+
+<ul>
+<%
+it = resource.getResourceResolver().listChildren(resource);
+while(it.hasNext()) {
+  child = it.next();
+  %>
+  <li>
+    <a href="<%= child.getPath() %>.html">
+    <%= child.getPath().replace(/.*\/(.*)/g,"$1") %>
+    </a>
+  </li>
+  <%
+}
+%>
+
+</ul>
+</script>
+</body>
\ No newline at end of file

Propchange: sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
------------------------------------------------------------------------------
    svn:executable = *



Mime
View raw message