Java tutorial
/* * Copyright (c) 2013 DataTorrent, Inc. ALL Rights Reserved. * * 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 com.datatorrent.contrib.frauddetect; import com.datatorrent.api.BaseOperator; import com.datatorrent.api.DefaultInputPort; import com.datatorrent.api.DefaultOutputPort; import com.datatorrent.lib.util.KeyValPair; import com.datatorrent.contrib.frauddetect.util.JsonUtils; import org.apache.commons.lang.mutable.MutableLong; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.*; /** * An operator to alert in case a transaction of a small lowAmount is followed by a transaction which is significantly larger for a given credit card number. * This is done for each transaction. This also means that this happens for each individual credit card. * It accepts merchant transaction object and for each CC listed in the transaction(s), checks for the transaction amounts. An alert is raised if the transaction * lowAmount is significantly > the lowest amt in this window. * * @since 0.9.0 */ public class CreditCardAmountSamplerOperator extends BaseOperator { private transient final JsonFactory jsonFactory = new JsonFactory(); private transient final ObjectMapper mapper = new ObjectMapper(jsonFactory); private static final Logger logger = LoggerFactory.getLogger(Application.class); // Factor to be applied to existing lowAmount to flag potential alerts. private double threshold = 9500; private Map<String, CreditCardInfo> ccTxnMap = new HashMap<String, CreditCardInfo>(); //private Map<String, MutableLong> ccQueryTxnMap = new HashMap<String, MutableLong>(); private List<CreditCardAlertData> alerts = new ArrayList<CreditCardAlertData>(); //private List<CreditCardAlertData> userAlerts = new ArrayList<CreditCardAlertData>(); private static final String ALERT_MSG = "Potential fraudulent CC transactions (small one USD %d followed by large USD %d) performed using credit card: %s"; public final transient DefaultOutputPort<String> ccAlertOutputPort = new DefaultOutputPort<String>(); /* public final transient DefaultOutputPort<Map<String, Object>> ccUserAlertOutputPort = new DefaultOutputPort<Map<String, Object>>(); */ public final transient DefaultOutputPort<Map<String, Object>> ccAlertNotificationPort = new DefaultOutputPort<Map<String, Object>>(); public double getThreshold() { return threshold; } public void setThreshold(double threshold) { this.threshold = threshold; } private void processTuple(KeyValPair<MerchantKey, CreditCardData> tuple, Map<String, CreditCardInfo> txMap) { String fullCcNum = tuple.getValue().fullCcNum; long ccAmount = tuple.getValue().amount; MerchantKey key = tuple.getKey(); CreditCardInfo cardInfo = txMap.get(fullCcNum); if (cardInfo != null) { long currentSmallValue = cardInfo.lowAmount.longValue(); if (ccAmount < currentSmallValue) { cardInfo.lowAmount.setValue(ccAmount); cardInfo.time = key.time; } else if (ccAmount > (currentSmallValue + threshold)) { // If the transaction lowAmount is > 70% of the min. lowAmount, send an alert. CreditCardAlertData data = new CreditCardAlertData(); data.merchantId = key.merchantId; data.terminalId = key.terminalId == null ? 0 : key.terminalId; data.zipCode = key.zipCode; data.merchantType = key.merchantType; data.fullCcNum = fullCcNum; data.small = currentSmallValue; data.large = ccAmount; data.threshold = threshold; data.userGenerated = key.userGenerated; data.time = System.currentTimeMillis(); alerts.add(data); /* if (userGenerated){ userAlerts.add(data); } */ ccAlertNotificationPort.emit(getOutputData(data)); // Any high value transaction after a low value transaction with difference greater than threshold // will trigger the alert. Not resetting the low value also helps in a system generated transaction // alert not resetting the low value from a user generated transaction //txMap.remove(fullCcNum); } } else { cardInfo = new CreditCardInfo(); cardInfo.lowAmount.setValue(ccAmount); cardInfo.time = key.time; txMap.put(fullCcNum, cardInfo); } } public transient DefaultInputPort<KeyValPair<MerchantKey, CreditCardData>> inputPort = new DefaultInputPort<KeyValPair<MerchantKey, CreditCardData>>() { // // This function checks if a CC entry exists. // If so, it checks whether the current transaction is for an lowAmount lesser than the one stored in the hashmap. If so, this becomes the min. transaction lowAmount. // If the lowAmount is > 70% of the existing lowAmount in the hash map, raise an alert. // @Override public void process(KeyValPair<MerchantKey, CreditCardData> tuple) { processTuple(tuple, ccTxnMap); } }; @Override public void endWindow() { for (CreditCardAlertData data : alerts) { try { ccAlertOutputPort.emit(JsonUtils.toJson(data)); } catch (IOException e) { logger.warn("Exception while converting object to JSON", e); } } //for (CreditCardAlertData data: userAlerts) { /*for (CreditCardAlertData data: alerts) { ccAlertNotificationPort.emit(getOutputData(data)); }*/ long ctime = System.currentTimeMillis(); Iterator<Map.Entry<String, CreditCardInfo>> iterator = ccTxnMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, CreditCardInfo> entry = iterator.next(); long time = entry.getValue().time; if ((ctime - time) > 60000) { iterator.remove(); } } //ccTxnMap.clear(); alerts.clear(); //ccQueryTxnMap.clear(); //userAlerts.clear(); } private class CreditCardInfo { MutableLong lowAmount = new MutableLong(); Long time; } private Map<String, Object> getOutputData(CreditCardAlertData data) { Map<String, Object> output = new HashMap<String, Object>(); output.put("message", String.format(ALERT_MSG, data.small, data.large, data.fullCcNum)); output.put("alertType", "smallThenLarge"); output.put("userGenerated", "" + data.userGenerated); output.put("alertData", data); try { String str = mapper.writeValueAsString(output); logger.debug("Alert generated: " + str + " userGenerated: " + data.userGenerated); } catch (Exception exc) { //ignore } return output; } }