org.nightlabs.jfire.trade.Article.java Source code

Java tutorial

Introduction

Here is the source code for org.nightlabs.jfire.trade.Article.java

Source

/* *****************************************************************************
 * JFire - it's hot - Free ERP System - http://jfire.org                       *
 * Copyright (C) 2004-2005 NightLabs - http://NightLabs.org                    *
 *                                                                             *
 * This library is free software; you can redistribute it and/or               *
 * modify it under the terms of the GNU Lesser General Public                  *
 * License as published by the Free Software Foundation; either                *
 * version 2.1 of the License, or (at your option) any later version.          *
 *                                                                             *
 * This library is distributed in the hope that it will be useful,             *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of              *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU           *
 * Lesser General Public License for more details.                             *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public            *
 * License along with this library; if not, write to the                       *
 *     Free Software Foundation, Inc.,                                         *
 *     51 Franklin St, Fifth Floor,                                            *
 *     Boston, MA  02110-1301  USA                                             *
 *                                                                             *
 * Or get it online :                                                          *
 *     http://opensource.org/licenses/lgpl-license.php                         *
 *                                                                             *
 *                                                                             *
 ******************************************************************************/

package org.nightlabs.jfire.trade;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jdo.JDODetachedFieldAccessException;
import javax.jdo.JDOHelper;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.JDOUserException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.Column;
import javax.jdo.annotations.FetchGroup;
import javax.jdo.annotations.FetchGroups;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.PersistenceModifier;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.annotations.Queries;
import javax.jdo.annotations.Version;
import javax.jdo.annotations.VersionStrategy;
import javax.jdo.listener.AttachCallback;
import javax.jdo.listener.DeleteCallback;
import javax.jdo.listener.DetachCallback;
import javax.jdo.listener.StoreCallback;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.nightlabs.jdo.NLJDOHelper;
import org.nightlabs.jdo.ObjectIDUtil;
import org.nightlabs.jfire.accounting.Currency;
import org.nightlabs.jfire.accounting.Invoice;
import org.nightlabs.jfire.accounting.Tariff;
import org.nightlabs.jfire.accounting.id.InvoiceID;
import org.nightlabs.jfire.accounting.priceconfig.IPackagePriceConfig;
import org.nightlabs.jfire.idgenerator.IDGenerator;
import org.nightlabs.jfire.organisation.LocalOrganisation;
import org.nightlabs.jfire.security.SecurityReflector;
import org.nightlabs.jfire.security.User;
import org.nightlabs.jfire.store.DeliveryNote;
import org.nightlabs.jfire.store.Product;
import org.nightlabs.jfire.store.ProductType;
import org.nightlabs.jfire.store.ReceptionNote;
import org.nightlabs.jfire.store.id.DeliveryNoteID;
import org.nightlabs.jfire.store.id.ReceptionNoteID;
import org.nightlabs.jfire.trade.id.ArticleID;
import org.nightlabs.jfire.trade.id.OfferID;
import org.nightlabs.jfire.trade.id.OrderID;
import org.nightlabs.jfire.transfer.id.AnchorID;
import org.nightlabs.util.Util;
import org.nightlabs.util.reflect.ReflectUtil;

/**
 * An instance of <tt>Article</tt> occurs in the context of a {@link Segment}. It represents a
 * {@link Product} or a {@link ProductType} within an {@link Order}, an {@link Offer},
 * an {@link org.nightlabs.jfire.accounting.Invoice} or a {@link org.nightlabs.jfire.store.DeliveryNote}.
 * So you could understand an <tt>Article</tt> as a line on your <tt>Invoice</tt>, but depending
 * on the GUI, a whole <tt>Segment</tt> could be rendered differently, not showing the <tt>Article</tt>s
 * in detail.
 *
 * @version $Revision$ - $Date$
 * @author Marco Schulze - marco at nightlabs dot de
 * @author Alexander Bieber <alex[AT]nightlabs[DOT]de>
 *
 * @jdo.persistence-capable
 *      identity-type="application"
 *      objectid-class="org.nightlabs.jfire.trade.id.ArticleID"
 *      detachable="true"
 *      table="JFireTrade_Article"
 *
 * @jdo.version strategy="version-number"
 *
 * @jdo.inheritance strategy="new-table"
 *
 * @jdo.create-objectid-class field-order="organisationID, articleID"
 *
 * @jdo.fetch-group name="Article.articleLocal" fields="articleLocal"
 * @jdo.fetch-group name="Article.segment" fields="segment"
 * @jdo.fetch-group name="Article.currency" fields="currency"
 * @jdo.fetch-group name="Article.productType" fields="productType"
 * @jdo.fetch-group name="Article.product" fields="product"
 * @jdo.fetch-group name="Article.tariff" fields="tariff"
 * @jdo.fetch-group name="Article.price" fields="price"
 * @jdo.fetch-group name="Article.reversedArticle" fields="reversedArticle"
 * @jdo.fetch-group name="Article.reversingArticle" fields="reversingArticle"
 * @jdo.fetch-group name="Article.order" fields="order"
 * @jdo.fetch-group name="Article.offer" fields="offer"
 * @jdo.fetch-group name="Article.invoice" fields="invoice"
 * @jdo.fetch-group name="Article.deliveryNote" fields="deliveryNote"
 * @jdo.fetch-group name="Article.receptionNote" fields="receptionNote"
 * @!jdo.fetch-group name="Article.delivery" fields="delivery"
 *
 * @jdo.fetch-group name="Order.articles" fields="order"
 * @jdo.fetch-group name="Offer.articles" fields="offer"
 * @jdo.fetch-group name="Invoice.articles" fields="invoice"
 * @jdo.fetch-group name="DeliveryNote.articles" fields="deliveryNote"
 * @jdo.fetch-group name="ReceptionNote.articles" fields="receptionNote"
 *
 * @jdo.fetch-group name="FetchGroupsTrade.articleInOrderEditor" fields="articleLocal, segment, productType, product, tariff, price"
 * @jdo.fetch-group name="FetchGroupsTrade.articleInOfferEditor" fields="articleLocal, segment, productType, product, tariff, price"
 * @jdo.fetch-group name="FetchGroupsTrade.articleInInvoiceEditor" fields="articleLocal, segment, productType, product, tariff, price"
 * @jdo.fetch-group name="FetchGroupsTrade.articleInDeliveryNoteEditor" fields="articleLocal, segment, productType, product, tariff, price"
 *
 * @jdo.fetch-group
 *       name="FetchGroupsTrade.articleCrossTradeReplication"
 *       fields="
 *          order, offer, invoice, deliveryNote, receptionNote,
 *          price, currency, tariff, reversedArticle, reversingArticle,
 *          segment, productType, product, createUser
 *       "
 *
 * @!jdo.fetch-group name="FetchGroupsTrade.articleInOrderEditor" fields="segment, productType, product, tariff, price, order, offer, invoice, deliveryNote"
 * @!jdo.fetch-group name="FetchGroupsTrade.articleInOfferEditor" fields="segment, productType, product, tariff, price, order, invoice, deliveryNote"
 * @!jdo.fetch-group name="FetchGroupsTrade.articleInInvoiceEditor" fields="segment, productType, product, tariff, price, order, offer, deliveryNote"
 * @!jdo.fetch-group name="FetchGroupsTrade.articleInDeliveryNoteEditor" fields="segment, productType, product, tariff, price, order, offer, invoice"
 *
 * @jdo.query
 *      name="getAllocatedArticleByOfferAndProduct"
 *      query="SELECT UNIQUE WHERE this.offer == :offer && this.product == :product && this.allocated"
 *
 * @jdo.query
 *      name="getArticlesByProduct"
 *      query="SELECT WHERE this.product == :product"
 */
