/* * 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: * <br>AEFConst.INVALID_ARGUMENT, AEFConst.WEIGHT_AND_QUANTITY_MUTUALLY_EXCLUSIVE * <br>AEFConst.APPLICATION_NOT_IN_PROPER_STATE, AEFConst.APPLICATION_NOT_IN_PROPER_SUBSTATE * <br><a href="../commonerrorcodes.html">Common Errors</a> */ 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", "<<<Detected item from application."); } if (log.isTraceEnabled()) { tempAEFMessage.setMessage("-Exit ACEItemEntryActionImpl.waitForItem()"); log.trace(tempAEFMessage); } return retVal; } /** * A AEF POSDataProvider property was updated. We have registered for two different types of events: * <ul> * <li>a substate change</li> * <li>a change to the ITEM_SALE_IN_PROGRESS flag in TransactionStatusProperties. * </ul> * 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); }