xc.mst.bo.record.RecordCounts.java Source code

Java tutorial

Introduction

Here is the source code for xc.mst.bo.record.RecordCounts.java

Source

/**
 * Copyright (c) 2010 eXtensible Catalog Organization
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the MIT/X11 license. The text of the
 * license can be found at http://www.opensource.org/licenses/mit-license.php and copy of the license can be found on the project
 * website http://www.extensiblecatalog.org/.
 *
 */
package xc.mst.bo.record;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import xc.mst.utils.Util;

/**
 * Requirements this tries to adhere to can be found here:
 * http://code.google.com/p/xcmetadataservicestoolkit/wiki/LoggingRecordCounts
 *
 * I found it so vital to entangling what is going on here that I am going to include part of it here as well.
 *
 * Dave in email on 2011-03-31
    
I guess I have an initial comment to start:
    
We are counting active, updates, and deletes, but I believe there are at least four categories
- perhaps we should count the following kinds of arriving records
(I am assuming that active is defined as a record that is not marked-deleted):
    
1) new record (active)
    
2) new record (marked-deleted)
    
3) updated record (active)
    
4) updated record (marked-deleted)
    
We could also count some additional categories that would also look at what the updated records are replacing (two of the above cases) :
    
3a) updated (active replacing an existing active)
    
3b) updated (active replacing an existing marked-deleted)
    
4a) updated (marked-deleted replacing an existing active)
    
4b) updated (marked-deleted replacing an existing marked-deleted)
    
 *
 * @author Benjamin Anderson
 *
 */
public class RecordCounts {

    private static final Logger LOG = Logger.getLogger(RecordCounts.class);

    public static final Date TOTALS_DATE = new Date(0);
    public static final String TOTALS = "TOTALS";
    public static final String OTHER = "unknown";

    public static String INCOMING = "incoming";
    public static String OUTGOING = "outgoing";

    public static String NEW_ACTIVE = "new_act_cnt";
    public static String NEW_HELD = "new_held_cnt";
    public static String NEW_DELETE = "new_del_cnt";
    public static String UPDATE_ACTIVE = "upd_act_cnt";
    public static String UPDATE_HELD = "upd_held_cnt";
    public static String UPDATE_DELETE = "upd_del_cnt";

    public static String UNEXPECTED_ERROR = "unexpected_error_cnt";

    public static Set<String> INCOMING_STATUS_COLUMN_NAMES = null;
    public static Map<String, String> UPD_PREV_COLUMN_NAMES = null;

    static {
        UPD_PREV_COLUMN_NAMES = new LinkedHashMap<String, String>();

        UPD_PREV_COLUMN_NAMES.put(Record.ACTIVE + "" + Record.ACTIVE, "upd_act_prev_act_cnt");
        UPD_PREV_COLUMN_NAMES.put(Record.ACTIVE + "" + Record.HELD, "upd_act_prev_held_cnt");
        UPD_PREV_COLUMN_NAMES.put(Record.ACTIVE + "" + Record.DELETED, "upd_act_prev_del_cnt");

        UPD_PREV_COLUMN_NAMES.put(Record.HELD + "" + Record.ACTIVE, "upd_held_prev_act_cnt");
        UPD_PREV_COLUMN_NAMES.put(Record.HELD + "" + Record.HELD, "upd_held_prev_held_cnt");
        UPD_PREV_COLUMN_NAMES.put(Record.HELD + "" + Record.DELETED, "upd_held_prev_del_cnt");

        UPD_PREV_COLUMN_NAMES.put(Record.DELETED + "" + Record.ACTIVE, "upd_del_prev_act_cnt");
        UPD_PREV_COLUMN_NAMES.put(Record.DELETED + "" + Record.HELD, "upd_del_prev_held_cnt");
        UPD_PREV_COLUMN_NAMES.put(Record.DELETED + "" + Record.DELETED, "upd_del_prev_del_cnt");

        INCOMING_STATUS_COLUMN_NAMES = new LinkedHashSet<String>();

        INCOMING_STATUS_COLUMN_NAMES.add(NEW_ACTIVE);
        INCOMING_STATUS_COLUMN_NAMES.add(NEW_HELD);
        INCOMING_STATUS_COLUMN_NAMES.add(NEW_DELETE);
        INCOMING_STATUS_COLUMN_NAMES.add(UPDATE_ACTIVE);
        INCOMING_STATUS_COLUMN_NAMES.add(UPDATE_HELD);
        INCOMING_STATUS_COLUMN_NAMES.add(UPDATE_DELETE);

    }