@PersistenceCapable(objectIdClass = ArticleID.class, identityType = IdentityType.APPLICATION, detachable = "true", table = "JFireTrade_Article")
@Version(strategy = VersionStrategy.VERSION_NUMBER)
@FetchGroups({ @FetchGroup(name = Article.FETCH_GROUP_ARTICLE_LOCAL, members = @Persistent(name = "articleLocal")),
        @FetchGroup(name = Article.FETCH_GROUP_SEGMENT, members = @Persistent(name = "segment")),
        @FetchGroup(name = Article.FETCH_GROUP_CURRENCY, members = @Persistent(name = "currency")),
        @FetchGroup(name = Article.FETCH_GROUP_PRODUCT_TYPE, members = @Persistent(name = "productType")),
        @FetchGroup(name = Article.FETCH_GROUP_PRODUCT, members = @Persistent(name = "product")),
        @FetchGroup(name = Article.FETCH_GROUP_TARIFF, members = @Persistent(name = "tariff")),
        @FetchGroup(name = Article.FETCH_GROUP_PRICE, members = @Persistent(name = "price")),
        @FetchGroup(name = Article.FETCH_GROUP_REVERSED_ARTICLE, members = @Persistent(name = "reversedArticle")),
        @FetchGroup(name = Article.FETCH_GROUP_REVERSING_ARTICLE, members = @Persistent(name = "reversingArticle")),
        @FetchGroup(name = Article.FETCH_GROUP_ORDER, members = @Persistent(name = "order")),
        @FetchGroup(name = Article.FETCH_GROUP_OFFER, members = @Persistent(name = "offer")),
        @FetchGroup(name = Article.FETCH_GROUP_INVOICE, members = @Persistent(name = "invoice")),
        @FetchGroup(name = Article.FETCH_GROUP_DELIVERY_NOTE, members = @Persistent(name = "deliveryNote")),
        @FetchGroup(name = Article.FETCH_GROUP_RECEPTION_NOTE, members = @Persistent(name = "receptionNote")),
        @FetchGroup(name = "Order.articles", members = @Persistent(name = "order")),
        @FetchGroup(name = "Offer.articles", members = @Persistent(name = "offer")),
        @FetchGroup(name = "Invoice.articles", members = @Persistent(name = "invoice")),
        @FetchGroup(name = "DeliveryNote.articles", members = @Persistent(name = "deliveryNote")),
        @FetchGroup(name = "ReceptionNote.articles", members = @Persistent(name = "receptionNote")),
        @FetchGroup(name = "FetchGroupsTrade.articleInOrderEditor", members = { @Persistent(name = "articleLocal"),
                @Persistent(name = "segment"), @Persistent(name = "productType"), @Persistent(name = "product"),
                @Persistent(name = "tariff"), @Persistent(name = "price"), @Persistent(name = "endCustomer") }),
        @FetchGroup(name = "FetchGroupsTrade.articleInOfferEditor", members = { @Persistent(name = "articleLocal"),
                @Persistent(name = "segment"), @Persistent(name = "productType"), @Persistent(name = "product"),
                @Persistent(name = "tariff"), @Persistent(name = "price"), @Persistent(name = "endCustomer") }),
        @FetchGroup(name = "FetchGroupsTrade.articleInInvoiceEditor", members = {
                @Persistent(name = "articleLocal"), @Persistent(name = "segment"),
                @Persistent(name = "productType"), @Persistent(name = "product"), @Persistent(name = "tariff"),
                @Persistent(name = "price"), @Persistent(name = "endCustomer") }),
        @FetchGroup(name = "FetchGroupsTrade.articleInDeliveryNoteEditor", members = {
                @Persistent(name = "articleLocal"), @Persistent(name = "segment"),
                @Persistent(name = "productType"), @Persistent(name = "product"), @Persistent(name = "tariff"),
                @Persistent(name = "price"), @Persistent(name = "endCustomer") }),
        @FetchGroup(name = "FetchGroupsTrade.articleCrossTradeReplication", members = { @Persistent(name = "order"),
                @Persistent(name = "offer"), @Persistent(name = "invoice"), @Persistent(name = "deliveryNote"),
                @Persistent(name = "receptionNote"), @Persistent(name = "price"), @Persistent(name = "currency"),
                @Persistent(name = "tariff"), @Persistent(name = "reversedArticle"),
                @Persistent(name = "reversingArticle"), @Persistent(name = "segment"),
                @Persistent(name = "productType"), @Persistent(name = "product"),
                @Persistent(name = "createUser") }),
        @FetchGroup(name = Article.FETCH_GROUP_END_CUSTOMER, members = @Persistent(name = "endCustomer")) })
@Queries({
        @javax.jdo.annotations.Query(name = "getAllocatedArticleByOfferAndProduct", value = "SELECT UNIQUE WHERE this.offer == :offer && this.product == :product && this.allocated"),
        @javax.jdo.annotations.Query(name = "getArticlesByProduct", value = "SELECT WHERE this.product == :product") })
@Inheritance(strategy = InheritanceStrategy.NEW_TABLE)
public class Article implements Serializable, DeleteCallback, AttachCallback, DetachCallback, StoreCallback {
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(Article.class);

    public static final String FETCH_GROUP_ARTICLE_LOCAL = "Article.articleLocal";
    public static final String FETCH_GROUP_SEGMENT = "Article.segment";
    public static final String FETCH_GROUP_CURRENCY = "Article.currency";
    public static final String FETCH_GROUP_PRODUCT_TYPE = "Article.productType";
    public static final String FETCH_GROUP_PRODUCT = "Article.product";
    public static final String FETCH_GROUP_PRICE = "Article.price";
    public static final String FETCH_GROUP_REVERSED_ARTICLE = "Article.reversedArticle";
    public static final String FETCH_GROUP_REVERSING_ARTICLE = "Article.reversingArticle";
    public static final String FETCH_GROUP_ORDER = "Article.order";
    public static final String FETCH_GROUP_OFFER = "Article.offer";
    public static final String FETCH_GROUP_INVOICE = "Article.invoice";
    public static final String FETCH_GROUP_DELIVERY_NOTE = "Article.deliveryNote";
    public static final String FETCH_GROUP_RECEPTION_NOTE = "Article.receptionNote";
    public static final String FETCH_GROUP_TARIFF = "Article.tariff";
    //   public static final String FETCH_GROUP_DELIVERY = "Article.delivery";
    public static final String FETCH_GROUP_END_CUSTOMER = "Article.endCustomer";

    // the following fetch-groups are virtual and processed in the detach callback
    // all of them are included virtually in the FetchGroupsTrade.articleInXXX fetch-groups
    public static final String FETCH_GROUP_ORDER_ID = "Article.orderID";
    public static final String FETCH_GROUP_OFFER_ID = "Article.offerID";
    public static final String FETCH_GROUP_INVOICE_ID = "Article.invoiceID";
    public static final String FETCH_GROUP_DELIVERY_NOTE_ID = "Article.deliveryNoteID";
    public static final String FETCH_GROUP_RECEPTION_NOTE_ID = "Article.receptionNoteID";
    public static final String FETCH_GROUP_REVERSED_ARTICLE_ID = "Article.reversedArticleID";
    public static final String FETCH_GROUP_REVERSING_ARTICLE_ID = "Article.reversingArticleID";
    public static final String FETCH_GROUP_VENDOR_ID = "Article.vendorID";
    public static final String FETCH_GROUP_CUSTOMER_ID = "Article.customerID";
    public static final String FETCH_GROUP_END_CUSTOMER_ID = "Article.endCustomerID";

