/*
* ACEItemEntryActionImpl
*
* 07/10/2003
*
* Copyright:
* Licensed Materials - Property of IBM
* "Restricted Materials of IBM"
* 5724-AEF
* (C) Copyright IBM Corp. 2003.
*
* %W% %E%
*/
package com.ibm.retail.AEF.action;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.ibm.retail.AEF.automation.ActionRequest;
import com.ibm.retail.AEF.automation.AndCondition;
import com.ibm.retail.AEF.automation.Condition;
import com.ibm.retail.AEF.automation.DetectorAccess;
import com.ibm.retail.AEF.automation.ItemIdentifier;
import com.ibm.retail.AEF.automation.ObjectDetector;
import com.ibm.retail.AEF.automation.PropertyEqualsCondition;
import com.ibm.retail.AEF.automation.State;
import com.ibm.retail.AEF.automation.Substate;
import com.ibm.retail.AEF.data.POSDeviceProperties;
import com.ibm.retail.AEF.data.TransactionStatusProperties;
import com.ibm.retail.AEF.event.AEFPropertyChangeEvent;
import com.ibm.retail.AEF.event.AEFPropertyChangeListener;
import com.ibm.retail.AEF.session.SessionContext;
import com.ibm.retail.AEF.thread.ConditionLock;
import com.ibm.retail.AEF.thread.ObjectDetectorLock;
import com.ibm.retail.AEF.util.AEFErrorHandler;
import com.ibm.retail.AEF.util.AEFMessage;
import com.ibm.retail.AEF.util.AEFPerfTrace;
import com.ibm.retail.AEF.util.BadConditionsImpl;
import com.ibm.retail.si.util.AEFConst;
import com.ibm.retail.si.util.AEFException;
/**
* ACEItemEntryActionImpl is a class which is used to add
* an item entry to a transaction on the ACE application.
*
*/
public class ACEItemEntryActionImpl extends ACEActionImpl
implements AEFPropertyChangeListener
{
static String copyright()
{ return com.ibm.retail.si.Copyright.IBM_COPYRIGHT_SHORT; }
private static AEFPerfTrace perfTrace = AEFPerfTrace.getInstance();
/**
* Constructor
*
* @param request The ActionRequest which contains a HashMap of arguments.
* @exception AEFException
*
*/
public ACEItemEntryActionImpl(ActionRequest request) throws AEFException
{
super(request);
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter constructor of ACEItemEntryActionImpl.");
log.trace(tempAEFMessage);
}
// theValidSubstates contains a list of all the substates for which
// an item may be entered. This is a class static, so it is only
// initialized once.
if (theValidSubstates == null)
{
theValidSubstates = new Vector();
theValidSubstates.addElement(Substate.getSubstate("SELECT_PROCEDURE"));
theValidSubstates.addElement(Substate.getSubstate("EXPECTING_ITEM"));
theValidSubstates.addElement(Substate.getSubstate("EXPECTING_TENDER"));
theValidSubstates.addElement(Substate.getSubstate("EXPECTING_COUPON"));
theValidSubstates.addElement(Substate.getSubstate("TRAINING_MODE"));
theValidSubstates.addElement(Substate.getSubstate("ENTER_ITEM"));
theValidSubstates.addElement(Substate.getSubstate("ITEM_TRAINING")); // 5103
}
// theItemID = (ItemIdentifier)(request.getArguments().get("id"));
theItemID = (ItemIdentifier)(request.getArguments());
theQty = theItemID.getQuantity();
thePrice = theItemID.getPrice();
theWeight = theItemID.getWeight();
theDealPrice = theItemID.getDealPrice();
theDealQuantity = theItemID.getDealQuantity();
if (thePrice != null)
{
// Remove any non-digits from the price.
thePrice = thePrice.replaceAll("\\D","");
}
if (theWeight != null)
{
// Remove any non-digits from the weight.
theWeight = theWeight.replaceAll("\\D","");
}
if (theDealPrice != null)
{
// Remove any non-digits from the deal price.
theDealPrice = theDealPrice.replaceAll("\\D","");
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit constructor of ACEItemEntryActionImpl.");
log.trace(tempAEFMessage);
}
}
/**
* Perform the action represented by the ActionRequest and return an ActionResult.
*
*
* @param request The ActionRequest which contains the classname and arguments.
* @return Object The Item instance.
* @exception AEFException
* Among the possible AEFException error codes are:
*
AEFConst.INVALID_ARGUMENT, AEFConst.WEIGHT_AND_QUANTITY_MUTUALLY_EXCLUSIVE
*
AEFConst.APPLICATION_NOT_IN_PROPER_STATE, AEFConst.APPLICATION_NOT_IN_PROPER_SUBSTATE
*
Common Errors
*/
public Object performAction() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter ACEItemEntryActionImpl.performAction()");
log.trace(tempAEFMessage);
}
// Call super to perform any common processing and clear any errors before we start.
super.performAction();
Object retVal = null;
if (log.isDebugEnabled())
{
log.debug( new AEFMessage( sessionID, "ACEItemEntryActionImpl.performAction() itemCode = " + theItemID.getItemCode() +
", qty = " + theQty +
", price = " + thePrice +
", weight = " + theWeight +
", dealPrice = " + theDealPrice +
", dealQuantity = " + theDealQuantity)) ;
}
// If app is in secured state, and options are set to automatically do a special
// signon, then do a special signon first.
checkForSpecialSignon();
int seqResult = -1;
theDetector = ((DetectorAccess)(SessionContext.getSession())).getLineItemDetector();
//Check that ACE is in one of the substates which is valid for item entry.
//If not, throw an exception.
String currentSubstate = getCurrentSubstate();
if (theValidSubstates.contains(currentSubstate))
{
// Validate input, cannot have qty and weight in same sequence
if (theWeight != null && theWeight.length() != 0)
{
if (theQty != null && theQty.length() != 0)
{
// input bad, cannot have both keyed weight and keyed quantity
String tempString = "ACEItemEntryActionImpl.performAction(): Cannot have both a keyed weight and a keyed quantity.";
AEFException tempException = new AEFException(AEFConst.INVALID_ARGUMENT,
AEFConst.WEIGHT_AND_QUANTITY_MUTUALLY_EXCLUSIVE,
tempString);
tempAEFMessage.setMessage(tempString);
log.error(tempAEFMessage, tempException);
throw tempException;
}
}
seqResult = sendItemEntrySequence();
if (seqResult==0)
{
retVal = waitForItem();
}
}
else
{
// Not in valid state for item entry.
String tempString = "ACEItemEntryActionImpl.performAction(): POS application not in proper substate to perform item entry.\n Application substate is " + currentSubstate + ".";
AEFException tempException = new AEFException(AEFConst.APPLICATION_NOT_IN_PROPER_STATE,
AEFConst.APPLICATION_NOT_IN_PROPER_SUBSTATE,
tempString);
tempAEFMessage.setMessage(tempString);
log.error(tempAEFMessage, tempException);
throw tempException;
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit ACEItemEntryActionImpl.performAction()");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the key sequence which will cause the item to be sold.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int sendItemEntrySequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter ACEItemEntryActionImpl.sendItemEntrySequence()");
log.trace(tempAEFMessage);
}
int retVal = -1;
theKeySequenceAction = buildKeySequence();
// The good state is state 10 (main) and substate 1001 (expecting item). However, because of
// timing conditions with ACE and the fact that we start off in the same state/substate that we
// expect to end in, we need to use a couple of other POSDataProvider properties to "remember"
// various conditions that become true during the actual execution of the add-item command.
Condition andConditions[] =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("MAIN")),
// note: this next property is set to true when the ITEM_SALE_IN_PROGRESS property is
// set to TRUE. We have to remember this, because the ITEM_SALE_IN_PROGRESS property is
// set to FALSE by the time the add-item command finishes.
new PropertyEqualsCondition(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_ITEM_ADDED,
"true"),
// note: this next property is set to true when an "EXPECTING_ITEM" or "EXPECTING_COUPON"
// substate is received.
new PropertyEqualsCondition(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.EXPECTING_ITEM,
"true"),
};
Condition goodConditions[] =
{
new AndCondition(andConditions)
};
try
{
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_ITEM_ADDED,
"false");
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.EXPECTING_ITEM,
"false");
dataProvider.addAEFPropertyChangeListener(this,
TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.ITEM_SALE_IN_PROGRESS);
dataProvider.addAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE);
}
catch (RemoteException re) { /* Can't get here. */ }
theLock = new ConditionLock();
// Save any current item instance number so we are sure to only get an item
// created after this key sequence is sent.
theInstanceNumber = theDetector.getInstanceNumber();
if (perfTrace.isEnabled(AEFPerfTrace. COARSE))
{
perfTrace.reportTimer(AEFPerfTrace. COARSE,
sessionID,
"addItem",
">>>Sending addItem key sequence to application.");
}
try {
retVal = theLock.performActionAndWait("wait-for-item-sale-in-progress",
theKeySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Send Item Entry Sequence Error");
retVal = errorHandler.handleError(theLock,
theKeySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout(),
retVal);
}
} catch (AEFException e) {
tempAEFMessage.setMessage("There was an AEF exception thrown in ACEItemEntryActionImpl.sendItemEntrySequence()");
log.error(tempAEFMessage, e);
throw e;
} finally {
try {
dataProvider.removeAEFPropertyChangeListener(this,
TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.ITEM_SALE_IN_PROGRESS);
dataProvider.removeAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE);
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit ACEItemEntryActionImpl.sendItemEntrySequence()");
log.trace(tempAEFMessage);
}
} catch (RemoteException re) { /* Can't get here. */ }
}
return retVal;
}
/**
* Builds the appropriate key sequence action for the item entry.
*
* @exception AEFException
*/
protected AEFAction buildKeySequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter ACEItemEntryActionImpl.buildKeySequence()");
log.trace(tempAEFMessage);
}
args.clear();
StringBuffer sequenceIdBuf = new StringBuffer();
if (thePrice != null)
{
sequenceIdBuf.append("itemPrice+");
args.put("%itemPrice.0", thePrice);
}
else if (theDealPrice != null)
{
sequenceIdBuf.append("itemDealPrice+");
args.put("%itemDealPrice.0", theDealQuantity);
args.put("%itemDealPrice.1", theDealPrice);
}
if (theQty != null)
{
sequenceIdBuf.append("itemQty+");
args.put("%itemQty.0", theQty);
}
else if (theWeight != null)
{
sequenceIdBuf.append("itemWeight+");
args.put("%itemWeight.0", theWeight);
}
sequenceIdBuf.append("item");
args.put("%item.0", theItemID.getItemCode());
args.put("SEQUENCE_ID", new String(sequenceIdBuf));
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit ACEItemEntryActionImpl.buildKeySequence()");
log.trace(tempAEFMessage);
}
return (AEFAction) (actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
}
/**
* Sends the key sequence which will cause the item to be sold.
*
*
* @return ArrayList An array of LineItems created by the action.
* @exception AEFException
*/
public ArrayList waitForItem() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter ACEItemEntryActionImpl.waitForItem()");
log.trace(tempAEFMessage);
}
ArrayList retVal = null;
// Successfully got to item entry. Wait for the array of LineItems to be created.
ObjectDetectorLock objLock = new ObjectDetectorLock();
retVal = (ArrayList)(objLock.waitForNewObject("wait-for-item-array", theDetector, theInstanceNumber, getTimeout()));
if (perfTrace.isEnabled(AEFPerfTrace. COARSE))
{
perfTrace.reportTimer(AEFPerfTrace. COARSE,
sessionID,
"addItem",
"<<
* a substate change
* a change to the ITEM_SALE_IN_PROGRESS flag in TransactionStatusProperties.
*
* If we get a substate change to EXPECTING_ITEM or EXPECTING_COUPON, we set another POSDataProvider
* property - TransactionStatusProperties.EXPECTING_ITEM - to remember that we had a state change.
* This is to work around an ACE bug where the substate change is received after the Unit-Of-Work.
*
* @param evt contains details of the event
*
*/
public void propertyChanged(AEFPropertyChangeEvent evt)
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter ACEItemEntryActionImpl.propertyChanged()");
log.trace(tempAEFMessage);
}
String newValue = (String)evt.getNewValue();
if (log.isDebugEnabled()) {
log.debug(new AEFMessage(sessionID, "ACEItemEntryActionImpl property changed, category=" + evt.getCategoryName()
+ ", property=" + evt.getPropertyName() + ", newValue=" + newValue));
}
if (evt.getCategoryName().equals(TransactionStatusProperties.CATEGORY)) {
if (newValue.equalsIgnoreCase("true")) {
// Item was added, set the POSDataProvider property. This allows us to remember that the
// item was actually sold, because we may miss the change to the "itemSaleInProgress" flag
// if we're in the middle of handling error conditions
try {
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_ITEM_ADDED,
"true");
} catch (RemoteException re) { /* Can't get here. */ }
}
} else { // must be a change to the substate
if (newValue.equals(Substate.getSubstate("EXPECTING_ITEM")) ||
newValue.equals(Substate.getSubstate("EXPECTING_COUPON")))
{
// Credit was approved, set the POSDataProvider property.
try
{
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.EXPECTING_ITEM,
"true");
}
catch (RemoteException re) { /* Can't get here. */ }
}
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit ACEItemEntryActionImpl.propertyChanged()");
log.trace(tempAEFMessage);
}
}
/* Instance Variables */
protected AEFAction theKeySequenceAction;
protected ItemIdentifier theItemID;
protected String theQty = null;
protected String thePrice = null;
protected String theWeight = null;
protected String theDealPrice = null;
protected String theDealQuantity = null;
protected ConditionLock theLock;
protected ObjectDetector theDetector;
protected int theInstanceNumber;
protected static Vector theValidSubstates;
private static Log log = LogFactory.getLog(ACEItemEntryActionImpl.class);
}