    protected Map<String, Map<String, AtomicInteger>> counts = null;
    protected Date harvestStartDate = null;
    protected String incomingOutgoing = null;

    public RecordCounts(Date harvestStartDate, String incomingOutgoing) {
        this.counts = new HashMap<String, Map<String, AtomicInteger>>();
        this.harvestStartDate = harvestStartDate;
        this.incomingOutgoing = incomingOutgoing;
        getCountsByType(RecordCounts.TOTALS);
    }

    public String getIncomingOutgoing() {
        return incomingOutgoing;
    }

    public void setIncomingOutgoing(String incomingOutgoing) {
        this.incomingOutgoing = incomingOutgoing;
    }

    protected Map<String, AtomicInteger> getCountsByType(String type) {
        if (type == null) {
            type = TOTALS;
        }
        Map<String, AtomicInteger> counts4type = counts.get(type);
        if (counts4type == null) {
            counts4type = new HashMap<String, AtomicInteger>();
            counts.put(type, counts4type);
        }
        return counts4type;
    }

    protected AtomicInteger getCount(Map<String, AtomicInteger> counts4type, String col_1) {
        if (UPD_PREV_COLUMN_NAMES.containsKey(col_1)) {
            col_1 = UPD_PREV_COLUMN_NAMES.get(col_1);
        }
        AtomicInteger ai = counts4type.get(col_1);
        if (ai == null) {
            ai = new AtomicInteger(0);
            counts4type.put(col_1, ai);
        }
        return ai;
    }

    /**
     * generally do not externally want to call this one.
     * currently it is only externally called in the case of an error.
     * @param type
     * @param col_1
     */
    public void incr(String type, String col_1) {
        if (type == null) {
            type = TOTALS;
        }
        if (col_1 == null) {
            throw new RuntimeException("bogus");
        }
        getCount(getCountsByType(type), col_1).addAndGet(1);
    }

    /**
     * You probably want to call this one.
     *
     * A collection of things I have learned.
     *
     * This method generally ends up incrementing two counters.
     *
     * i.e. lets take the example:
     * We received 144 generic updated active records.
     * These break down further to all 144 being updated active records replacing active records.
     * So 2 counters get updated, upd_act_cnt += 144 and upd_act_prev_act_cnt += 144.
     *
     * They are both incremented as a result of a updated active record being received that replaces one that was previously active.
     * It could have been an updated active record being received that replaces one that was previously held,
     * or an updated active record received that replaces one that was previously deleted.
     *
     * Another example:
     * upd_act_cnt:        6,755
     * upd_act_prev_act_cnt:        4,212
     * upd_act_prev_held_cnt:        2,543
     * upd_act_prev_del_cnt:            0
     * For the above, we have 6,755 upd_act_cnt, and that breaks down to 4,212 of 1 type and 2,543 of another.
     * The below code does that.
     *
     *
     * RecordIfc tells through comments that get/setType are key for RecordCount reporting.
     * So if you want to see something besides 'Totals' and 'RecordCounts.OTHER'
     * then the service must set the Record's type.
     *
     * As explained in DCTransformationService:
     *          //
            // setting this here increments this type in the record counts when
            // incremented in GenericMetadataService.process() -- else it then
            // increments RecordCounts.OTHER
            //
     *
     * @see RecordIfc
     * @param type - set by the service, see comments above
     * @param newStatus - need to know the prevStatus and the newStatus
                      then with both of these make the decision on which counter(s) to increment
     * @param prevStatus - need to know the prevStatus and the newStatus
                      then with both of these make the decision on which counter(s) to increment
     */
    public void incr(String type, char newStatus, char prevStatus) {
        // LOG.debug("incr - type:"+type+" newStatus:"+newStatus+" prevStatus:"+prevStatus);
        if (type == null) {
            type = TOTALS;
        }
        String col_1 = null;
        if (prevStatus == 0 || prevStatus == Record.NULL) {
            if (newStatus == Record.ACTIVE) {
                col_1 = RecordCounts.NEW_ACTIVE;
            } else if (newStatus == Record.HELD) {
                col_1 = RecordCounts.NEW_HELD;
            } else if (newStatus == Record.DELETED) {
                col_1 = RecordCounts.NEW_DELETE;
            }
        } else {
            if (newStatus == Record.ACTIVE) {
                col_1 = RecordCounts.UPDATE_ACTIVE;
            } else if (newStatus == Record.HELD) {
                col_1 = RecordCounts.UPDATE_HELD;
            } else if (newStatus == Record.DELETED) {
                col_1 = RecordCounts.UPDATE_DELETE;
            }
        }
        try {
            incr(type, col_1);
        } catch (RuntimeException re) {
            LOG.error("type: " + type);
            LOG.error("newStatus: " + newStatus);
            LOG.error("prevStatus: " + prevStatus);
            throw re;
        }
        // the other incr:?
        if (prevStatus != 0 && prevStatus != Record.NULL) {
            getCount(getCountsByType(type), newStatus + "" + prevStatus).addAndGet(1);
        }
    }