    //   /**
    //    * @param pm The {@link PersistenceManager} used to access the datastore.
    //    * @param offerID The ID of the {@link Offer}.
    //    * @param productID The ID of the {@link Product}.
    //    * @return Either <code>null</code>, if the specified IDs don't match an {@link Article} or the {@link Article} which references the
    //    *      specified <code>Product</code> within the specified <code>Offer</code>. Note, that <code>null</code> is returned, too, if no
    //    *      <code>Offer</code> with the specified <code>offerID</code> exists, or no <code>Product</code> with the specified <code>productID</code> exists.
    //    */
    //   public static Article getArticle(PersistenceManager pm, OfferID offerID, ProductID productID)
    //   {
    //      pm.getExtent(Offer.class); pm.getExtent(Product.class);
    //      Offer offer; Product product;
    //      try {
    //         offer = (Offer) pm.getObjectById(offerID);
    //         product = (Product) pm.getObjectById(productID);
    //      } catch (JDOObjectNotFoundException x) {
    //         return null;
    //      }
    //      return getArticle(pm, offer, product);
    //   }

    /**
     * @param pm The {@link PersistenceManager} used to access the datastore.
     * @param offer The {@link Offer} containing the searched <code>Article</code>.
     * @param product The {@link Product} referenced by the searched <code>Article</code>.
     * @return Either <code>null</code>, if no allocated {@link Article} can be found matching the
     *      specified <code>Product</code> within the specified <code>Offer</code>.
     */
    public static Article getAllocatedArticle(PersistenceManager pm, Offer offer, Product product) {
        Query q = pm.newNamedQuery(Article.class, "getAllocatedArticleByOfferAndProduct");
        return (Article) q.execute(offer, product);
    }

    /**
     * Returns the Set of {@link Article}s where the given {@link Product} is referenced
     * or an empty Set, if no {@link Article}s can be found matching the
     * specified <code>Product</code>.
     *
     * @param pm The {@link PersistenceManager} used to access the datastore.
     * @param product The {@link Product} referenced by the searched <code>Article</code>s.
     * @return Either <code>null</code>, if no {@link Article}s can be found matching the
     *      specified <code>Product</code>.
     */
    @SuppressWarnings("unchecked")
    public static Set<Article> getArticles(PersistenceManager pm, Product product) {
        Query q = pm.newNamedQuery(Article.class, "getArticlesByProduct");
        return new HashSet<Article>((Collection<Article>) q.execute(product));
    }

    public static Map<Class<? extends ProductType>, Set<Article>> getProductTypeClass2articleSetMapFromArticleContainers(
            Collection<? extends ArticleContainer> articleContainers) {
        Map<Class<? extends ProductType>, Set<Article>> result = new HashMap<Class<? extends ProductType>, Set<Article>>();
        for (ArticleContainer articleContainer : articleContainers) {
            populateProductTypeClass2articleSetMap(result, articleContainer.getArticles());
        }
        return result;
    }

    public static Map<Class<? extends ProductType>, Set<Article>> getProductTypeClass2articleSetMap(
            Collection<? extends Article> articles) {
        Map<Class<? extends ProductType>, Set<Article>> result = new HashMap<Class<? extends ProductType>, Set<Article>>();
        populateProductTypeClass2articleSetMap(result, articles);
        return result;
    }

    public static void populateProductTypeClass2articleSetMap(
            Map<Class<? extends ProductType>, Set<Article>> productTypeClass2articleSetMap,
            Collection<? extends Article> articles) {
        if (productTypeClass2articleSetMap == null)
            throw new IllegalArgumentException("productTypeClass2articleSetMap is null!");

        for (Article article : articles) {
            Class<? extends ProductType> clazz = article.getProductType().getClass();
            Set<Article> articleSet = productTypeClass2articleSetMap.get(clazz);
            if (articleSet == null) {
                articleSet = new HashSet<Article>();
                productTypeClass2articleSetMap.put(clazz, articleSet);
            }
            articleSet.add(article);
        }
    }

    /**
     * @jdo.field primary-key="true"
     * @jdo.column length="100"
     */
    @PrimaryKey
    @Column(length = 100)
    private String organisationID;

    /**
     * @jdo.field primary-key="true"
     */
    @PrimaryKey
    private long articleID;

    public static long createArticleID() {
        return IDGenerator.nextID(Article.class);
    }

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private String primaryKey;

    /**
     * @jdo.field persistence-modifier="persistent" mapped-by="article"
     */
    @Persistent(mappedBy = "article", persistenceModifier = PersistenceModifier.PERSISTENT)
    private ArticleLocal articleLocal;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Segment segment;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Order order;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Offer offer;

    /**
     * An <tt>Article</tt> can only be part of none or exactly one <tt>Invoice</tt>.
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Invoice invoice = null;

    /**
     * An <tt>Article</tt> can only be in one <tt>DeliveryNote</tt> (or in none).
     * If the <tt>Article</tt> shall be returned, a new reverse-<tt>Article</tt>
     * needs to be created. Hence, <tt>Invoice</tt> and <tt>DeliveryNote</tt> handling
     * is quite the same.
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private DeliveryNote deliveryNote = null;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private ReceptionNote receptionNote = null;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private ProductType productType;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Product product = null;

    /**
     * Creation date of this Invoice.
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Date createDT;

    /**
     * The user who created this Invoice.
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private User createUser = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private OrderID orderID = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean orderID_detached = false;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private OfferID offerID = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean offerID_detached = false;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private InvoiceID invoiceID = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean invoiceID_detached = false;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private DeliveryNoteID deliveryNoteID = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean deliveryNoteID_detached = false;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private ReceptionNoteID receptionNoteID = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean receptionNoteID_detached = false;

    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private LegalEntity endCustomer;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private AnchorID endCustomerID = null;

    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean endCustomerID_detached = false;

    public OrderID getOrderID() {
        if (orderID == null && !orderID_detached)
            orderID = (OrderID) JDOHelper.getObjectId(order);

        return orderID;
    }

    public OfferID getOfferID() {
        if (offerID == null && !offerID_detached)
            offerID = (OfferID) JDOHelper.getObjectId(offer);

        return offerID;
    }

    public InvoiceID getInvoiceID() {
        if (invoiceID == null && !invoiceID_detached)
            invoiceID = (InvoiceID) JDOHelper.getObjectId(invoice);

        return invoiceID;
    }

    public DeliveryNoteID getDeliveryNoteID() {
        if (deliveryNoteID == null && !deliveryNoteID_detached)
            deliveryNoteID = (DeliveryNoteID) JDOHelper.getObjectId(deliveryNote);

        return deliveryNoteID;
    }

    public ReceptionNoteID getReceptionNoteID() {
        if (receptionNoteID == null && !receptionNoteID_detached)
            receptionNoteID = (ReceptionNoteID) JDOHelper.getObjectId(receptionNote);

        return receptionNoteID;
    }

    public ArticleID getReversedArticleID() {
        if (reversedArticleID == null && !reversedArticleID_detached)
            reversedArticleID = (ArticleID) JDOHelper.getObjectId(reversedArticle);

        return reversedArticleID;
    }

    public ArticleID getReversingArticleID() {
        if (reversingArticleID == null && !reversingArticleID_detached)
            reversingArticleID = (ArticleID) JDOHelper.getObjectId(reversingArticle);

        return reversingArticleID;
    }

    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private AnchorID vendorID = null;
    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean vendorID_detached = false;

    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private AnchorID customerID = null;
    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean customerID_detached = false;

    /**
     * @return Returns the ID of the vendor, which is either obtained via {@link Order#getVendorID()} or
     *      manually detached in {@link #jdoPostDetach(Object)}.
     */
    public AnchorID getVendorID() {
        if (vendorID == null && !vendorID_detached)
            vendorID = order.getVendorID();

        return vendorID;
    }

