[ 
https://issues.apache.org/jira/browse/OFBIZ-3557?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13447316#comment-13447316
 ] 

Markus M. May edited comment on OFBIZ-3557 at 9/4/12 2:29 AM:
--------------------------------------------------------------

We have now implemented the Service "invoiceSequenceEnforced" with a small 
Java-Method, so that the Transaction starts in the method and ends after 
committing the change on the DB. Seems to work now ;-)

{code:title=InvoiceServices.java|borderStyle=solid}
  // service to create a new invoiceId in a small transaction, so that the ids 
are updated correctly
  public static Map<String, Object> invoiceSequenceEnforced(DispatchContext 
dctx, Map<String, Object> context) throws GenericServiceException {
    Delegator delegator = dctx.getDelegator();
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Locale locale = (Locale) context.get("locale");

    String partyId = (String) context.get("partyId");
    Transaction parentTransaction = null;
    Long lastInvoiceNumber = null;

    try {
      // Enforce a new Transaction
      parentTransaction = TransactionUtil.suspend();
      if (TransactionUtil.isTransactionInPlace()) {
        throw new GenericTransactionException("In service " + module + " 
transaction is still in place after suspend, status is " + 
TransactionUtil.getStatusString());
      }

      // now start a new transaction
      boolean beganTrans = TransactionUtil.begin(0);

      GenericValue partyAcctgPreference = 
delegator.findOne("PartyAcctgPreference", false, UtilMisc.toMap("partyId", 
partyId));

      lastInvoiceNumber = partyAcctgPreference.getLong("lastInvoiceNumber");

      if (UtilValidate.isNotEmpty(lastInvoiceNumber)) {
        lastInvoiceNumber = lastInvoiceNumber + 1L;
      } else {
        lastInvoiceNumber = 1L;
      }

      Debug.logInfo("generated new InvoiceNumber: " + lastInvoiceNumber, 
module);
      partyAcctgPreference.set("lastInvoiceNumber", lastInvoiceNumber);
      partyAcctgPreference.store();

      TransactionUtil.commit();

    } catch (GenericEntityException e) {
      Debug.logError (e, "Entity/data problem creating invoiceId: " + 
e.toString(), module);
      return ServiceUtil.returnError(UtilProperties.getMessage(resource,
        "AccountingEntityDataProblemCreatingInvoiceFromOrderItems",
        UtilMisc.toMap("reason", e.toString()), locale));
    } finally {
      // resume the parent transaction
      if (parentTransaction != null) {
        try {
          TransactionUtil.resume(parentTransaction);
        } catch (GenericTransactionException ite) {
          Debug.logWarning(ite, "Transaction error, not resumed", module);
          throw new GenericServiceException("Resume transaction exception, see 
logs");
        }
      }
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("invoiceId", lastInvoiceNumber);
    return result;
  }
{code}

And in the service-defition:

{code:title=sales_invoice.xml|borderStyle=solid}
    <service name="invoiceSequence-enforced" engine="java"
             location="org.ofbiz.accounting.invoice.InvoiceServices" 
invoke="invoiceSequenceEnforced">
      <attribute name="partyAcctgPreference" 
type="org.ofbiz.entity.GenericValue" mode="IN"/>

      <attribute name="partyId" type="String" mode="IN" optional="false"/>
      <attribute name="invoiceId" type="Long" mode="OUT"/>
    </service>
{code}

Up until now, this seems to work fine. It still has the above mentioned 
problem, that a sequence number is wasted if an invoice creation is rolled 
back, though. Therefor this issue is still marked as open, this is just a minor 
workaround.

                
      was (Author: m...@apache.org):
    We have now implemented the Service "invoiceSequenceEnforced" with a small 
Java-Method, so that the Transaction starts in the method and ends after 
committing the change on the DB. Seems to work now ;-)

{code:title=InvoiceServices.java|borderStyle=solid}
  // service to create a new invoiceId in a small transaction, so that the ids 
are updated correctly
  public static Map<String, Object> invoiceSequenceEnforced(DispatchContext 
dctx, Map<String, Object> context) throws GenericServiceException {
    Delegator delegator = dctx.getDelegator();
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Locale locale = (Locale) context.get("locale");

    String partyId = (String) context.get("partyId");
    Transaction parentTransaction = null;
    Long lastInvoiceNumber = null;

    try {
      // Enforce a new Transaction
      parentTransaction = TransactionUtil.suspend();
      if (TransactionUtil.isTransactionInPlace()) {
        throw new GenericTransactionException("In service " + module + " 
transaction is still in place after suspend, status is " + 
TransactionUtil.getStatusString());
      }

      // now start a new transaction
      boolean beganTrans = TransactionUtil.begin(0);

      GenericValue partyAcctgPreference = 
delegator.findOne("PartyAcctgPreference", false, UtilMisc.toMap("partyId", 
partyId));

      lastInvoiceNumber = partyAcctgPreference.getLong("lastInvoiceNumber");

      if (UtilValidate.isNotEmpty(lastInvoiceNumber)) {
        lastInvoiceNumber = lastInvoiceNumber + 1L;
      } else {
        lastInvoiceNumber = 1L;
      }

      Debug.logInfo("generated new InvoiceNumber: " + lastInvoiceNumber, 
module);
      partyAcctgPreference.set("lastInvoiceNumber", lastInvoiceNumber);
      partyAcctgPreference.store();

      TransactionUtil.commit();

    } catch (GenericEntityException e) {
      Debug.logError (e, "Entity/data problem creating invoiceId: " + 
e.toString(), module);
      return ServiceUtil.returnError(UtilProperties.getMessage(resource,
        "AccountingEntityDataProblemCreatingInvoiceFromOrderItems",
        UtilMisc.toMap("reason", e.toString()), locale));
    } finally {
      // resume the parent transaction
      if (parentTransaction != null) {
        try {
          TransactionUtil.resume(parentTransaction);
        } catch (GenericTransactionException ite) {
          Debug.logWarning(ite, "Transaction error, not resumed", module);
          throw new GenericServiceException("Resume transaction exception, see 
logs");
        }
      }
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("invoiceId", lastInvoiceNumber);
    return result;
  }
{code}

And in the service-defition:

{code title=sales_invoice.xml|borderStyle=solid}
    <service name="invoiceSequence-enforced" engine="java"
             location="org.ofbiz.accounting.invoice.InvoiceServices" 
invoke="invoiceSequenceEnforced">
      <attribute name="partyAcctgPreference" 
type="org.ofbiz.entity.GenericValue" mode="IN"/>

      <attribute name="partyId" type="String" mode="IN" optional="false"/>
      <attribute name="invoiceId" type="Long" mode="OUT"/>
    </service>
{code}

Up until now, this seems to work fine. It still has the above mentioned 
problem, that a sequence number is wasted if an invoice creation is rolled 
back, though. Therefor this issue is still marked as open, this is just a minor 
workaround.

                  
> Enforced sequence does not work with concurrent access
> ------------------------------------------------------
>
>                 Key: OFBIZ-3557
>                 URL: https://issues.apache.org/jira/browse/OFBIZ-3557
>             Project: OFBiz
>          Issue Type: Bug
>          Components: framework
>    Affects Versions: Release Branch 09.04, SVN trunk
>            Reporter: Wickersheimer Jeremy
>         Attachments: OFBIZ-3557-1.patch, OFBIZ-3557-2.patch
>
>
> There is a fundamental issue with enforced sequences (for orders, invoices, 
> etc ..) and concurrency.
> For example if two users are creating an order at the same time one of them 
> will see the creation fail with a PK error. The problem is that the 
> "getNextXXXId" rely on the party accounting preference entity, but there is 
> absolutely no guarantee that the last number in the sequence gets updated 
> before another service can read it.
> This is at best very annoying when used only internally but may be 
> unpractical for e-commerce sites.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to