    public Map<String, Map<String, AtomicInteger>> getCounts() {
        return counts;
    }

    public int getCount(String type, String col) {
        Map<String, AtomicInteger> counts4type = counts.get(type);
        if (counts4type != null) {
            AtomicInteger ai = counts4type.get(col);
            if (ai != null) {
                return ai.get();
            }
        }
        return 0;
    }

    public int getCount(String type, char status, char prevStatus) {
        Map<String, AtomicInteger> counts4type = counts.get(type);
        if (counts4type != null) {
            AtomicInteger ai = counts4type.get(UPD_PREV_COLUMN_NAMES.get(status + "" + prevStatus));
            if (ai != null) {
                return ai.get();
            }
        }
        return 0;
    }

    public Date getHarvestStartDate() {
        return this.harvestStartDate;
    }

    public void clear() {
        this.counts = new HashMap<String, Map<String, AtomicInteger>>();
    }

    public String toString(String repoName) {
        StringBuilder sb = new StringBuilder();

        // This is for the purpose of making the totals appear last
        List<String> keys = new ArrayList<String>();
        for (String type : counts.keySet()) {
            if (!type.equals(TOTALS) && !type.equals(OTHER)) {
                keys.add(type);
            }
        }
        keys.add(OTHER);
        keys.add(TOTALS);
        for (String type : keys) {
            Map<String, AtomicInteger> counts4Type = counts.get(type);
            if (counts4Type == null)
                continue;

            type = StringUtils.rightPad(type, 25);
            int col = 0;
            List<String> colNames = new ArrayList<String>();
            colNames.addAll(INCOMING_STATUS_COLUMN_NAMES);
            if (RecordCounts.OUTGOING.equals(this.incomingOutgoing)) {
                colNames.addAll(UPD_PREV_COLUMN_NAMES.values());
            } else {
                colNames.add(UNEXPECTED_ERROR);
            }
            String date = "all time            ";
            LOG.debug("TOTALS_DATE.getTime(): " + TOTALS_DATE.getTime());
            LOG.debug("this.harvestStartDate.getTime(): " + this.harvestStartDate.getTime());
            if (this.harvestStartDate.getTime() != TOTALS_DATE.getTime()) {
                LOG.debug("new Util().printDateTime(this.harvestStartDate): "
                        + new Util().printDateTime(this.harvestStartDate));
                LOG.debug("new Util().printDateTime(TOTALS_DATE): " + new Util().printDateTime(TOTALS_DATE));

                date = new Util().printDateTime(this.harvestStartDate);
            }
            for (String updateType : colNames) {
                if (col == 0)
                    sb.append("\n" + incomingOutgoing + " " + date + " " + type + " ");
                long num = 0;
                if (counts4Type.get(updateType) != null) {
                    num = counts4Type.get(updateType).get();
                }
                DecimalFormat myFormatter = new DecimalFormat("###,###,###");
                String line = StringUtils.leftPad(updateType, 25) + ": "
                        + StringUtils.leftPad(myFormatter.format(num), 12) + "  ";
                sb.append(line);

                if (++col == 3) {
                    col = 0;
                }
            }
        }

        return sb.toString();
    }

}