    /**
     * @return Returns the ID of the customer, which is either obtained via {@link Order#getCustomerID()} or
     *      manually detached in {@link #jdoPostDetach(Object)}.
     */
    public AnchorID getCustomerID() {
        if (customerID == null && !customerID_detached)
            customerID = order.getCustomerID();

        return customerID;
    }

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Currency currency;

    /**
     * @jdo.field persistence-modifier="persistent" dependent="true"
     */
    @Persistent(dependent = "true", persistenceModifier = PersistenceModifier.PERSISTENT)
    private ArticlePrice price = null;

    /**
     * This is set according to the result of PriceConfig.isDependentOnOffer().
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean priceDependentOnOffer;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Tariff tariff = null;

    /**
     * Whether or not this Article has already been reversed. This is <code>true</code>,
     * if {@link #reversingArticle} is assigned.
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean reversed = false;

    /**
     * Whether or not this Article reverses another one. This is <code>true</code>,
     * if {@link #reversedArticle} is assigned.
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean reversing = false;

    /**
     * This flag is set <code>true</code>, if the reversing offer is somehow cancelled (rejected, expired, revoked, aborted etc.).
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean isReversingAborted = false;

    /**
     * If this <tt>Article</tt> reverses (i.e. refunds) a previously sold Article, this member points
     * to the original Article.
     *
     * @see #reverse
     * @see #reversingArticle
     *
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Article reversedArticle = null;

    /**
     * If this <tt>Article</tt> is reversed, the new Article (which reverses this one),
     * will be referenced here.
     *
     * @see #reversedArticle
     *
     * @jdo.field persistence-modifier="persistent"
     * @! mapped-by="reversedArticle"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Article reversingArticle = null;

    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private ArticleID reversedArticleID = null;
    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean reversedArticleID_detached = false;

    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private ArticleID reversingArticleID = null;
    /**
     * @jdo.field persistence-modifier="none"
     */
    @Persistent(persistenceModifier = PersistenceModifier.NONE)
    private boolean reversingArticleID_detached = false;

    //   /**
    //    * @jdo.field persistence-modifier="persistent"
    //    */
    //   private Article referencedArticle = null;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean allocationPending = false;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean releasePending = false;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean allocated = false;

    /**
     * This is usually <tt>null</tt> or the fully qualified class name of the root exception.
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="255"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    @Column(length = 255)
    private String allocationExceptionClass = null;

    /**
     * This is usually <tt>null</tt> or the exception message (no stacktrace) of the root exception.
     *
     * @jdo.field persistence-modifier="persistent" default-fetch-group="false"
     * @jdo.column sql-type="CLOB"
     */
    @Persistent(defaultFetchGroup = "false", persistenceModifier = PersistenceModifier.PERSISTENT)
    @Column(sqlType = "CLOB")
    private String allocationExceptionMessage = null;

    /**
     * This is usually <tt>null</tt> or the result of {@link StackTraceElement#toString()}
     * specifying where the root exception happened.
     *
     * @jdo.field persistence-modifier="persistent" default-fetch-group="false"
     * @jdo.column sql-type="CLOB"
     */
    @Persistent(defaultFetchGroup = "false", persistenceModifier = PersistenceModifier.PERSISTENT)
    @Column(sqlType = "CLOB")
    private String allocationExceptionStackTrace = null;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean allocationAbandoned = false;

    /**
     * This is usually <tt>null</tt> or the fully qualified class name of the root exception.
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="255"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    @Column(length = 255)
    private String releaseExceptionClass = null;

    /**
     * This is usually <tt>null</tt> or the exception message (no stacktrace) of the root exception.
     *
     * @jdo.field persistence-modifier="persistent" default-fetch-group="false"
     * @jdo.column sql-type="CLOB"
     */
    @Persistent(defaultFetchGroup = "false", persistenceModifier = PersistenceModifier.PERSISTENT)
    @Column(sqlType = "CLOB")
    private String releaseExceptionMessage = null;

    /**
     * This is usually <tt>null</tt> or the result of {@link StackTraceElement#toString()}
     * specifying where the root exception happened.
     *
     * @jdo.field persistence-modifier="persistent" default-fetch-group="false"
     * @jdo.column sql-type="CLOB"
     */
    @Persistent(defaultFetchGroup = "false", persistenceModifier = PersistenceModifier.PERSISTENT)
    @Column(sqlType = "CLOB")
    private String releaseExceptionStackTrace = null;

    /**
     * @jdo.field persistence-modifier="persistent"
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private boolean releaseAbandoned = false;

    /**
     * This field describes the estimated delivery date for the article,
     * which can be determined in the offer.
     * This field may be null, if no delivery date was specified.
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Date deliveryDateOffer = null;

    /**
     * This field describes the estimated delivery date for the article,
     * which can be determined in the delivery note.
     * This field may be null, if no delivery date was specified.
     */
    @Persistent(persistenceModifier = PersistenceModifier.PERSISTENT)
    private Date deliveryDateDeliveryNote = null;

    /**
     * @deprecated Only for JDO!
     */
    @Deprecated
    protected Article() {
    }

    /**
     * This method is used by all constructors to check common parameters and to
     * initialize the related fields.
     */
    protected void init(User user, Offer offer, Segment segment, long articleID) {
        if (offer == null)
            throw new NullPointerException("offer");

        if (segment == null)
            throw new NullPointerException("segment");

        if (articleID < 0)
            throw new IllegalArgumentException("articleID < 0!");

        this.createUser = user;
        this.createDT = new Date();
        this.offer = offer;
        this.order = offer.getOrder();
        this.segment = segment;
        this.organisationID = offer.getOrganisationID();
        this.articleID = articleID;
        this.primaryKey = getPrimaryKey(organisationID, articleID);
        this.currency = offer.getCurrency();
    }

    //   /**
    //    * This constructor creates a referencing article. This is used to cancel
    //    * an Invoice or a DeliveryNote. In this case, all Articles within the cancelled
    //    * Invoice/DeliveryNote are replaced by referencingArticles.
    //    *
    //    * @see #createReferencingArticle(User)
    //    */
    //   protected Article(User user, long articleID, Article referencedArticle)
    //   {
    //      init(user, referencedArticle.getOffer(), referencedArticle.getSegment(), articleID);
    //
    //      this.setReferencedArticle(referencedArticle);
    //      this.productType = referencedArticle.getProductType();
    //      this.product = referencedArticle.getProduct();
    //      this.tariff = referencedArticle.getTariff();
    //      this.price = referencedArticle.getPrice(); // the price is immutable, so we can share it
    //
    //      this.allocated = referencedArticle.isAllocated();
    //      this.allocationPending = referencedArticle.isAllocationPending();
    //      this.releasePending = referencedArticle.isReleasePending();
    //   }

    /**
     * This constructor is used to create a new Article which reverses a previously
     * sold one.
     * <p>
     * Do not use this constructor directly, but call
     * {@link Trader#reverseArticle(User, Offer, Article)}! <code>Trader</code>
     * delegates to {@link #reverseArticle(User, Offer, long)} which means, the
     * <code>reversedArticle</code> is in charge of creating its reversing <code>Article</code>.
     */
    protected Article(User user, Offer offer, long articleID, Article reversedArticle) {
        init(user, offer, reversedArticle.getSegment(), articleID);

        if (reversedArticle == null)
            throw new IllegalArgumentException("reversedArticle == null!");

        if (!this.offer.getOrder().getPrimaryKey().equals(reversedArticle.getOffer().getOrder().getPrimaryKey()))
            throw new IllegalArgumentException("An article can only be refunded within the same order!");

        if (reversedArticle.reversingArticle != null)
            throw new IllegalArgumentException("The passed article (" + reversedArticle.getPrimaryKey()
                    + ") cannot be reversed, because it has already been reversed before!");

        if (reversedArticle.reversedArticle != null)
            throw new IllegalArgumentException("The passed article (" + reversedArticle.getPrimaryKey()
                    + ") cannot be reversed, because it is itself reversing another Article!");

        // DataNucleus WORKAROUND: Maps/Collections that are populated in the constructor are not properly handled - their contents are not saved in the DB!
        PersistenceManager pm = NLJDOHelper.getThreadPersistenceManager(false);
        if (pm != null)
            pm.makePersistent(this);
        // end workaround

        this.setReversedArticle(reversedArticle);
        reversedArticle.setReversingArticle(this);
        this.productType = reversedArticle.getProductType();
        this.product = reversedArticle.getProduct();
        this.tariff = reversedArticle.getTariff();

        ArticlePrice tmpAP = reversedArticle.getPrice().createReversingArticlePrice();
        if (pm != null)
            tmpAP = pm.makePersistent(tmpAP);

        this.price = tmpAP;

        //      JDOHelper.makeDirty(this, "price"); // DataNucleus WORKAROUND: Without this, the price is not assigned (field is written as null to the DB). Marco.

        // copy status
        this.allocated = reversedArticle.isAllocated();
        this.allocationPending = reversedArticle.isAllocationPending();
        this.releasePending = reversedArticle.isReleasePending();

        // DataNucleus WORKAROUND: Without this, the price is not assigned (field is written as null to the DB) and other fields
        // are written incompletely/wrong, too. Marco.
        //      NLJDOHelper.makeDirtyAllFieldsRecursively(this); // This fails with an exception, because making things dirty with fully qualified name seems not to work while it is attached (simple names work) :-(
        // Since we don't need it recursively anyway, we use the following code instead
        workaround_makeFieldsDirty_noRecursion(this);
        workaround_handleArticlePriceRecursively(this.price);
        if (pm != null)
            pm.flush();
    }

    private void workaround_handleArticlePriceRecursively(ArticlePrice price) {
        workaround_makeFieldsDirty_noRecursion(price);
        for (ArticlePrice nestedPrice : price.getNestedArticlePrices())
            workaround_handleArticlePriceRecursively(nestedPrice);
    }

    private void workaround_makeFieldsDirty_noRecursion(Object o) {
        List<Field> fields = ReflectUtil.collectAllFields(o.getClass(), true);
        for (Field field : fields) {
            try {
                JDOHelper.makeDirty(o, field.getName());
            } catch (JDOUserException x) {
            } // silently ignore non-persistent fields causing an exception
        }
    }

    /**
     * This constructor is used to create an <tt>Article</tt> with a <tt>Product</tt>.
     * The product will not be allocated by this action! This must be done by outside logic.
     * <p>
     * Do not use this constructor directly, but use the {@link Trader} to create an
     * <tt>Article</tt>!
     * <p>
     * The new article will not yet have a price! This must be set afterwards to allow
     * subclassing of <tt>Article</tt> and making prices dependent on the new fields
     * of the subclass.
     */
    public Article(User user, Offer offer, Segment segment, long articleID, Product product, Tariff tariff) {
        this(user, offer, segment, articleID, product.getProductType(), product, tariff);
    }

    /**
     * This constructor is used to create an <tt>Article</tt> with a <tt>ProductType</tt>, means
     * the allocation of the <tt>Product</tt> will be done later (e.g. after the customer
     * accepted the <tt>Offer</tt>).
     * <p>
     * Do not use this constructor directly, but use the {@link Trader} to create an
     * <tt>Article</tt>!
     * <p>
     * The new article will not yet have a price! This must be set afterwards to allow
     * subclassing of <tt>Article</tt> and making prices dependent on the new fields
     * of the subclass.
     */
    public Article(User user, Offer offer, Segment segment, long articleID, ProductType productType,
            Tariff tariff) {
        this(user, offer, segment, articleID, productType, null, tariff);
    }

    /**
     * @param offer must not be null
     * @param segment must not be null
     * @param articleID must be >= 0
     * @param productType must not be null
     * @param product can be null
     * @param currency must not be null
     */
    protected Article(User user, Offer offer, Segment segment, long articleID, ProductType productType,
            Product product, Tariff tariff) {
        init(user, offer, segment, articleID);

        if (productType == null)
            throw new NullPointerException("productType");

        if (productType.isPackageInner()) // TODO wie machen wir das bei inter-organisations-handel? Wir MSSEN innerproducts handeln knnen!
            throw new IllegalArgumentException("productType \"" + productType.getPrimaryKey()
                    + "\" is an inner part of a package! Cannot make an article out of it!");

        IPackagePriceConfig packagePriceConfig = productType.getPackagePriceConfig();
        if (packagePriceConfig == null)
            throw new IllegalStateException(
                    "packagePriceConfig of productType \"" + productType.getPrimaryKey() + "\" is undefined!");

        this.productType = productType;
        this.product = product;
        this.tariff = tariff;

        //      PersistenceManager pm = JDOHelper.getPersistenceManager(offer);
        //      if (pm == null)
        //         throw new IllegalStateException("offer is currently not persistent! Can only create an article with a persistent offer.");
        //
        //      Accounting accounting = Accounting.getAccounting(pm);
        //      AccountingPriceConfig accountingPriceConfig = accounting.getAccountingPriceConfig();

        //      this.price = new ArticlePrice(
        //            packagePriceConfig.getArticlePrice(this),
        //            accounting.getOrganisationID(),
        //            accountingPriceConfig.getPriceConfigID(),
        //            accountingPriceConfig.createPriceID(), false);
        // The price is set after creation to allow subclassing of Article and making the
        // price dependent on new fields of the subclass.
    }

    /**
     * @return Returns the organisationID.
     */
    public String getOrganisationID() {
        return organisationID;
    }

    /**
     * @return Returns the articleID.
     */
    public long getArticleID() {
        return articleID;
    }

    /**
     * @return Returns the articleID converted to a String using {@link ObjectIDUtil#longObjectIDFieldToString(long)}.
     */
    public String getArticleIDAsString() {
        return ObjectIDUtil.longObjectIDFieldToString(articleID);
    }

    public static String getPrimaryKey(String organisationID, long articleID) {
        return organisationID + '/' + ObjectIDUtil.longObjectIDFieldToString(articleID);
    }

    public String getPrimaryKey() {
        return primaryKey;
    }

    /**
     * Returns the creation {@link Date} of this {@link Article}.
     *
     * @return The creation {@link Date} of this {@link Article}.
     */
    public Date getCreateDT() {
        return createDT;
    }

    /**
     * Returns the {@link User} who created this {@link Article}
     *
     * @return The {@link User} who created this {@link Article}
     */
    public User getCreateUser() {
        return createUser;
    }

    /**
     * @return Returns the productType.
     */
    public ProductType getProductType() {
        return productType;
    }

    /**
     * @param productType The productType to set.
     */
    protected void setProductType(ProductType productType) {
        this.productType = productType;
    }

    /**
     * @return Returns the product.
     */
    public Product getProduct() {
        return product;
    }

    /**
     * @return Returns the price.
     */
    public ArticlePrice getPrice() {
        return price;
    }

    /**
     * @param price The price to set.
     */
    public void setPrice(ArticlePrice price) {
        // WORKAROUND JPOX should remove dependent objects, but currently it doesn't
        if (this.price != null) {
            PersistenceManager pm = getPersistenceManager();
            if (pm == null)
                throw new IllegalStateException(
                        "I thought, this method would only be called in the server?! Marco.");

            ArticlePrice oldPrice = this.price;
            this.price = null;
            pm.deletePersistent(oldPrice);
        }

        this.price = price;
    }

    /**
     * @return Returns the priceDependentOnOffer.
     */
    public boolean isPriceDependentOnOffer() {
        return priceDependentOnOffer;
    }

    /**
     * @param priceDependentOnOffer The priceDependentOnOffer to set.
     */
    protected void setPriceDependentOnOffer(boolean priceDependentOnOffer) {
        this.priceDependentOnOffer = priceDependentOnOffer;
    }

    /**
     * @return Returns the offer.
     */
    public Offer getOffer() {
        return offer;
    }

    /**
     * @return Returns either <code>null</code> or the <code>Article</code> that is reversed by this instance.
     */
    public Article getReversedArticle() {
        return reversedArticle;
    }

    /**
     * @return Returns either <code>null</code> or the <code>Article</code> which reverses this instance.
     */
    public Article getReversingArticle() {
        return reversingArticle;
    }

    //   /**
    //    * @return Returns the allocated.
    //    */
    //   public boolean isAllocated()
    //   {
    //      return allocated;
    //   }
    /**
     * @return Returns the deliveryNote.
     */
    public DeliveryNote getDeliveryNote() {
        return deliveryNote;
    }

    /**
     * @param deliveryNote The deliveryNote to set.
     */
    public void setDeliveryNote(DeliveryNote deliveryNote) {
        this.deliveryNote = deliveryNote;
    }

    public ReceptionNote getReceptionNote() {
        return receptionNote;
    }

    public void setReceptionNote(ReceptionNote receptionNote) {
        this.receptionNote = receptionNote;
    }

    /**
     * @return Returns the segment.
     */
    public Segment getSegment() {
        return segment;
    }

    /**
     * @param segment The segment to set.
     */
    protected void setSegment(Segment segment) {
        this.segment = segment;
    }
    //   /**
    //    * This method returns the date and time when the Article will
    //    * automatically be released. To change this, you need to call
    //    * <tt>allocate(..)</tt>.
    //    *
    //    * @return Returns the autoReleaseDT.
    //    * @see #allocate(User, Date)
    //    */
    //   public Date getAutoReleaseDT()
    //   {
    //      return autoReleaseDT;
    //   }

    // TODO logic for allocate and release must be in the Trader (and not here)
    //   /**
    //    * This method allocates the product which is embraced by this Article.
    //    * The allocation work is delegated to the Trader, because it is necessary
    //    * to create Order-s & Offer-s to get the requirement-products.
    //    * @throws ModuleException
    //    */
    //   public void allocate(User user, Date autoReleaseDT)
    //         throws ModuleException
    //   {
    //      PersistenceManager pm = JDOHelper.getPersistenceManager(this);
    //      if (pm == null)
    //         throw new IllegalStateException("This instance of Article is currently not persistent!");
    //
    //      Trader trader = Trader.getTrader(pm);
    //      trader.allocateArticle(user, this, autoReleaseDT);
    //
    //      this.autoReleaseDT = autoReleaseDT;
    //      this.allocated = true;
    //   }
    //
    //   public void release(User user)
    //         throws ModuleException
    //   {
    //      if (!allocated)
    //         return;
    //
    //      PersistenceManager pm = JDOHelper.getPersistenceManager(this);
    //      if (pm == null)
    //         throw new IllegalStateException("This instance of Article is currently not persistent!");
    //
    //      Trader trader = Trader.getTrader(pm);
    //      trader.releaseArticle(user, this);
    //      this.allocated = false;
    //   }

    public Invoice getInvoice() {
        return invoice;
    }

    public void setInvoice(Invoice invoice) {
        this.invoice = invoice;
    }

    /**
     * @return Returns the tariff.
     */
    public Tariff getTariff() {
        return tariff;
    }

    /**
     * @param tariff The tariff to set.
     */
    public void setTariff(Tariff tariff) {
        this.tariff = tariff;
    }

    /**
     * @return Returns the currency.
     */
    public Currency getCurrency() {
        return currency;
    }

    public boolean isAllocationPending() {
        return allocationPending;
    }

    protected void _setAllocationPending(boolean allocationPending) {
        this.allocationPending = allocationPending;
    }

    protected void setAllocationPending(boolean allocationPending) {
        this.allocationPending = allocationPending;

        if (isReversed())
            getReversingArticle()._setAllocationPending(allocationPending);

        if (isReversing())
            getReversedArticle()._setAllocationPending(allocationPending);
    }

    public boolean isReleasePending() {
        return releasePending;
    }

    protected void _setReleasePending(boolean releasePending) {
        this.releasePending = releasePending;
    }

    protected void setReleasePending(boolean releasePending) {
        this.releasePending = releasePending;

        if (isReversed())
            getReversingArticle()._setReleasePending(releasePending);

        if (isReversing())
            getReversedArticle()._setReleasePending(releasePending);
    }

    public boolean isAllocated() {
        return allocated;
    }

    protected void _setAllocated(boolean allocated) {
        this.allocated = allocated;
    }

    protected void setAllocated(boolean allocated) {
        this.allocated = allocated;

        if (isReversed())
            getReversingArticle()._setAllocated(allocated);

        if (isReversing())
            getReversedArticle()._setAllocated(allocated);
    }

    /**
     * @return Returns the order.
     */
    public Order getOrder() {
        return order;
    }

    /**
     * This method is called by {@link Trader#reverseArticles(User, Offer, Collection)}.
     * When inheriting <code>Article</code>, you MUST override this method, because it
     * MUST return an instance of the same class as the reversed <code>Article</code>.
     * <p>
     * Override this method and create a new instance of your <code>Article</code> implementation
     * which reverses this Article (if possible).
     * </p>
     * <p>
     * This method should be a one-liner! You should put all your logic into the constructor
     * (see {@link #Article(User, Offer, long, Article)}).
     * </p>
     * @param user The responsible user.
     * @param offer The <code>Offer</code> into which the new <code>Article</code> will be created.
     * @param articleID A new (not yet used) unique id within the current organisation for the new <code>Article</code>, that will be created.
     * @return Returns the new Article that reverses this one.
     */
    protected Article reverseArticle(User user, Offer offer, long articleID) {
        if (this.getClass() != Article.class)
            throw new IllegalStateException(
                    "You did not override Article.reverseArticle(...)! When inheriting Article, you must override this method and return an instance of your new class!");

        return new Article(user, offer, articleID, this);
    }

    protected void setReversedArticle(Article reversedArticle) {
        this.reversedArticle = reversedArticle;
        this.reversing = reversedArticle != null;
    }

    protected void setReversingArticle(Article reversingArticle) {
        this.reversingArticle = reversingArticle;
        this.reversed = reversingArticle != null;
    }

    //   protected void setReferencedArticle(Article referencedArticle)
    //   {
    //      if (this.referencedArticle != null)
    //         throw new IllegalStateException("this.referencedArticle != null !!! Cannot re-assign.");
    //
    //      this.referencedArticle = referencedArticle;
    //   }
    //
    //   public Article createReferencingArticle(User user)
    //   {
    //      return new Article(user, IDGenerator.nextID(Article.class), this);
    //   }
    //
    //   public Article getReferencedArticle()
    //   {
    //      return referencedArticle;
    //   }

    /**
     * @return Whether or not this Article has already been reversed. Returns <code>true</code>,
     *         if {@link #getReversingArticle()} is assigned.
     */
    public boolean isReversed() {
        return reversed;
    }

    /**
     * @return Whether or not this Article reverses another one. Returns <code>true</code>,
     *         if {@link #getReversedArticle()} is assigned.
     */
    public boolean isReversing() {
        return reversing;
    }

    /**
     * @return This flag is set <code>true</code>, if the reversing offer is
     *         somehow cancelled (rejected, expired, revoked, aborted etc.).
     */
    public boolean isReversingAborted() {
        return isReversingAborted;
    }

    protected void setReversingAborted() {
        this.isReversingAborted = true;

        // This article will never be updated because the reversed article is not pointing to this "defunct" reversing article anymore.
        // Therefore it probably looks better, if we mark it as not allocated. The UI should somehow show this status.
        this._setAllocated(false);
        this.setReleasePending(false);
        this.setAllocationPending(false);
    }

    public void jdoPreDelete() {
        if (reversedArticle != null)
            reversedArticle.setReversingArticle(null);

        ArticleLocal tmpArticleLocal = this.articleLocal;
        this.articleLocal = null;
        getPersistenceManager().deletePersistent(tmpArticleLocal);
    }

    protected PersistenceManager getPersistenceManager() {
        PersistenceManager pm = JDOHelper.getPersistenceManager(this);
        if (pm == null)
            throw new IllegalStateException(
                    "This instance of Article has currently no PersistenceManager assigned!");

        return pm;
    }

    public void jdoPreDetach() {
    }

    public void jdoPostDetach(Object _attached) {
        Article attached = (Article) _attached;
        Article detached = this;
        Set<?> fetchGroups = attached.getPersistenceManager().getFetchPlan().getGroups();

        boolean fetchGroupsArticleInEditor = fetchGroups
                .contains(FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_ORDER_EDITOR)
                || fetchGroups.contains(FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_OFFER_EDITOR)
                || fetchGroups.contains(FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_INVOICE_EDITOR)
                || fetchGroups.contains(FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_DELIVERY_NOTE_EDITOR)
                || fetchGroups.contains(FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_RECEPTION_NOTE_EDITOR);

        // The following lines (nulling all the IDs) is not really necessary, but in case the JDO impl
        // has a bug that causes them to be copied, we're sure they're always null - despite of any bug
        detached.orderID = null;
        detached.offerID = null;
        detached.invoiceID = null;
        detached.deliveryNoteID = null;

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_ORDER_ID)) {
            detached.orderID = attached.getOrderID();
            detached.orderID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_OFFER_ID)) {
            detached.offerID = attached.getOfferID();
            detached.offerID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_INVOICE_ID)) {
            detached.invoiceID = attached.getInvoiceID();
            detached.invoiceID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_DELIVERY_NOTE_ID)) {
            detached.deliveryNoteID = attached.getDeliveryNoteID();
            detached.deliveryNoteID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_RECEPTION_NOTE_ID)) {
            detached.receptionNoteID = attached.getReceptionNoteID();
            detached.receptionNoteID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_REVERSED_ARTICLE_ID)) {
            detached.reversedArticleID = attached.getReversedArticleID();
            detached.reversedArticleID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_REVERSING_ARTICLE_ID)) {
            detached.reversingArticleID = attached.getReversingArticleID();
            detached.reversingArticleID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_VENDOR_ID)) {
            detached.vendorID = attached.getVendorID();
            detached.vendorID_detached = true;
        }

        if (fetchGroupsArticleInEditor || fetchGroups.contains(FETCH_GROUP_CUSTOMER_ID)) {
            detached.customerID = attached.getCustomerID();
            detached.customerID_detached = true;
        }

        if (fetchGroups.contains(FETCH_GROUP_END_CUSTOMER_ID)) {
            detached.endCustomerID = attached.getEndCustomerID();
            detached.endCustomerID_detached = true;
        }
    }

    @Override
    public void jdoPreAttach() {
        LegalEntity endCustomer;
        try {
            endCustomer = this.getEndCustomer();
        } catch (JDODetachedFieldAccessException x) {
            endCustomer = null;
        }

        if (endCustomer != null) {
            PersistenceManager pm = NLJDOHelper.getThreadPersistenceManager();
            if (this.organisationID.equals(LocalOrganisation.getLocalOrganisation(pm).getOrganisationID())) {
                ArticleID articleID = (ArticleID) JDOHelper.getObjectId(this);
                Article persistentArticle = null;
                try {
                    persistentArticle = (Article) pm.getObjectById(articleID);
                } catch (JDOObjectNotFoundException x) {
                    // ignore
                }
                if (persistentArticle != null && !endCustomer.equals(persistentArticle.getEndCustomer())) {
                    User user = SecurityReflector.getUserDescriptor().getUser(pm);
                    pm.makePersistent(new ArticleEndCustomerHistoryItem(this, persistentArticle.getEndCustomer(),
                            endCustomer, user));
                }
            }
        }
    }

    @Override
    public void jdoPostAttach(Object o) {
    }

    public void jdoPreStore() {
        if (primaryKey == null) {
            primaryKey = getPrimaryKey(organisationID, articleID);
            logger.info("Seems, the JPOX bug still exists: primaryKey == null! Resetting it to " + primaryKey);
        }

        if (JDOHelper.isNew(this) && endCustomer != null) {
            PersistenceManager pm = getPersistenceManager();
            User user = SecurityReflector.getUserDescriptor().getUser(pm);
            pm.makePersistent(new ArticleEndCustomerHistoryItem(this, null, endCustomer, user));
        }
    }

    public void setAllocationException(String allocationExceptionClass, String allocationExceptionMessage,
            String allocationExceptionStackTrace) {
        this.allocationExceptionClass = allocationExceptionClass;
        this.allocationExceptionMessage = allocationExceptionMessage;
        this.allocationExceptionStackTrace = allocationExceptionStackTrace;
    }

    public void setAllocationException(Throwable t) {
        if (t == null) {
            this.allocationExceptionClass = null;
            this.allocationExceptionMessage = null;
            this.allocationExceptionStackTrace = null;
            return;
        }

        Throwable root = ExceptionUtils.getRootCause(t);
        if (root == null)
            root = t;
        //      StackTraceElement ste = root.getStackTrace()[0];

        this.allocationExceptionClass = root.getClass().getName();
        this.allocationExceptionMessage = root.getLocalizedMessage();
        //      this.allocationExceptionStackTrace = ste.toString();
        this.allocationExceptionStackTrace = Util.getStackTraceAsString(root);
    }

    public void setReleaseException(String releaseExceptionClass, String releaseExceptionMessage,
            String releaseExceptionStackTrace) {
        this.releaseExceptionClass = releaseExceptionClass;
        this.releaseExceptionMessage = releaseExceptionMessage;
        this.releaseExceptionStackTrace = releaseExceptionStackTrace;
    }

    public void setReleaseException(Throwable t) {
        if (t == null) {
            this.releaseExceptionClass = null;
            this.releaseExceptionMessage = null;
            this.releaseExceptionStackTrace = null;
            return;
        }

        Throwable root = ExceptionUtils.getRootCause(t);
        if (root == null)
            root = t;
        StackTraceElement ste = root.getStackTrace()[0];

        this.releaseExceptionClass = root.getClass().getName();
        this.releaseExceptionMessage = root.getLocalizedMessage();
        this.releaseExceptionStackTrace = ste.toString();
    }

    public String getAllocationExceptionClass() {
        return allocationExceptionClass;
    }

    public String getAllocationExceptionMessage() {
        return allocationExceptionMessage;
    }

    public String getAllocationExceptionStackTrace() {
        return allocationExceptionStackTrace;
    }

    public boolean isAllocationAbandoned() {
        return allocationAbandoned;
    }

    public void setAllocationAbandoned(boolean allocationAbandoned) {
        this.allocationAbandoned = allocationAbandoned;
    }

    public String getReleaseExceptionClass() {
        return releaseExceptionClass;
    }

    public String getReleaseExceptionMessage() {
        return releaseExceptionMessage;
    }

    public String getReleaseExceptionStackTrace() {
        return releaseExceptionStackTrace;
    }

    public boolean isReleaseAbandoned() {
        return releaseAbandoned;
    }

    public void setReleaseAbandoned(boolean releaseAbandoned) {
        this.releaseAbandoned = releaseAbandoned;
    }

    public ArticleLocal getArticleLocal() {
        return articleLocal;
    }

    protected void setArticleLocal(ArticleLocal articleLocal) {
        this.articleLocal = articleLocal;
    }

    public ArticleLocal createArticleLocal(User user) {
        if (articleLocal != null)
            throw new IllegalStateException(
                    "There is already an instance of ArticleLocal assigned to this Article: " + getPrimaryKey());

        ArticleLocal al = new ArticleLocal(this);
        setArticleLocal(al);
        return al;
    }

    /**
     * Returns the estimated delivery date for this article specified in the corresponding {@link Offer}.
     * @return the deliveryDateOffer (may be null)
     */
    public Date getDeliveryDateOffer() {
        return deliveryDateOffer;
    }

    /**
     * Sets the estimated delivery date for this article specified in the corresponding {@link Offer}.
     * This may only be set until the corresponding {@link Offer} where this article is contained in, is not yet finalized,
     * otherwise an exception is thrown.
     * @param deliveryDateOffer the deliveryDateOffer to set
     */
    public void setDeliveryDateOffer(Date deliveryDateOffer) {
        if (offer.isFinalized()) {
            throw new IllegalStateException(
                    "Once the offer of an article is finalized, the deliveryDateOffer for the article can not be changed anymore!");
        }
        this.deliveryDateOffer = deliveryDateOffer;
    }

    /**
     * Returns the estimated delivery date for this article specified in the corresponding {@link DeliveryNote}.
     * @return the deliveryDateDeliveryNote (may be null)
     */
    public Date getDeliveryDateDeliveryNote() {
        return deliveryDateDeliveryNote;
    }

    /**
     * Sets the estimated delivery date for this article specified in the corresponding {@link DeliveryNote}.
     * This may only be set until the corresponding {@link DeliveryNote} where this article is contained in, is not yet finalized,
     * otherwise an exception is thrown.
     * @param deliveryDateDeliveryNote the deliveryDateDeliveryNote to set
     */
    public void setDeliveryDateDeliveryNote(Date deliveryDateDeliveryNote) {
        if (deliveryNote.isFinalized()) {
            throw new IllegalStateException(
                    "Once the delivery note of an article is finalized, the deliveryDateDeliveryNote for the article can not be changed anymore!");
        }
        this.deliveryDateDeliveryNote = deliveryDateDeliveryNote;
    }

    @Override
    public String toString() {
        return this.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(this)) + '['
                + organisationID + ',' + ObjectIDUtil.longObjectIDFieldToString(articleID) + ']' + "(version "
                + JDOHelper.getVersion(this) + ')';
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (!(obj instanceof Article))
            return false;

        Article o = (Article) obj;

        return Util.equals(this.organisationID, o.organisationID) && Util.equals(this.articleID, o.articleID);
    }

    @Override
    public int hashCode() {
        return Util.hashCode(organisationID) ^ Util.hashCode(articleID);
    }

    /**
     * This is a temporary method until this problem is solved: http://www.jpox.org/servlet/forum/viewthread?thread=4718
     */
    public void makeAllDirty() {
        if (!JDOHelper.isDetached(this))
            throw new IllegalStateException("This method may only be called while this object is detached!");

        try {
            DeliveryNote d = deliveryNote;
            deliveryNote = null;
            deliveryNote = d;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            Invoice i = invoice;
            invoice = null;
            invoice = i;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            Offer o = offer;
            offer = null;
            offer = o;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            Article a = reversedArticle;
            reversedArticle = null;
            reversedArticle = a;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            Article a = reversingArticle;
            reversingArticle = null;
            reversingArticle = a;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            boolean r = reversed;
            reversed = !r;
            reversed = r;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            boolean r = reversing;
            reversing = !r;
            reversing = r;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }

        try {
            ArticlePrice p = price;
            price = null;
            price = p;
        } catch (JDODetachedFieldAccessException x) {
            // ignore
        }
    }

    /**
     * This is a temporary method until this problem is solved: http://www.jpox.org/servlet/forum/viewthread?thread=4718
     */
    public void checkReversing() {
        Article reversedArticle = this.getReversedArticle();
        if (reversedArticle != null) {
            if (reversedArticle.getReversingArticle() == null)
                reversedArticle.setReversingArticle(this);
        }
    }

    public LegalEntity getEndCustomer() {
        return endCustomer;
    }

    public void setEndCustomer(LegalEntity endCustomer) {
        if (Util.equals(this.endCustomer, endCustomer))
            return;

        PersistenceManager pm = JDOHelper.getPersistenceManager(this);
        if (pm != null) {
            User user = SecurityReflector.getUserDescriptor().getUser(pm);
            pm.makePersistent(new ArticleEndCustomerHistoryItem(this, this.endCustomer, endCustomer, user));
        }

        this.endCustomer = endCustomer;

        this.endCustomerID = null;
        this.endCustomerID_detached = false;
    }

    public AnchorID getEndCustomerID() {
        if (endCustomerID == null && !endCustomerID_detached)
            endCustomerID = (AnchorID) JDOHelper.getObjectId(endCustomer);

        return endCustomerID;
    }
}