org.nightlabs.jfire.store.StoreManagerBean.java Source code

Java tutorial

Introduction

Here is the source code for org.nightlabs.jfire.store.StoreManagerBean.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.store;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.jdo.FetchPlan;
import javax.jdo.JDOHelper;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.nightlabs.jdo.NLJDOHelper;
import org.nightlabs.jdo.query.AbstractJDOQuery;
import org.nightlabs.jdo.query.AbstractSearchQuery;
import org.nightlabs.jdo.query.JDOQueryCollectionDecorator;
import org.nightlabs.jdo.query.QueryCollection;
import org.nightlabs.jfire.accounting.Invoice;
import org.nightlabs.jfire.accounting.id.InvoiceID;
import org.nightlabs.jfire.asyncinvoke.AsyncInvoke;
import org.nightlabs.jfire.asyncinvoke.AsyncInvokeEnqueueException;
import org.nightlabs.jfire.asyncinvoke.Invocation;
import org.nightlabs.jfire.base.BaseSessionBeanImpl;
import org.nightlabs.jfire.config.ConfigSetup;
import org.nightlabs.jfire.config.UserConfigSetup;
import org.nightlabs.jfire.config.WorkstationConfigSetup;
import org.nightlabs.jfire.idgenerator.IDGenerator;
import org.nightlabs.jfire.idgenerator.IDNamespaceDefault;
import org.nightlabs.jfire.jbpm.graph.def.ProcessDefinition;
import org.nightlabs.jfire.jbpm.query.StatableQuery;
import org.nightlabs.jfire.multitxjob.MultiTxJob;
import org.nightlabs.jfire.organisation.Organisation;
import org.nightlabs.jfire.security.Authority;
import org.nightlabs.jfire.security.ResolveSecuringAuthorityStrategy;
import org.nightlabs.jfire.security.User;
import org.nightlabs.jfire.security.id.UserID;
import org.nightlabs.jfire.store.book.DefaultLocalStorekeeperDelegate;
import org.nightlabs.jfire.store.deliver.CheckRequirementsEnvironment;
import org.nightlabs.jfire.store.deliver.CrossTradeDeliveryCoordinator;
import org.nightlabs.jfire.store.deliver.Delivery;
import org.nightlabs.jfire.store.deliver.DeliveryData;
import org.nightlabs.jfire.store.deliver.DeliveryException;
import org.nightlabs.jfire.store.deliver.DeliveryHelperBean;
import org.nightlabs.jfire.store.deliver.DeliveryHelperLocal;
import org.nightlabs.jfire.store.deliver.DeliveryQueue;
import org.nightlabs.jfire.store.deliver.DeliveryResult;
import org.nightlabs.jfire.store.deliver.ModeOfDelivery;
import org.nightlabs.jfire.store.deliver.ModeOfDeliveryConst;
import org.nightlabs.jfire.store.deliver.ModeOfDeliveryFlavour;
import org.nightlabs.jfire.store.deliver.ServerDeliveryProcessor;
import org.nightlabs.jfire.store.deliver.ServerDeliveryProcessorDeliveryQueue;
import org.nightlabs.jfire.store.deliver.ServerDeliveryProcessorJFire;
import org.nightlabs.jfire.store.deliver.ServerDeliveryProcessorMailingPhysicalDefault;
import org.nightlabs.jfire.store.deliver.ServerDeliveryProcessorManual;
import org.nightlabs.jfire.store.deliver.ServerDeliveryProcessorNonDelivery;
import org.nightlabs.jfire.store.deliver.DeliveryHelperBean.ConsolidateProductReferencesInvocation;
import org.nightlabs.jfire.store.deliver.ModeOfDeliveryFlavour.ModeOfDeliveryFlavourProductTypeGroup;
import org.nightlabs.jfire.store.deliver.ModeOfDeliveryFlavour.ModeOfDeliveryFlavourProductTypeGroupCarrier;
import org.nightlabs.jfire.store.deliver.config.ModeOfDeliveryConfigModule;
import org.nightlabs.jfire.store.deliver.id.DeliveryDataID;
import org.nightlabs.jfire.store.deliver.id.DeliveryID;
import org.nightlabs.jfire.store.deliver.id.DeliveryQueueID;
import org.nightlabs.jfire.store.deliver.id.ModeOfDeliveryFlavourID;
import org.nightlabs.jfire.store.deliver.id.ModeOfDeliveryID;
import org.nightlabs.jfire.store.id.DeliveryNoteID;
import org.nightlabs.jfire.store.id.ProductTypeActionHandlerID;
import org.nightlabs.jfire.store.id.ProductTypeGroupID;
import org.nightlabs.jfire.store.id.ProductTypeID;
import org.nightlabs.jfire.store.id.ProductTypePermissionFlagSetID;
import org.nightlabs.jfire.store.id.ReceptionNoteID;
import org.nightlabs.jfire.store.id.RepositoryTypeID;
import org.nightlabs.jfire.store.id.UnitID;
import org.nightlabs.jfire.store.prop.DeliveryNoteStruct;
import org.nightlabs.jfire.store.prop.ReceptionNoteStruct;
import org.nightlabs.jfire.store.query.ProductTransferQuery;
import org.nightlabs.jfire.store.search.AbstractProductTypeQuery;
import org.nightlabs.jfire.store.search.ProductTypeIDTreeNode;
import org.nightlabs.jfire.timer.Task;
import org.nightlabs.jfire.timer.id.TaskID;
import org.nightlabs.jfire.trade.Article;
import org.nightlabs.jfire.trade.ArticleContainer;
import org.nightlabs.jfire.trade.CustomerGroup;
import org.nightlabs.jfire.trade.LegalEntity;
import org.nightlabs.jfire.trade.Offer;
import org.nightlabs.jfire.trade.OfferRequirement;
import org.nightlabs.jfire.trade.Order;
import org.nightlabs.jfire.trade.OrganisationLegalEntity;
import org.nightlabs.jfire.trade.TradeSide;
import org.nightlabs.jfire.trade.Trader;
import org.nightlabs.jfire.trade.id.ArticleContainerID;
import org.nightlabs.jfire.trade.id.ArticleID;
import org.nightlabs.jfire.trade.id.CustomerGroupID;
import org.nightlabs.jfire.trade.id.OfferID;
import org.nightlabs.jfire.trade.id.OrderID;
import org.nightlabs.jfire.trade.jbpm.ProcessDefinitionAssignment;
import org.nightlabs.jfire.trade.query.OfferQuery;
import org.nightlabs.jfire.transfer.id.AnchorID;
import org.nightlabs.jfire.transfer.id.TransferID;
import org.nightlabs.util.CollectionUtil;
import org.nightlabs.util.NLLocale;
import org.nightlabs.util.Util;

/**
 * @author marco schulze - marco at nightlabs dot de
 *
 * @ejb.bean name="jfire/ejb/JFireTrade/StoreManager"
 *                jndi-name="jfire/ejb/JFireTrade/StoreManager"
 *                type="Stateless"
 *                transaction-type="Container"
 *
 * @ejb.util generate="physical"
 * @ejb.transaction type="Required"
 */
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@TransactionManagement(TransactionManagementType.CONTAINER)
@Stateless
public class StoreManagerBean extends BaseSessionBeanImpl implements StoreManagerRemote, StoreManagerLocal {
    private static final long serialVersionUID = 1L;
    /**
     * LOG4J logger used by this class
     */
    private static final Logger logger = Logger.getLogger(StoreManagerBean.class);

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#initialise()
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("_System_")
    @Override
    public void initialise() throws Exception {
        PersistenceManager pm = createPersistenceManager();
        try {
            initRegisterConfigModules(pm);

            initTimerTaskCalculateProductTypeAvailabilityPercentage(pm);

            pm.getExtent(ModeOfDelivery.class);
            try {
                pm.getObjectById(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_MANUAL);

                // it already exists, hence initialization is already done
                return;
            } catch (JDOObjectNotFoundException x) {
                // not yet initialized
            }

            pm.getExtent(ProductTypePermissionFlagSet.class); // We must already initialize the meta data here, because otherwise we run into deadlocks.

            SecurityChangeListenerProductTypePermission.register(pm);

            DefaultLocalStorekeeperDelegate.getDefaultLocalStorekeeperDelegate(pm);

            // Initalise standard property set structures for articleContainers
            DeliveryNoteStruct.getDeliveryNoteStructLocal(pm);
            ReceptionNoteStruct.getReceptionNoteStructLocal(pm);

            // create & persist instances of RepositoryType
            RepositoryType repositoryType;

            repositoryType = pm.makePersistent(new RepositoryType(RepositoryType.REPOSITORY_TYPE_ID_HOME, false));
            repositoryType.getName().setText(Locale.ENGLISH.getLanguage(), "Home");
            repositoryType.getName().setText(Locale.GERMAN.getLanguage(), "Heim");

            repositoryType = pm.makePersistent(new RepositoryType(RepositoryType.REPOSITORY_TYPE_ID_OUTSIDE, true));
            repositoryType.getName().setText(Locale.ENGLISH.getLanguage(), "Outside");
            repositoryType.getName().setText(Locale.GERMAN.getLanguage(), "Auerhalb");

            repositoryType = pm
                    .makePersistent(new RepositoryType(RepositoryType.REPOSITORY_TYPE_ID_PARTNER, false));
            repositoryType.getName().setText(Locale.ENGLISH.getLanguage(), "Business partner");
            repositoryType.getName().setText(Locale.GERMAN.getLanguage(), "Geschftspartner");

            Store store = Store.getStore(pm);
            Trader trader = Trader.getTrader(pm);

            LegalEntity anonymousCustomer = LegalEntity.getAnonymousLegalEntity(pm);
            CustomerGroup anonymousCustomerGroup = anonymousCustomer.getDefaultCustomerGroup();

            //       create fundamental set of ModeOfDelivery/ModeOfDeliveryFlavour
            // manual
            ModeOfDelivery modeOfDelivery = new ModeOfDelivery(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_MANUAL);
            modeOfDelivery.getName().setText(Locale.ENGLISH.getLanguage(),
                    "Personal Delivery (manually from hand to hand)");
            modeOfDelivery.getName().setText(Locale.GERMAN.getLanguage(),
                    "Persnliche Lieferung (manuell von Hand zu Hand)");
            ModeOfDeliveryFlavour modeOfDeliveryFlavour = modeOfDelivery
                    .createFlavour(Organisation.DEV_ORGANISATION_ID, "manual");
            modeOfDeliveryFlavour.loadIconFromResource();
            modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(),
                    "Personal Delivery (manually from hand to hand)");
            modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(),
                    "Persnliche Lieferung (manuell von Hand zu Hand)");
            modeOfDelivery = pm.makePersistent(modeOfDelivery);
            trader.getDefaultCustomerGroupForKnownCustomer().addModeOfDelivery(modeOfDelivery);
            anonymousCustomerGroup.addModeOfDelivery(modeOfDelivery);

            ModeOfDelivery modeOfDeliveryManual = modeOfDelivery;

            // nonDelivery
            modeOfDelivery = new ModeOfDelivery(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_NON_DELIVERY);
            modeOfDelivery.getName().setText(Locale.ENGLISH.getLanguage(), "Non-Delivery");
            modeOfDelivery.getName().setText(Locale.GERMAN.getLanguage(), "Nichtlieferung");
            modeOfDeliveryFlavour = modeOfDelivery.createFlavour(Organisation.DEV_ORGANISATION_ID, "nonDelivery");
            modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(), "Non-Delivery");
            modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(), "Nichtlieferung");
            modeOfDelivery = pm.makePersistent(modeOfDelivery);
            trader.getDefaultCustomerGroupForKnownCustomer().addModeOfDelivery(modeOfDelivery);
            ModeOfDelivery modeOfDeliveryNonDelivery = modeOfDelivery;

            // mailing.physical
            modeOfDelivery = new ModeOfDelivery(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_MAILING_PHYSICAL);
            modeOfDelivery.getName().setText(Locale.ENGLISH.getLanguage(), "Mailing delivery (physical)");
            modeOfDelivery.getName().setText(Locale.GERMAN.getLanguage(), "Postversand (physisch)");
            modeOfDeliveryFlavour = modeOfDelivery.createFlavour(Organisation.DEV_ORGANISATION_ID,
                    "mailing.physical.default");
            modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(),
                    "Mailing delivery via default service provider");
            modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(),
                    "Postversand via Standard-Dienstleister");
            ModeOfDeliveryFlavour modeOfDeliveryFlavourMailingPhysicalDefault = modeOfDeliveryFlavour;
            //         modeOfDeliveryFlavour = modeOfDelivery.createFlavour(Organisation.DEV_ORGANISATION_ID, "mailing.physical.DHL");
            //         modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(), "Mailing delivery via DHL");
            //         modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(), "Postversand via DHL");
            //         modeOfDeliveryFlavour = modeOfDelivery.createFlavour(Organisation.DEV_ORGANISATION_ID, "mailing.physical.UPS");
            //         modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(), "Mailing delivery via UPS");
            //         modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(), "Postversand via UPS");
            modeOfDelivery = pm.makePersistent(modeOfDelivery);
            trader.getDefaultCustomerGroupForKnownCustomer().addModeOfDelivery(modeOfDelivery);
            anonymousCustomerGroup.addModeOfDelivery(modeOfDelivery);

            // mailing.virtual
            modeOfDelivery = new ModeOfDelivery(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_MAILING_VIRTUAL);
            modeOfDelivery.getName().setText(Locale.ENGLISH.getLanguage(), "Virtual Delivery (online)");
            modeOfDelivery.getName().setText(Locale.GERMAN.getLanguage(), "Virtuelle Lieferung (online)");
            //         modeOfDeliveryFlavour = modeOfDelivery.createFlavour(Organisation.DEV_ORGANISATION_ID, "mailing.virtual.email");
            //         modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(), "Delivery by eMail");
            //         modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(), "Zustellung via eMail");
            //         pm.makePersistent(modeOfDelivery);
            //         trader.getDefaultCustomerGroupForKnownCustomer().addModeOfDelivery(modeOfDelivery);
            //         anonymousCustomerGroup.addModeOfDelivery(modeOfDelivery);

            modeOfDelivery = new ModeOfDelivery(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_JFIRE);
            modeOfDelivery.getName().setText(Locale.ENGLISH.getLanguage(), "JFire Internal Delivery");
            modeOfDelivery.getName().setText(Locale.GERMAN.getLanguage(), "JFire-interne Lieferung");
            modeOfDeliveryFlavour = modeOfDelivery
                    .createFlavour(ModeOfDeliveryConst.MODE_OF_DELIVERY_FLAVOUR_ID_JFIRE);
            ModeOfDeliveryFlavour modeOfDeliveryFlavourJFire = modeOfDeliveryFlavour;
            modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(), "JFire Internal Delivery");
            modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(), "JFire-interne Lieferung");
            modeOfDelivery = pm.makePersistent(modeOfDelivery);
            //         trader.getDefaultCustomerGroupForKnownCustomer().addModeOfDelivery(modeOfDelivery);
            //         anonymousCustomerGroup.addModeOfDelivery(modeOfDelivery);

            // deliveryQueue
            modeOfDelivery = new ModeOfDelivery(ModeOfDeliveryConst.MODE_OF_DELIVERY_ID_DELIVER_TO_DELIVERY_QUEUE);
            modeOfDelivery.getName().setText(Locale.ENGLISH.getLanguage(), "Deliver to Delivery Queue");
            modeOfDelivery.getName().setText(Locale.GERMAN.getLanguage(), "Lieferung in Lieferwarteschlange");
            modeOfDeliveryFlavour = modeOfDelivery.createFlavour(Organisation.DEV_ORGANISATION_ID,
                    "deliverToDeliveryQueue");
            modeOfDeliveryFlavour.getName().setText(Locale.ENGLISH.getLanguage(), "Deliver to Delivery Queue");
            modeOfDeliveryFlavour.getName().setText(Locale.GERMAN.getLanguage(),
                    "Lieferung in Lieferwarteschlange");
            modeOfDelivery = pm.makePersistent(modeOfDelivery);
            ModeOfDelivery modeOfDeliveryDeliveryQueue = modeOfDelivery;
            trader.getDefaultCustomerGroupForKnownCustomer().addModeOfDelivery(modeOfDelivery);
            anonymousCustomerGroup.addModeOfDelivery(modeOfDeliveryDeliveryQueue);

            // create some ServerDeliveryProcessor s
            ServerDeliveryProcessorManual serverDeliveryProcessorManual = ServerDeliveryProcessorManual
                    .getServerDeliveryProcessorManual(pm);
            serverDeliveryProcessorManual.addModeOfDelivery(modeOfDeliveryManual);
            serverDeliveryProcessorManual.getName().setText(Locale.ENGLISH.getLanguage(),
                    "Manual Delivery (no digital action)");
            serverDeliveryProcessorManual.getName().setText(Locale.GERMAN.getLanguage(),
                    "Manuelle Lieferung (nicht-digitale Aktion)");

            ServerDeliveryProcessorNonDelivery serverDeliveryProcessorNonDelivery = ServerDeliveryProcessorNonDelivery
                    .getServerDeliveryProcessorNonDelivery(pm);
            serverDeliveryProcessorNonDelivery.addModeOfDelivery(modeOfDeliveryNonDelivery);
            serverDeliveryProcessorNonDelivery.getName().setText(Locale.ENGLISH.getLanguage(),
                    "Non-Delivery (delivery will be postponed)");
            serverDeliveryProcessorNonDelivery.getName().setText(Locale.GERMAN.getLanguage(),
                    "Nichtlieferung (Lieferung wird verschoben)");

            ServerDeliveryProcessorMailingPhysicalDefault serverDeliveryProcessorMailingPhysicalDefault = ServerDeliveryProcessorMailingPhysicalDefault
                    .getServerDeliveryProcessorMailingPhysicalDefault(pm);
            serverDeliveryProcessorMailingPhysicalDefault
                    .addModeOfDeliveryFlavour(modeOfDeliveryFlavourMailingPhysicalDefault);
            serverDeliveryProcessorMailingPhysicalDefault.getName().setText(Locale.ENGLISH.getLanguage(),
                    "Physical mail via default service provider");
            serverDeliveryProcessorMailingPhysicalDefault.getName().setText(Locale.GERMAN.getLanguage(),
                    "Postversand via Standard-Dienstleister");

            ServerDeliveryProcessorDeliveryQueue serverDeliveryProcessorDeliveryQueue = ServerDeliveryProcessorDeliveryQueue
                    .getServerDeliveryProcessorDeliveryQueue(pm);
            serverDeliveryProcessorDeliveryQueue.addModeOfDelivery(modeOfDeliveryDeliveryQueue);

            ServerDeliveryProcessorJFire serverDeliveryProcessorJFire = ServerDeliveryProcessorJFire
                    .getServerDeliveryProcessorJFire(pm);
            serverDeliveryProcessorJFire.addModeOfDeliveryFlavour(modeOfDeliveryFlavourJFire);
            serverDeliveryProcessorJFire.getName().setText(Locale.ENGLISH.getLanguage(), "JFire Internal Delivery");

            // persist process definitions
            ProcessDefinition processDefinitionDeliveryNoteCustomerLocal;
            processDefinitionDeliveryNoteCustomerLocal = store.storeProcessDefinitionDeliveryNote(
                    TradeSide.customerLocal,
                    ProcessDefinitionAssignment.class.getResource("deliverynote/customer/local/"));
            pm.makePersistent(new ProcessDefinitionAssignment(DeliveryNote.class, TradeSide.customerLocal,
                    processDefinitionDeliveryNoteCustomerLocal));

            ProcessDefinition processDefinitionDeliveryNoteCustomerCrossOrg;
            processDefinitionDeliveryNoteCustomerCrossOrg = store.storeProcessDefinitionDeliveryNote(
                    TradeSide.customerCrossOrganisation,
                    ProcessDefinitionAssignment.class.getResource("deliverynote/customer/crossorganisation/"));
            pm.makePersistent(new ProcessDefinitionAssignment(DeliveryNote.class,
                    TradeSide.customerCrossOrganisation, processDefinitionDeliveryNoteCustomerCrossOrg));

            ProcessDefinition processDefinitionDeliveryNoteVendor;
            processDefinitionDeliveryNoteVendor = store.storeProcessDefinitionDeliveryNote(TradeSide.vendor,
                    ProcessDefinitionAssignment.class.getResource("deliverynote/vendor/"));
            pm.makePersistent(new ProcessDefinitionAssignment(DeliveryNote.class, TradeSide.vendor,
                    processDefinitionDeliveryNoteVendor));

            ProcessDefinition processDefinitionReceptionNoteCustomer;
            processDefinitionReceptionNoteCustomer = store.storeProcessDefinitionReceptionNote(
                    TradeSide.customerCrossOrganisation,
                    ProcessDefinitionAssignment.class.getResource("receptionnote/customer/crossorganisation/"));
            pm.makePersistent(new ProcessDefinitionAssignment(ReceptionNote.class,
                    TradeSide.customerCrossOrganisation, processDefinitionReceptionNoteCustomer));

            // TODO create and persist ProcessDefinition for ReceptionNote.Vendor
            // TODO and for customerLocal

            IDNamespaceDefault idNamespaceDefault = IDNamespaceDefault.createIDNamespaceDefault(pm,
                    getOrganisationID(), DeliveryNote.class);
            idNamespaceDefault.setCacheSizeServer(0);
            idNamespaceDefault.setCacheSizeClient(0);

            pm.makePersistent(new EditLockTypeDeliveryNote(EditLockTypeDeliveryNote.EDIT_LOCK_TYPE_ID));

            Unit unit = new Unit(Organisation.DEV_ORGANISATION_ID, "h", 2);
            unit.getSymbol().setText(Locale.ENGLISH.getLanguage(), "h");
            unit.getName().setText(Locale.ENGLISH.getLanguage(), "hour");
            unit.getName().setText(Locale.GERMAN.getLanguage(), "Stunde");
            pm.makePersistent(unit);

            unit = new Unit(Organisation.DEV_ORGANISATION_ID, "piece", 3);
            unit.getSymbol().setText(Locale.ENGLISH.getLanguage(), "pcs.");
            unit.getName().setText(Locale.ENGLISH.getLanguage(), "pieces");
            unit.getSymbol().setText(Locale.GERMAN.getLanguage(), "Stk.");
            unit.getName().setText(Locale.GERMAN.getLanguage(), "Stck");
            pm.makePersistent(unit);

            unit = new Unit(Organisation.DEV_ORGANISATION_ID, "flatRate", 0);
            unit.getSymbol().setText(Locale.ENGLISH.getLanguage(), "()");
            unit.getName().setText(Locale.ENGLISH.getLanguage(), "(flat-rate)");
            unit.getSymbol().setText(Locale.GERMAN.getLanguage(), "()");
            unit.getName().setText(Locale.GERMAN.getLanguage(), "(pauschal)");
            pm.makePersistent(unit);

        } finally {
            pm.close();
        }
    }

    /**
     * Called by {@link #initialise()} and registeres the
     * config-modules in their config-setup.
     *
     * This method checks itself whether initialisation
     * was performed already and therefore can be safely
     * called anytime in the process.
     */
    private void initRegisterConfigModules(PersistenceManager pm) {
        boolean needsUpdate = false;
        // Register all User - ConfigModules
        ConfigSetup configSetup = ConfigSetup.getConfigSetup(pm, getOrganisationID(),
                UserConfigSetup.CONFIG_SETUP_TYPE_USER);
        if (!configSetup.getConfigModuleClasses().contains(ModeOfDeliveryConfigModule.class.getName())) {
            configSetup.getConfigModuleClasses().add(ModeOfDeliveryConfigModule.class.getName());
            needsUpdate = true;
        }

        // Register all Workstation - ConfigModules
        configSetup = ConfigSetup.getConfigSetup(pm, getOrganisationID(),
                WorkstationConfigSetup.CONFIG_SETUP_TYPE_WORKSTATION);
        if (!configSetup.getConfigModuleClasses().contains(ModeOfDeliveryConfigModule.class.getName())) {
            configSetup.getConfigModuleClasses().add(ModeOfDeliveryConfigModule.class.getName());
            needsUpdate = true;
        }
        if (needsUpdate)
            ConfigSetup.ensureAllPrerequisites(pm);
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getUnitIDs()
     */
    @RolesAllowed("_Guest_")
    @Override
    public Set<UnitID> getUnitIDs() {
        PersistenceManager pm = createPersistenceManager();
        try {
            Query q = pm.newQuery(Unit.class);
            q.setResult("JDOHelper.getObjectId(this)");
            Collection<? extends UnitID> c = CollectionUtil.castCollection((Collection<?>) q.execute());
            return new HashSet<UnitID>(c);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getUnits(java.util.Collection, java.lang.String[], int)
     */
    @RolesAllowed("_Guest_")
    @Override
    public List<Unit> getUnits(Collection<UnitID> unitIDs, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, unitIDs, Unit.class, fetchGroups, maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getDeliveryNotes(java.util.Set, java.lang.String[], int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryDeliveryNotes")
    @Override
    public List<DeliveryNote> getDeliveryNotes(Set<DeliveryNoteID> deliveryNoteIDs, String[] fetchGroups,
            int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, deliveryNoteIDs, DeliveryNote.class, fetchGroups,
                    maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    //   /**
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="org.nightlabs.jfire.store.queryDeliveryNotes"
    //    * @!ejb.transaction type="Supports" @!This usually means that no transaction is opened which is significantly faster and recommended for all read-only EJB methods! Marco.
    //    */
    //   public DeliveryNote getDeliveryNote(DeliveryNoteID deliveryNoteID, String[] fetchGroups, int maxFetchDepth)
    //   {
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
    //         if (fetchGroups != null)
    //            pm.getFetchPlan().setGroups(fetchGroups);
    //
    //         return (DeliveryNote) pm.detachCopy(pm.getObjectById(deliveryNoteID));
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getDeliveryNoteIDs(org.nightlabs.jdo.query.QueryCollection)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryDeliveryNotes")
    @SuppressWarnings("unchecked")
    @Override
    public Set<DeliveryNoteID> getDeliveryNoteIDs(QueryCollection<? extends AbstractJDOQuery> queries) {
        if (queries == null)
            return null;

        if (!DeliveryNote.class.isAssignableFrom(queries.getResultClass())) {
            throw new RuntimeException("Given QueryCollection has invalid return type! " + "Invalid return type= "
                    + queries.getResultClassName());
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            if (!(queries instanceof JDOQueryCollectionDecorator)) {
                queries = new JDOQueryCollectionDecorator<AbstractJDOQuery>(queries);
            }

            JDOQueryCollectionDecorator<AbstractJDOQuery> decoratedCollection = (JDOQueryCollectionDecorator<AbstractJDOQuery>) queries;

            decoratedCollection.setPersistenceManager(pm);
            Collection<DeliveryNote> deliveryNotes = (Collection<DeliveryNote>) decoratedCollection
                    .executeQueries();

            return NLJDOHelper.getObjectIDSet(deliveryNotes);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTypes(java.util.Set, java.lang.String[], int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    //   public List<ProductType> getProductTypes(Set<ProductTypeID> productTypeIDs, String[] fetchGroups, int maxFetchDepth)
    public List<ProductType> getProductTypes(Collection<ProductTypeID> productTypeIDs, String[] fetchGroups,
            int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            //         List<ProductTypeLocal> productTypeLocals = new ArrayList<ProductTypeLocal>(productTypeIDs.size());
            //         for (ProductTypeID productTypeID : productTypeIDs) {
            //            ProductType productType = (ProductType) pm.getObjectById(productTypeID);
            //            ProductTypeLocal productTypeLocal = productType.getProductTypeLocal();
            //            if (productTypeLocal == null)
            //               throw new IllegalStateException("productType.productTypeLocal is null: " + productType);
            //
            //            productTypeLocals.add(productTypeLocal);
            //         }
            //
            //         productTypeLocals = Authority.filterSecuredObjects(
            //               pm,
            //               productTypeLocals,
            //               getPrincipal(),
            //               RoleConstants.seeProductType,
            //               ResolveSecuringAuthorityStrategy.allow
            //         );
            //
            //         List<ProductType> productTypes = new ArrayList<ProductType>(productTypeLocals.size());
            //         for (ProductTypeLocal productTypeLocal : productTypeLocals) {
            //            ProductType productType = productTypeLocal.getProductType();
            //            if (productType == null)
            //               throw new IllegalStateException("productTypeLocal.productType is null: " + productTypeLocal);
            //
            //            productTypes.add(pm.detachCopy(productType));
            //         }
            List<ProductType> productTypes = NLJDOHelper.getObjectList(pm, productTypeIDs, ProductType.class);

            productTypes = Authority.filterIndirectlySecuredObjects(pm, productTypes, getPrincipal(),
                    RoleConstants.seeProductType, ResolveSecuringAuthorityStrategy.allow);

            productTypes = (List<ProductType>) pm.detachCopyAll(productTypes);

            return productTypes;

            //         return NLJDOHelper.getDetachedObjectList(pm, productTypeIDs, ProductType.class, fetchGroups, maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#setProductTypeStatus_published(org.nightlabs.jfire.store.id.ProductTypeID, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed({ "org.nightlabs.jfire.store.editUnconfirmedProductType",
            "org.nightlabs.jfire.store.editConfirmedProductType" })
    @Override
    public ProductTypeStatusHistoryItem setProductTypeStatus_published(ProductTypeID productTypeID, boolean get,
            String[] fetchGroups, int maxFetchDepth) throws CannotPublishProductTypeException {
        PersistenceManager pm = createPersistenceManager();
        try {
            if (get) {
                pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
                if (fetchGroups != null)
                    pm.getFetchPlan().setGroups(fetchGroups);
            }

            pm.getExtent(ProductType.class);
            Store store = Store.getStore(pm);
            ProductType productType = (ProductType) pm.getObjectById(productTypeID);
            ProductTypeStatusHistoryItem productTypeStatusHistoryItem = store
                    .setProductTypeStatus_published(User.getUser(pm, getPrincipal()), productType);

            if (productTypeStatusHistoryItem == null || !get)
                return null;
            else
                return pm.detachCopy(productTypeStatusHistoryItem);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#setProductTypeStatus_confirmed(org.nightlabs.jfire.store.id.ProductTypeID, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed({ "org.nightlabs.jfire.store.editUnconfirmedProductType",
            "org.nightlabs.jfire.store.editConfirmedProductType" })
    @Override
    public ProductTypeStatusHistoryItem setProductTypeStatus_confirmed(ProductTypeID productTypeID, boolean get,
            String[] fetchGroups, int maxFetchDepth) throws CannotConfirmProductTypeException {
        PersistenceManager pm = createPersistenceManager();
        try {
            if (get) {
                pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
                if (fetchGroups != null)
                    pm.getFetchPlan().setGroups(fetchGroups);
            }

            pm.getExtent(ProductType.class);
            Store store = Store.getStore(pm);
            ProductType productType = (ProductType) pm.getObjectById(productTypeID);
            ProductTypeStatusHistoryItem productTypeStatusHistoryItem = store
                    .setProductTypeStatus_confirmed(User.getUser(pm, getPrincipal()), productType);

            if (productTypeStatusHistoryItem == null || !get)
                return null;
            else
                return pm.detachCopy(productTypeStatusHistoryItem);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#setProductTypeStatus_saleable(org.nightlabs.jfire.store.id.ProductTypeID, boolean, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed({ "org.nightlabs.jfire.store.editUnconfirmedProductType",
            "org.nightlabs.jfire.store.editConfirmedProductType" })
    @Override
    public ProductTypeStatusHistoryItem setProductTypeStatus_saleable(ProductTypeID productTypeID, boolean saleable,
            boolean get, String[] fetchGroups, int maxFetchDepth) throws CannotMakeProductTypeSaleableException {
        PersistenceManager pm = createPersistenceManager();
        try {
            if (get) {
                pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
                if (fetchGroups != null)
                    pm.getFetchPlan().setGroups(fetchGroups);
            }

            pm.getExtent(ProductType.class);
            Store store = Store.getStore(pm);
            ProductType productType = (ProductType) pm.getObjectById(productTypeID);
            ProductTypeStatusHistoryItem productTypeStatusHistoryItem = store
                    .setProductTypeStatus_saleable(User.getUser(pm, getPrincipal()), productType, saleable);

            if (productTypeStatusHistoryItem == null || !get)
                return null;
            else
                return pm.detachCopy(productTypeStatusHistoryItem);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#setProductTypeStatus_closed(org.nightlabs.jfire.store.id.ProductTypeID, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editConfirmedProductType")
    @Override
    public ProductTypeStatusHistoryItem setProductTypeStatus_closed(ProductTypeID productTypeID, boolean get,
            String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            if (get) {
                pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
                if (fetchGroups != null)
                    pm.getFetchPlan().setGroups(fetchGroups);
            }

            pm.getExtent(ProductType.class);
            Store store = Store.getStore(pm);
            ProductType productType = (ProductType) pm.getObjectById(productTypeID);
            ProductTypeStatusHistoryItem productTypeStatusHistoryItem = store
                    .setProductTypeStatus_closed(User.getUser(pm, getPrincipal()), productType);

            if (productTypeStatusHistoryItem == null || !get)
                return null;
            else
                return pm.detachCopy(productTypeStatusHistoryItem);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getModeOfDeliveryFlavourProductTypeGroupCarrier(java.util.Collection, java.util.Collection, byte, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("_Guest_")
    @Override
    public ModeOfDeliveryFlavourProductTypeGroupCarrier getModeOfDeliveryFlavourProductTypeGroupCarrier(
            Collection<ProductTypeID> productTypeIDs, Collection<CustomerGroupID> customerGroupIDs, byte mergeMode,
            boolean filterByConfig, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            ModeOfDeliveryFlavourProductTypeGroupCarrier res = ModeOfDeliveryFlavour
                    .getModeOfDeliveryFlavourProductTypeGroupCarrier(pm, productTypeIDs, customerGroupIDs,
                            mergeMode, filterByConfig);

            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            ModeOfDeliveryFlavourProductTypeGroupCarrier res_detached = new ModeOfDeliveryFlavourProductTypeGroupCarrier(
                    customerGroupIDs);
            for (Iterator<ModeOfDeliveryFlavour> it = res.getModeOfDeliveryFlavours().iterator(); it.hasNext();) {
                ModeOfDeliveryFlavour modf = it.next();

                res_detached.addModeOfDeliveryFlavour(pm.detachCopy(modf));
            }

            for (Iterator<ModeOfDeliveryFlavourProductTypeGroup> it = res
                    .getModeOfDeliveryFlavourProductTypeGroups().iterator(); it.hasNext();) {
                ModeOfDeliveryFlavourProductTypeGroup group = it.next();
                res_detached.addModeOfDeliveryFlavourProductTypeGroup(group);
            }

            return res_detached;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getModeOfDeliverys(java.util.Set, java.lang.String[], int)
     */
    @RolesAllowed("_Guest_")
    @Override
    public Collection<ModeOfDelivery> getModeOfDeliverys(Set<ModeOfDeliveryID> modeOfDeliveryIDs,
            String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, modeOfDeliveryIDs, ModeOfDelivery.class, fetchGroups,
                    maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getAllModeOfDeliveryFlavourIDs()
     */
    @RolesAllowed("_Guest_")
    @Override
    public Set<ModeOfDeliveryFlavourID> getAllModeOfDeliveryFlavourIDs() {
        PersistenceManager pm = createPersistenceManager();
        try {
            return ModeOfDeliveryFlavour.getAllModeOfDeliveryFlavourIDs(pm);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getAllModeOfDeliveryIDs()
     */
    @RolesAllowed("_Guest_")
    @Override
    public Set<ModeOfDeliveryID> getAllModeOfDeliveryIDs() {
        PersistenceManager pm = createPersistenceManager();
        try {
            return ModeOfDelivery.getAllModeOfDeliveryIDs(pm);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getModeOfDeliveryFlavours(java.util.Set, java.lang.String[], int)
     */
    @RolesAllowed("_Guest_")
    @Override
    public Collection<ModeOfDeliveryFlavour> getModeOfDeliveryFlavours(
            Set<ModeOfDeliveryFlavourID> modeOfDeliveryFlavourIDs, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, modeOfDeliveryFlavourIDs, ModeOfDeliveryFlavour.class,
                    fetchGroups, maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getServerDeliveryProcessorsForOneModeOfDeliveryFlavour(org.nightlabs.jfire.store.deliver.id.ModeOfDeliveryFlavourID, org.nightlabs.jfire.store.deliver.CheckRequirementsEnvironment, java.lang.String[], int)
     */
    @RolesAllowed("_Guest_")
    @Override
    public Collection<ServerDeliveryProcessor> getServerDeliveryProcessorsForOneModeOfDeliveryFlavour(
            ModeOfDeliveryFlavourID modeOfDeliveryFlavourID,
            CheckRequirementsEnvironment checkRequirementsEnvironment, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            Collection<ServerDeliveryProcessor> c = ServerDeliveryProcessor
                    .getServerDeliveryProcessorsForOneModeOfDeliveryFlavour(pm, modeOfDeliveryFlavourID);

            for (ServerDeliveryProcessor pp : c) {
                pp.checkRequirements(checkRequirementsEnvironment);
            }

            // Because the checkRequirements method might have manipulated the fetch-plan, we set it again.
            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            return pm.detachCopyAll(c);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#createDeliveryNote(java.util.Collection, java.lang.String, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editDeliveryNote")
    @Override
    public DeliveryNote createDeliveryNote(Collection<ArticleID> articleIDs, String deliveryNoteIDPrefix,
            boolean get, String[] fetchGroups, int maxFetchDepth) throws DeliveryNoteEditException {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getExtent(Article.class);
            User user = User.getUser(pm, getPrincipal());
            Trader trader = Trader.getTrader(pm);
            Store store = trader.getStore();

            List<Article> articles = new ArrayList<Article>(articleIDs.size());
            for (ArticleID articleID : articleIDs) {
                Article article = (Article) pm.getObjectById(articleID);
                Offer offer = article.getOffer();
                trader.validateOffer(offer);
                trader.acceptOfferImplicitely(offer);
                articles.add(article);
            }

            DeliveryNote deliveryNote = store.createDeliveryNote(user, articles, deliveryNoteIDPrefix);
            store.validateDeliveryNote(deliveryNote);

            if (get) {
                pm.getFetchPlan()
                        .setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS + FetchPlan.DETACH_UNLOAD_FIELDS);
                pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
                if (fetchGroups != null)
                    pm.getFetchPlan().setGroups(fetchGroups);

                return pm.detachCopy(deliveryNote);
            }
            return null;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#createDeliveryNote(org.nightlabs.jfire.trade.id.ArticleContainerID, java.lang.String, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editDeliveryNote")
    @Override
    public DeliveryNote createDeliveryNote(ArticleContainerID articleContainerID, String deliveryNoteIDPrefix,
            boolean get, String[] fetchGroups, int maxFetchDepth) throws DeliveryNoteEditException {
        PersistenceManager pm = createPersistenceManager();
        try {
            if (articleContainerID == null)
                throw new IllegalArgumentException("articleContainerID must not be null!");

            if (articleContainerID instanceof OrderID)
                pm.getExtent(Order.class);
            else if (articleContainerID instanceof OfferID)
                pm.getExtent(Offer.class);
            else if (articleContainerID instanceof InvoiceID)
                pm.getExtent(Invoice.class);
            else
                throw new IllegalArgumentException(
                        "articleContainerID must be an instance of OrderID, OfferID or InvoiceID, but is "
                                + articleContainerID.getClass().getName());

            ArticleContainer articleContainer = (ArticleContainer) pm.getObjectById(articleContainerID);

            User user = User.getUser(pm, getPrincipal());
            Trader trader = Trader.getTrader(pm);
            Store store = trader.getStore();

            if (articleContainer instanceof Offer) {
                Offer offer = (Offer) articleContainer;
                //            OfferLocal offerLocal = offer.getOfferLocal();
                trader.validateOffer(offer);
                trader.acceptOfferImplicitely(offer);
                //            trader.finalizeOffer(user, offer);
                //            trader.acceptOffer(user, offerLocal);
                //            trader.confirmOffer(user, offerLocal);
            } else {
                for (Iterator<Article> it = articleContainer.getArticles().iterator(); it.hasNext();) {
                    Article article = it.next();
                    Offer offer = article.getOffer();
                    //               OfferLocal offerLocal = offer.getOfferLocal();
                    trader.validateOffer(offer);
                    trader.acceptOfferImplicitely(offer);
                    //               trader.finalizeOffer(user, offer);
                    //               trader.acceptOffer(user, offerLocal);
                    //               trader.confirmOffer(user, offerLocal);
                }
            }

            DeliveryNote deliveryNote = store.createDeliveryNote(user, articleContainer, deliveryNoteIDPrefix);
            store.validateDeliveryNote(deliveryNote);

            if (get) {
                pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
                pm.getFetchPlan()
                        .setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS + FetchPlan.DETACH_UNLOAD_FIELDS);
                if (fetchGroups != null)
                    pm.getFetchPlan().setGroups(fetchGroups);

                return pm.detachCopy(deliveryNote);
            }
            return null;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#addArticlesToDeliveryNote(org.nightlabs.jfire.store.id.DeliveryNoteID, java.util.Collection, boolean, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editDeliveryNote")
    @Override
    public DeliveryNote addArticlesToDeliveryNote(DeliveryNoteID deliveryNoteID, Collection<ArticleID> articleIDs,
            boolean validate, boolean get, String[] fetchGroups, int maxFetchDepth)
            throws DeliveryNoteEditException {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getExtent(DeliveryNote.class);
            pm.getExtent(Article.class);
            Trader trader = Trader.getTrader(pm);

            DeliveryNote deliveryNote = (DeliveryNote) pm.getObjectById(deliveryNoteID);
            Collection<Article> articles = new ArrayList<Article>(articleIDs.size());
            for (ArticleID articleID : articleIDs) {
                Article article = (Article) pm.getObjectById(articleID);
                Offer offer = article.getOffer();
                trader.validateOffer(offer);
                trader.acceptOfferImplicitely(offer);
                articles.add(article);
            }

            Store store = Store.getStore(pm);
            store.addArticlesToDeliveryNote(User.getUser(pm, getPrincipal()), deliveryNote, articles);

            if (validate)
                store.validateDeliveryNote(deliveryNote);

            if (!get)
                return null;

            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            return pm.detachCopy(deliveryNote);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#removeArticlesFromDeliveryNote(org.nightlabs.jfire.store.id.DeliveryNoteID, java.util.Collection, boolean, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editDeliveryNote")
    @Override
    public DeliveryNote removeArticlesFromDeliveryNote(DeliveryNoteID deliveryNoteID,
            Collection<ArticleID> articleIDs, boolean validate, boolean get, String[] fetchGroups,
            int maxFetchDepth) throws DeliveryNoteEditException {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getExtent(DeliveryNote.class);
            pm.getExtent(Article.class);
            DeliveryNote deliveryNote = (DeliveryNote) pm.getObjectById(deliveryNoteID);
            Collection<Article> articles = new ArrayList<Article>(articleIDs.size());
            for (Iterator<ArticleID> it = articleIDs.iterator(); it.hasNext();) {
                ArticleID articleID = it.next();
                articles.add((Article) pm.getObjectById(articleID));
            }

            Store store = Store.getStore(pm);
            store.removeArticlesFromDeliveryNote(User.getUser(pm, getPrincipal()), deliveryNote, articles);

            if (validate)
                store.validateDeliveryNote(deliveryNote);

            if (!get)
                return null;

            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            return pm.detachCopy(deliveryNote);
        } finally {
            pm.close();
        }
    }

    @EJB
    private StoreManagerLocal storeManagerLocal;

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverBegin(java.util.List)
     */
    @RolesAllowed("org.nightlabs.jfire.store.deliver")
    @Override
    public List<DeliveryResult> deliverBegin(List<DeliveryData> deliveryDataList) {
        try {
            List<DeliveryResult> resList = new ArrayList<DeliveryResult>();
            for (Iterator<DeliveryData> it = deliveryDataList.iterator(); it.hasNext();) {
                DeliveryData deliveryData = it.next();

                DeliveryResult res = null;
                try {
                    res = storeManagerLocal._deliverBegin(deliveryData);
                } catch (Throwable t) {
                    DeliveryException x = null;
                    if (t instanceof DeliveryException)
                        x = (DeliveryException) t;
                    else {
                        int i = ExceptionUtils.indexOfThrowable(t, DeliveryException.class);
                        if (i >= 0)
                            x = (DeliveryException) ExceptionUtils.getThrowables(t)[i];
                    }
                    if (x != null)
                        res = x.getDeliveryResult();
                    else
                        res = new DeliveryResult(t);
                }
                if (res != null)
                    resList.add(res);

            }
            return resList;
        } catch (RuntimeException x) {
            throw x;
        } catch (Exception x) {
            throw new RuntimeException(x);
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverInSingleTransaction(org.nightlabs.jfire.store.deliver.DeliveryData)
     */
    @RolesAllowed("org.nightlabs.jfire.store.deliver")
    @Override
    public DeliveryResult[] deliverInSingleTransaction(DeliveryData deliveryData) throws DeliveryException {
        if (deliveryData == null)
            throw new IllegalArgumentException("deliveryData == null");

        {
            Delivery delivery = deliveryData.getDelivery();
            if (delivery == null)
                throw new IllegalArgumentException("deliveryData.getDelivery() == null");

            if (delivery.getDeliverBeginClientResult() == null)
                throw new IllegalArgumentException(
                        "deliveryData.getDelivery().getDeliverBeginClientResult() == null");

            if (delivery.getDeliverDoWorkClientResult() == null)
                throw new IllegalArgumentException(
                        "deliveryData.getDelivery().getDeliverDoWorkClientResult() == null");

            if (delivery.getDeliverEndClientResult() == null)
                throw new IllegalArgumentException(
                        "deliveryData.getDelivery().getDeliverEndClientResult() == null");
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            DeliveryResult[] result = new DeliveryResult[3];

            User user = User.getUser(pm, getPrincipal());
            deliveryData = DeliveryHelperBean.deliverBegin_storeDeliveryData(pm, user, deliveryData);
            DeliveryID deliveryID = (DeliveryID) JDOHelper.getObjectId(deliveryData.getDelivery());
            if (deliveryID == null)
                throw new IllegalStateException("JDOHelper.getObjectId(deliveryData.getDelivery()) returned null!");

            DeliveryResult deliverBeginServerResult = Store.getStore(pm).deliverBegin(user, deliveryData);
            deliverBeginServerResult = pm.makePersistent(deliverBeginServerResult);
            deliveryData.getDelivery().setDeliverBeginServerResult(deliverBeginServerResult);
            result[0] = deliverBeginServerResult;
            if (deliverBeginServerResult.isFailed())
                throw new DeliveryException(deliverBeginServerResult);

            DeliveryResult deliverDoWorkServerResult = Store.getStore(pm).deliverDoWork(user, deliveryData);
            deliverDoWorkServerResult = pm.makePersistent(deliverDoWorkServerResult);
            deliveryData.getDelivery().setDeliverDoWorkServerResult(deliverDoWorkServerResult);
            result[1] = deliverDoWorkServerResult;
            if (deliverDoWorkServerResult.isFailed())
                throw new DeliveryException(deliverDoWorkServerResult);

            DeliveryResult deliverEndServerResult = Store.getStore(pm).deliverEnd(user, deliveryData);

            if (deliveryData.getDelivery().isFailed()) { // FIXME this is already called within deliverEnd - should it really be called twice?!?! Marco.
                Store.getStore(pm).deliverRollback(user, deliveryData);
            }

            deliverEndServerResult = pm.makePersistent(deliverEndServerResult);
            deliveryData.getDelivery().setDeliverEndServerResult(deliverEndServerResult);
            result[2] = deliverEndServerResult;
            if (deliverEndServerResult.isFailed())
                throw new DeliveryException(deliverEndServerResult);

            Collection<DeliveryNoteID> deliveryNoteIDs = deliveryData.getDelivery().getDeliveryNoteIDs();
            try {
                AsyncInvoke.exec(new CrossTradeDeliverInvocation(deliveryID), true);
                AsyncInvoke.exec(new ConsolidateProductReferencesInvocation(deliveryNoteIDs, 5000), true);
            } catch (AsyncInvokeEnqueueException e) {
                throw new RuntimeException(e);
            }

            return result;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverBegin(org.nightlabs.jfire.store.deliver.DeliveryData)
     */
    @RolesAllowed("org.nightlabs.jfire.store.deliver")
    @Override
    public DeliveryResult deliverBegin(DeliveryData deliveryData) {
        return _deliverBegin(deliveryData);
    }

    @EJB
    private DeliveryHelperLocal deliveryHelperLocal;

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerLocal#_deliverBegin(org.nightlabs.jfire.store.deliver.DeliveryData)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    @RolesAllowed("_Guest_")
    @Override
    public DeliveryResult _deliverBegin(DeliveryData deliveryData) {
        if (logger.isDebugEnabled()) {
            logger.debug("_deliverBegin: *** begin ******************************************* ");
            logger.debug("_deliverBegin: IDGenerator.getOrganisationID()=" + IDGenerator.getOrganisationID());
            logger.debug("_deliverBegin: this.getOrganisationID()=" + this.getOrganisationID());
        }

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

        if (deliveryData.getDelivery() == null)
            throw new NullPointerException("deliveryData.getDelivery() is null!");

        //      if (deliveryData.getDelivery().getPartnerID() == null) {
        //         // if no partner is defined, at least one deliveryNote must be known!
        //         if (deliveryData.getDelivery().getDeliveryNoteIDs() == null)
        //            throw new NullPointerException("deliveryData.getDelivery().getPartnerID() and deliveryData.getDelivery().getDeliveryNoteIDs() are both null! One of them must be specified, because I need to know who's delivering!");
        //
        //         if (deliveryData.getDelivery().getDeliveryNoteIDs().isEmpty())
        //            throw new NullPointerException("deliveryData.getDelivery().getPartnerID() is null and deliveryData.getDelivery().getDeliveryNoteIDs() is empty! If no partner is specified explicitely, I need at least one invoice to find out who's delivering!");
        //      }
        if (deliveryData.getDelivery().getArticleIDs() == null)
            throw new IllegalArgumentException("deliveryData.getDelivery().getArticleIDs() is null!");

        if (deliveryData.getDelivery().getArticleIDs().isEmpty())
            throw new IllegalArgumentException("deliveryData.getDelivery().getArticleIDs() is empty!");

        //      if (deliveryData.getDelivery().getCurrencyID() == null)
        //         throw new NullPointerException("deliveryData.getDelivery().getCurrencyID() is null!");
        //
        //      if (deliveryData.getDelivery().getAmount() < 0)
        //         throw new IllegalArgumentException("deliveryData.getDelivery().getAmount() < 0!");

        if (deliveryData.getDelivery().getModeOfDeliveryFlavourID() == null)
            throw new IllegalArgumentException("deliveryData.getDelivery().getModeOfDeliveryFlavourID() is null!");

        if (deliveryData.getDelivery().getClientDeliveryProcessorFactoryID() == null)
            throw new IllegalArgumentException(
                    "deliveryData.getDelivery().getClientDeliveryProcessorFactoryID() is null!");

        if (deliveryData.getDelivery().getDeliverBeginClientResult() == null)
            throw new IllegalArgumentException("deliveryData.getDelivery().getDeliverBeginClientResult() is null!");

        // Store deliveryData into the database within a NEW TRANSACTION to prevent it
        // from being deleted (if this method fails later and causes a rollback).
        DeliveryDataID deliveryDataID = deliveryHelperLocal.deliverBegin_storeDeliveryData(deliveryData);

        String[] fetchGroups = new String[] { FetchPlan.DEFAULT };

        try {

            return deliveryHelperLocal.deliverBegin_internal(deliveryDataID, fetchGroups,
                    NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);

        } catch (Throwable t) {
            DeliveryResult deliverBeginServerResult = new DeliveryResult(t);

            try {
                return deliveryHelperLocal.deliverBegin_storeDeliverBeginServerResult(
                        DeliveryID.create(deliveryDataID), deliverBeginServerResult, true, fetchGroups,
                        NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);
            } catch (RuntimeException x) {
                throw x;
            } catch (Exception x) {
                throw new RuntimeException(x);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverEnd(java.util.List, java.util.List, boolean)
     */
    @RolesAllowed("_Guest_")
    @Override
    public List<DeliveryResult> deliverEnd(List<DeliveryID> deliveryIDs,
            List<DeliveryResult> deliverEndClientResults, boolean forceRollback) {
        try {
            if (deliveryIDs.size() != deliverEndClientResults.size())
                throw new IllegalArgumentException("deliveryIDs.size() != deliverEndClientResults.size()!!!");

            List<DeliveryResult> resList = new ArrayList<DeliveryResult>();
            Iterator<DeliveryResult> itResults = deliverEndClientResults.iterator();
            for (Iterator<DeliveryID> itIDs = deliveryIDs.iterator(); itIDs.hasNext();) {
                DeliveryID deliveryID = itIDs.next();
                DeliveryResult deliverEndClientResult = itResults.next();

                DeliveryResult res = null;
                try {
                    res = storeManagerLocal._deliverEnd(deliveryID, deliverEndClientResult, forceRollback);
                } catch (Throwable t) {
                    DeliveryException x = null;
                    if (t instanceof DeliveryException)
                        x = (DeliveryException) t;
                    else {
                        int i = ExceptionUtils.indexOfThrowable(t, DeliveryException.class);
                        if (i >= 0)
                            x = (DeliveryException) ExceptionUtils.getThrowables(t)[i];
                    }
                    if (x != null)
                        res = x.getDeliveryResult();
                    else
                        res = new DeliveryResult(t);
                }
                if (res != null)
                    resList.add(res);

            }
            return resList;
        } catch (RuntimeException x) {
            throw x;
        } catch (Exception x) {
            throw new RuntimeException(x);
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverDoWork(java.util.List, java.util.List, boolean)
     */
    @RolesAllowed("_Guest_")
    @Override
    public List<DeliveryResult> deliverDoWork(List<DeliveryID> deliveryIDs,
            List<DeliveryResult> deliverDoWorkClientResults, boolean forceRollback) {
        if (deliveryIDs.size() != deliverDoWorkClientResults.size())
            throw new IllegalArgumentException("deliveryIDs.size() != deliverDoWorkClientResults.size()!!!");

        List<DeliveryResult> resList = new ArrayList<DeliveryResult>();
        Iterator<DeliveryResult> itResults = deliverDoWorkClientResults.iterator();
        for (Iterator<DeliveryID> itIDs = deliveryIDs.iterator(); itIDs.hasNext();) {
            DeliveryID deliveryID = itIDs.next();
            DeliveryResult deliverDoWorkClientResult = itResults.next();

            DeliveryResult res = null;
            try {
                res = storeManagerLocal._deliverDoWork(deliveryID, deliverDoWorkClientResult, forceRollback);
            } catch (Throwable t) {
                DeliveryException x = null;
                if (t instanceof DeliveryException)
                    x = (DeliveryException) t;
                else {
                    int i = ExceptionUtils.indexOfThrowable(t, DeliveryException.class);
                    if (i >= 0)
                        x = (DeliveryException) ExceptionUtils.getThrowables(t)[i];
                }
                if (x != null)
                    res = x.getDeliveryResult();
                else
                    res = new DeliveryResult(t);
            }
            if (res != null)
                resList.add(res);

        }
        return resList;
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverDoWork(org.nightlabs.jfire.store.deliver.id.DeliveryID, org.nightlabs.jfire.store.deliver.DeliveryResult, boolean)
     */
    @RolesAllowed("_Guest_")
    @Override
    public DeliveryResult deliverDoWork(DeliveryID deliveryID, DeliveryResult deliverEndClientResult,
            boolean forceRollback) {
        return _deliverDoWork(deliveryID, deliverEndClientResult, forceRollback);
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerLocal#_deliverDoWork(org.nightlabs.jfire.store.deliver.id.DeliveryID, org.nightlabs.jfire.store.deliver.DeliveryResult, boolean)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    @RolesAllowed("_Guest_")
    @Override
    public DeliveryResult _deliverDoWork(DeliveryID deliveryID, DeliveryResult deliverDoWorkClientResult,
            boolean forceRollback) {
        if (deliveryID == null)
            throw new NullPointerException("deliveryID");

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

        // Store deliverDoWorkClientResult into the database within a NEW TRANSACTION to
        // prevent it from being lost (if this method fails later and causes a rollback).
        try {
            deliveryHelperLocal.deliverDoWork_storeDeliverDoWorkClientResult(deliveryID, deliverDoWorkClientResult,
                    forceRollback);
        } catch (RuntimeException x) {
            throw x;
        } catch (Exception x) {
            throw new RuntimeException(x);
        }

        String[] fetchGroups = new String[] { FetchPlan.DEFAULT };

        try {

            return deliveryHelperLocal.deliverDoWork_internal(deliveryID, fetchGroups,
                    NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);

        } catch (Throwable t) {
            DeliveryResult deliverDoWorkServerResult = new DeliveryResult(t);

            try {
                DeliveryResult deliverDoWorkServerResult_detached = deliveryHelperLocal
                        .deliverDoWork_storeDeliverDoWorkServerResult(deliveryID, deliverDoWorkServerResult, true,
                                fetchGroups, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);

                return deliverDoWorkServerResult_detached;
            } catch (RuntimeException x) {
                throw x;
            } catch (Exception x) {
                throw new RuntimeException(x);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#deliverEnd(org.nightlabs.jfire.store.deliver.id.DeliveryID, org.nightlabs.jfire.store.deliver.DeliveryResult, boolean)
     */
    @RolesAllowed("_Guest_")
    @Override
    public DeliveryResult deliverEnd(DeliveryID deliveryID, DeliveryResult deliverEndClientResult,
            boolean forceRollback) {
        return _deliverEnd(deliveryID, deliverEndClientResult, forceRollback);
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerLocal#_deliverEnd(org.nightlabs.jfire.store.deliver.id.DeliveryID, org.nightlabs.jfire.store.deliver.DeliveryResult, boolean)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    @RolesAllowed("_Guest_")
    @Override
    public DeliveryResult _deliverEnd(DeliveryID deliveryID, DeliveryResult deliverEndClientResult,
            boolean forceRollback) {
        if (deliveryID == null)
            throw new NullPointerException("deliveryID");

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

        // Store deliverEndClientResult into the database within a NEW TRANSACTION to
        // prevent it from being lost (if this method fails later and causes a rollback).
        try {
            deliveryHelperLocal.deliverEnd_storeDeliverEndClientResult(deliveryID, deliverEndClientResult,
                    forceRollback);
        } catch (RuntimeException x) {
            throw x;
        } catch (Exception x) {
            throw new RuntimeException(x);
        }

        String[] fetchGroups = new String[] { FetchPlan.DEFAULT };

        try {

            DeliveryResult res = deliveryHelperLocal.deliverEnd_internal(deliveryID, fetchGroups,
                    NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);
            if (!res.isFailed())
                AsyncInvoke.exec(new CrossTradeDeliverInvocation(deliveryID), false); // no xa necessary, because deliveryHelperLocal.deliverEnd_internal(...) used a new transaction before

            return res;

        } catch (Throwable t) {
            DeliveryResult deliverEndServerResult = new DeliveryResult(t);

            try {
                DeliveryResult deliverEndServerResult_detached = deliveryHelperLocal
                        .deliverEnd_storeDeliverEndServerResult(deliveryID, deliverEndServerResult, true,
                                fetchGroups, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);

                deliveryHelperLocal.deliverRollback(deliveryID);

                return deliverEndServerResult_detached;
            } catch (RuntimeException x) {
                throw x;
            } catch (Exception x) {
                throw new RuntimeException(x);
            }
        }
    }

    protected static class CrossTradeDeliverInvocation extends Invocation {
        private static final long serialVersionUID = 1L;

        private final DeliveryID deliveryID;

        /**
         * @param deliveryID This ID references the {@link Delivery} to the end-customer-side (or next-reseller-side). Hence, this
         *      <code>CrossTradeDeliverInvocation</code> needs to split the <code>Article</code>s contained in this delivery by
         *      back-hand supplier and by ProductType class, obtain the {@link ProductTypeActionHandler} for them and ask it
         *      for the correct {@link CrossTradeDeliveryCoordinator} (via {@link ProductTypeActionHandler#getCrossTradeDeliveryCoordinator()}).
         */
        public CrossTradeDeliverInvocation(DeliveryID deliveryID) {
            if (deliveryID == null)
                throw new IllegalArgumentException("deliveryID must not be null!");

            this.deliveryID = deliveryID;
        }

        @Override
        public Serializable invoke() throws Exception {
            PersistenceManager pm = getPersistenceManager();
            try {
                String localOrganisationID = getOrganisationID();
                User user = User.getUser(pm, getPrincipal());

                //         Map<Class, Map<String, Map<Boolean, Set<Article>>>> productTypeClass2organisationID2articleSet = null;
                Map<CrossTradeDeliveryCoordinator, Map<String, Map<Boolean, Set<Article>>>> productTypeClass2organisationID2articleSet = null;

                Delivery delivery = (Delivery) pm.getObjectById(deliveryID);
                for (Article article : delivery.getArticles()) {
                    ProductLocal productLocal = article.getProduct().getProductLocal();

                    for (ProductLocal nestedProductLocal : productLocal.getNestedProductLocals()) {
                        if (!localOrganisationID.equals(nestedProductLocal.getOrganisationID())) {
                            // supplier of the nested Product is a remote organisation => check, whether it's already here or needs delivery
                            //
                            // General info about ProductLocal.quantity:
                            // ProductLocal.quantity is > 0 (usually 1), if it's available (i.e. in this store AND not nested in another product)
                            // ProductLocal.quantity is = 0, if it's either (here in the store AND nested in another product) or (NOT here AND NOT nested)
                            // ProductLocal.quantity is < 0 (usually -1), if it's not here in the store AND nested in another product
                            //
                            // Info about ProductLocal.quantity at this point:
                            // Because we already delivered to our customer and now asynchronously obtain the supply, the quantity is normally -1.
                            // If the Product was already obtained before (e.g. because it was sold and refunded, but not given back to the supplier),
                            // then it should be 0, because the container has already been assembled and during assembling, the quantity of the nested
                            // product is decremented. Hence, in this case, it is here in the store, but not available (due to being part of another
                            // assembled product).
                            if (nestedProductLocal.getQuantity() < 0) {
                                // quantity indicates that the product is not here - is it still at supplier?

                                if (!(nestedProductLocal.getAnchor() instanceof Repository))
                                    throw new IllegalStateException(
                                            "The nested product is not in a Repository, but its anchor is of type \""
                                                    + (nestedProductLocal.getAnchor() == null ? null
                                                            : nestedProductLocal.getAnchor().getClass().getName())
                                                    + "\"! localArticle \"" + article.getPrimaryKey()
                                                    + "\" + nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey() + "\"");

                                Repository nestedProductLocalRepository = (Repository) nestedProductLocal
                                        .getAnchor();

                                if (!nestedProductLocalRepository.getRepositoryType().isOutside())
                                    throw new IllegalStateException(
                                            "The nested product is not outside, but has a quanity < 0! localArticle \""
                                                    + article.getPrimaryKey() + "\" + nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey() + "\"");

                                if (!nestedProductLocalRepository.getOwner().equals(OrganisationLegalEntity
                                        .getOrganisationLegalEntity(pm, nestedProductLocal.getOrganisationID())))
                                    throw new IllegalStateException(
                                            "The nested product is not in an outside Repository belonging to the supplier! Has it already been delivered to another customer? localArticle \""
                                                    + article.getPrimaryKey() + "\" + nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey() + "\"");

                                if (productTypeClass2organisationID2articleSet == null)
                                    //                        productTypeClass2organisationID2articleSet = new HashMap<Class, Map<String,Map<Boolean,Set<Article>>>>();
                                    productTypeClass2organisationID2articleSet = new HashMap<CrossTradeDeliveryCoordinator, Map<String, Map<Boolean, Set<Article>>>>();

                                ProductType nestedProductType = nestedProductLocal.getProduct().getProductType();
                                //                     Class nestedProductTypeClass = nestedProductTypeLocal.getClass();

                                if (nestedProductType.getDeliveryConfiguration() == null)
                                    throw new IllegalStateException(
                                            "productType.deliveryConfiguration is null!!! productType \""
                                                    + nestedProductType.getPrimaryKey() + "\" localArticle \""
                                                    + article.getPrimaryKey() + "\" + nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey() + "\"");

                                CrossTradeDeliveryCoordinator crossTradeDeliveryCoordinator = nestedProductType
                                        .getDeliveryConfiguration().getCrossTradeDeliveryCoordinator();
                                if (crossTradeDeliveryCoordinator == null)
                                    throw new IllegalStateException(
                                            "productType.deliveryConfiguration.crossTradeDeliveryCoordinator is null!!! productType \""
                                                    + nestedProductType.getPrimaryKey()
                                                    + "\" deliveryConfiguration \""
                                                    + nestedProductType.getDeliveryConfiguration().getPrimaryKey()
                                                    + "\" localArticle \"" + article.getPrimaryKey()
                                                    + "\" + nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey() + "\"");

                                Map<String, Map<Boolean, Set<Article>>> organisationID2articleSet = productTypeClass2organisationID2articleSet
                                        .get(crossTradeDeliveryCoordinator);
                                if (organisationID2articleSet == null) {
                                    organisationID2articleSet = new HashMap<String, Map<Boolean, Set<Article>>>();
                                    productTypeClass2organisationID2articleSet.put(crossTradeDeliveryCoordinator,
                                            organisationID2articleSet);
                                }

                                Map<Boolean, Set<Article>> direction2articleSet = organisationID2articleSet
                                        .get(nestedProductLocal.getOrganisationID());
                                if (direction2articleSet == null) {
                                    direction2articleSet = new HashMap<Boolean, Set<Article>>();
                                    organisationID2articleSet.put(nestedProductLocal.getOrganisationID(),
                                            direction2articleSet);
                                }

                                LegalEntity partner = OrganisationLegalEntity.getOrganisationLegalEntity(pm,
                                        nestedProductLocal.getOrganisationID());

                                Boolean directionIncoming = Boolean.TRUE;
                                if (article.isReversing()) // TODO this should imho never happen - maybe throw an exception?
                                    directionIncoming = Boolean.FALSE;

                                Set<Article> articleSet = direction2articleSet.get(directionIncoming);
                                if (articleSet == null) {
                                    articleSet = new HashSet<Article>();
                                    direction2articleSet.put(directionIncoming, articleSet);
                                }

                                // find the backhand-Article for this nestedProductLocal
                                OfferRequirement backhandOfferRequirement = OfferRequirement.getOfferRequirement(pm,
                                        article.getOffer(), true);

                                Offer backhandOffer = backhandOfferRequirement.getPartnerOffer(partner);
                                if (backhandOffer == null)
                                    throw new IllegalStateException(
                                            "Cannot find backhand-Offer for local Article \""
                                                    + article.getPrimaryKey() + "\" + and nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey()
                                                    + "\" and partnerLegalEntity \"" + partner.getPrimaryKey()
                                                    + "\"");

                                Article backhandArticle = Article.getAllocatedArticle(pm, backhandOffer,
                                        nestedProductLocal.getProduct());
                                if (backhandArticle == null)
                                    throw new IllegalStateException(
                                            "Cannot find backhand-Article for local Article \""
                                                    + article.getPrimaryKey() + "\" + and nestedProductLocal \""
                                                    + nestedProductLocal.getPrimaryKey() + "\"");

                                articleSet.add(backhandArticle);
                            } // if (nestedProductLocal.getQuantity() < 1) {
                        }
                    }
                }

                if (productTypeClass2organisationID2articleSet != null) {
                    //               for (Map.Entry<Class, Map<String, Map<Boolean, Set<Article>>>> me1 : productTypeClass2organisationID2articleSet.entrySet()) {
                    //                  Class productTypeClass = me1.getKey();
                    //                  ProductTypeActionHandler productTypeActionHandler = ProductTypeActionHandler.getProductTypeActionHandler(pm, productTypeClass);
                    //                  CrossTradeDeliveryCoordinator crossTradeDeliveryCoordinator = productTypeActionHandler.getCrossTradeDeliveryCoordinator();
                    for (Map.Entry<CrossTradeDeliveryCoordinator, Map<String, Map<Boolean, Set<Article>>>> me1 : productTypeClass2organisationID2articleSet
                            .entrySet()) {
                        CrossTradeDeliveryCoordinator crossTradeDeliveryCoordinator = me1.getKey();

                        for (Map.Entry<String, Map<Boolean, Set<Article>>> me2 : me1.getValue().entrySet()) {
                            //                     String organisationID = me2.getKey();

                            for (Map.Entry<Boolean, Set<Article>> me3 : me2.getValue().entrySet()) {
                                //                        Boolean directionIncoming = me3.getKey();

                                Set<Article> backhandArticles = me3.getValue();

                                // Because of transactional problems, crossTradeDeliveryCoordinator.performCrossTradeDelivery(...) will spawn an additional AsyncInvoke
                                // In the long run, we should implement a special "fast-track-delivery" which will be used between organisations and work within one
                                // transaction. See javadoc of the performCrossTradeDelivery method.
                                crossTradeDeliveryCoordinator.performCrossTradeDelivery(user, backhandArticles);
                            }
                        }
                    }
                }

                return null;
            } finally {
                pm.close();
            }
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getDeliveryNoteIDs(org.nightlabs.jfire.transfer.id.AnchorID, org.nightlabs.jfire.transfer.id.AnchorID, org.nightlabs.jfire.transfer.id.AnchorID, long, long)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.queryDeliveryNotes")
    @Override
    public List<DeliveryNoteID> getDeliveryNoteIDs(AnchorID vendorID, AnchorID customerID, AnchorID endCustomerID,
            long rangeBeginIdx, long rangeEndIdx) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return new ArrayList<DeliveryNoteID>(DeliveryNote.getDeliveryNoteIDs(pm, vendorID, customerID,
                    endCustomerID, rangeBeginIdx, rangeEndIdx));
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getDelivery(org.nightlabs.jfire.store.deliver.id.DeliveryID, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.deliver")
    @Override
    public Delivery getDelivery(DeliveryID deliveryID, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);
            Delivery delivery = (Delivery) pm.getObjectById(deliveryID);
            return pm.detachCopy(delivery);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getNonFinalizedDeliveryNotes(org.nightlabs.jfire.transfer.id.AnchorID, org.nightlabs.jfire.transfer.id.AnchorID, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.queryDeliveryNotes")
    @Override
    public List<DeliveryNote> getNonFinalizedDeliveryNotes(AnchorID vendorID, AnchorID customerID,
            String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);
            return (List<DeliveryNote>) pm
                    .detachCopyAll(DeliveryNote.getNonFinalizedDeliveryNotes(pm, vendorID, customerID));
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#signalDeliveryNote(org.nightlabs.jfire.store.id.DeliveryNoteID, java.lang.String)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editDeliveryNote")
    @Override
    public void signalDeliveryNote(DeliveryNoteID deliveryNoteID, String jbpmTransitionName) {
        PersistenceManager pm = createPersistenceManager();
        try {
            Store.getStore(pm).signalDeliveryNote(deliveryNoteID, jbpmTransitionName);
        } finally {
            pm.close();
        }
    }

    //   /**
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    * @!ejb.transaction type="Supports" @!This usually means that no transaction is opened which is significantly faster and recommended for all read-only EJB methods! Marco.
    //    */
    //   public Set<ProductID> getProductIDs(QueryCollection<? extends AbstractProductQuery> productQueries)
    //   {
    //      if (productQueries == null)
    //         return null;
    //
    //      if (!Product.class.isAssignableFrom(productQueries.getResultClass())) {
    //         throw new RuntimeException("Given QueryCollection has invalid return type! " +
    //               "Invalid return type= "+ productQueries.getResultClassName());
    //      }
    //
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setMaxFetchDepth(1);
    //         pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);
    //
    //         if (!(productQueries instanceof JDOQueryCollectionDecorator)){
    //            productQueries = new JDOQueryCollectionDecorator<AbstractProductQuery>(productQueries);
    //         }
    //         JDOQueryCollectionDecorator<AbstractProductQuery> queries =
    //            (JDOQueryCollectionDecorator<AbstractProductQuery>) productQueries;
    //
    //         queries.setPersistenceManager(pm);
    //
    //         Collection<Product> products = (Collection<Product>) queries.executeQueries();
    //
    //// TODO: Implement Authority checking here - only the role is missing - the rest is just the following line. marco.
    ////         products = Authority.filterSecuredObjects(pm, products, getPrincipal(), roleID);
    //
    //         return NLJDOHelper.getObjectIDSet(products);
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTypeIDs(org.nightlabs.jdo.query.QueryCollection)
     */
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Collection<ProductTypeID> getProductTypeIDs(
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries) {
        if (productTypeQueries == null)
            return null;

        if (!ProductType.class.isAssignableFrom(productTypeQueries.getResultClass())) {
            throw new RuntimeException("Given QueryCollection has invalid return type! " + "Invalid return type= "
                    + productTypeQueries.getResultClassName());
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            if (!(productTypeQueries instanceof JDOQueryCollectionDecorator)) {
                productTypeQueries = new JDOQueryCollectionDecorator<AbstractProductTypeQuery>(productTypeQueries);
            }
            @SuppressWarnings("unchecked")
            JDOQueryCollectionDecorator<AbstractProductTypeQuery> queries = (JDOQueryCollectionDecorator<AbstractProductTypeQuery>) productTypeQueries;

            queries.setPersistenceManager(pm);

            @SuppressWarnings("unchecked")
            Collection<ProductType> productTypes = (Collection<ProductType>) queries.executeQueries();

            // Commented out the following filtering, because that's already done by the query using the ProductTypePermissionFlagSets. Marco.
            //         productTypes = Authority.filterIndirectlySecuredObjects(
            //               pm,
            //               productTypes,
            //               getPrincipal(),
            //               RoleConstants.seeProductType,
            //               ResolveSecuringAuthorityStrategy.allow);

            return NLJDOHelper.getObjectIDList(productTypes);
        } finally {
            pm.close();
        }
    }

    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Collection<ProductTypeIDTreeNode> getProductTypeIDTree(
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries) {
        if (productTypeQueries == null)
            return null;

        if (!ProductType.class.isAssignableFrom(productTypeQueries.getResultClass())) {
            throw new RuntimeException("Given QueryCollection has invalid return type! Invalid return type= "
                    + productTypeQueries.getResultClassName());
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            if (!(productTypeQueries instanceof JDOQueryCollectionDecorator)) {
                productTypeQueries = new JDOQueryCollectionDecorator<AbstractProductTypeQuery>(productTypeQueries);
            }

            @SuppressWarnings("unchecked")
            JDOQueryCollectionDecorator<AbstractProductTypeQuery> queries = (JDOQueryCollectionDecorator<AbstractProductTypeQuery>) productTypeQueries;

            queries.setPersistenceManager(pm);

            @SuppressWarnings("unchecked")
            Collection<ProductType> productTypes = (Collection<ProductType>) queries.executeQueries();
            Collection<ProductTypeID> productTypeIDs = NLJDOHelper.getObjectIDList(productTypes);

            return ProductTypeIDTreeNode.buildTree(pm, productTypeIDs);
        } finally {
            pm.close();
        }
    }

    //// TODO @Daniel: this new method checked-in by you today, but it's not used anywhere. Is it really necessary? Marco.
    //   private Set<ProductTypeID> getInternalProductTypeIDs(QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries)
    //   {
    //      if (productTypeQueries == null)
    //         return null;
    //
    //      if (! ProductType.class.isAssignableFrom(productTypeQueries.getResultClass()))
    //      {
    //         throw new RuntimeException("Given QueryCollection has invalid return type! " +
    //               "Invalid return type= "+ productTypeQueries.getResultClassName());
    //      }
    //
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setMaxFetchDepth(1);
    //         pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);
    //
    //         if (! (productTypeQueries instanceof JDOQueryCollectionDecorator))
    //         {
    //            productTypeQueries = new JDOQueryCollectionDecorator<AbstractProductTypeQuery>(productTypeQueries);
    //         }
    //         JDOQueryCollectionDecorator<AbstractProductTypeQuery> queries =
    //            (JDOQueryCollectionDecorator<AbstractProductTypeQuery>) productTypeQueries;
    //
    //         queries.setPersistenceManager(pm);
    //
    //         Collection<ProductType> productTypes = (Collection<ProductType>) queries.executeQueries();
    //
    //         productTypes = Authority.filterIndirectlySecuredObjects(
    //               pm,
    //               productTypes,
    //               getPrincipal(),
    //               RoleConstants.seeProductType,
    //               ResolveSecuringAuthorityStrategy.allow);
    //
    //         return NLJDOHelper.getObjectIDSet(productTypes);
    //      } finally {
    //         pm.close();
    //      }
    //   }

    //   /**
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="org.nightlabs.jfire.store.seeProductType"
    //    * @!ejb.transaction type="Supports" @!This usually means that no transaction is opened which is significantly faster and recommended for all read-only EJB methods! Marco.
    //    */
    //   @SuppressWarnings("unchecked")
    //   public Set<ProductTypeID> getProductTypes(QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries)
    //   {
    //      return getProductTypeIDs(productTypeQueries);
    //   }

    //   /**
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="org.nightlabs.jfire.store.seeProductType"
    //    * @!ejb.transaction type="Supports" @!This usually means that no transaction is opened which is significantly faster and recommended for all read-only EJB methods! Marco.
    //    */
    //   @SuppressWarnings("unchecked")
    //   public Set<ProductTypeGroupID> getProductTypeGroupIDs(
    //         QueryCollection<? extends AbstractProductTypeGroupQuery> productTypeGroupQueries)
    //   {
    //      if (productTypeGroupQueries == null)
    //         return null;
    //
    //      if (! ProductTypeGroup.class.isAssignableFrom(productTypeGroupQueries.getResultClass()))
    //      {
    //         throw new RuntimeException("Given QueryCollection has invalid return type! " +
    //               "Invalid return type= "+ productTypeGroupQueries.getResultClassName());
    //      }
    //
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setMaxFetchDepth(1);
    //         pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);
    //
    //         if (! (productTypeGroupQueries instanceof JDOQueryCollectionDecorator))
    //         {
    //            productTypeGroupQueries = new JDOQueryCollectionDecorator<AbstractProductTypeGroupQuery>(productTypeGroupQueries);
    //         }
    //         JDOQueryCollectionDecorator<AbstractProductTypeGroupQuery> queries =
    //            (JDOQueryCollectionDecorator<AbstractProductTypeGroupQuery>) productTypeGroupQueries;
    //
    //         queries.setPersistenceManager(pm);
    //
    //         Collection<ProductTypeGroup> productTypeGroups_unfiltered = (Collection<ProductTypeGroup>) queries.executeQueries();
    //
    //         Collection<ProductTypeGroup> productTypeGroups = new ArrayList<ProductTypeGroup>(productTypeGroups_unfiltered.size());
    //         for (ProductTypeGroup productTypeGroup : productTypeGroups_unfiltered) {
    //            boolean hasFilteredProductTypes = !Authority.filterIndirectlySecuredObjects(
    //                  pm,
    //                  productTypeGroup.getProductTypes(),
    //                  getPrincipal(),
    //                  RoleConstants.seeProductType,
    //                  ResolveSecuringAuthorityStrategy.allow).isEmpty();
    //
    //            if (hasFilteredProductTypes)
    //               productTypeGroups.add(productTypeGroup);
    //         }
    //
    //         return NLJDOHelper.getObjectIDSet(productTypeGroups);
    //      } finally {
    //         pm.close();
    //      }
    //   }

    //   /**
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    * @!ejb.transaction type="Supports" @!This usually means that no transaction is opened which is significantly faster and recommended for all read-only EJB methods! Marco.
    //    */
    //   @SuppressWarnings("unchecked")
    //   public ProductTypeGroupIDSearchResult getProductTypeGroupSearchResult(
    //         QueryCollection<? extends AbstractProductTypeGroupQuery> productTypeGroupQueries)
    //   {
    //      if (productTypeGroupQueries == null)
    //         return null;
    //
    //      if (! ProductTypeGroup.class.isAssignableFrom(productTypeGroupQueries.getResultClass()))
    //      {
    //         throw new RuntimeException("Given QueryCollection has invalid return type! " +
    //               "Invalid return type= "+ productTypeGroupQueries.getResultClassName());
    //      }
    //
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setGroups(FetchPlan.DEFAULT);
    //
    //         if (! (productTypeGroupQueries instanceof JDOQueryCollectionDecorator))
    //         {
    //            productTypeGroupQueries = new JDOQueryCollectionDecorator<AbstractProductTypeGroupQuery>(productTypeGroupQueries);
    //         }
    //         JDOQueryCollectionDecorator<AbstractProductTypeGroupQuery> queries =
    //            (JDOQueryCollectionDecorator<AbstractProductTypeGroupQuery>) productTypeGroupQueries;
    //
    //         queries.setPersistenceManager(pm);
    //
    //         Collection<ProductTypeGroup> productTypeGroups = (Collection<ProductTypeGroup>) queries.executeQueries();
    //
    //         ProductTypeGroupIDSearchResult result = new ProductTypeGroupIDSearchResult();
    //         for (Iterator<? extends ProductTypeGroup> iter = productTypeGroups.iterator(); iter.hasNext();) {
    //            ProductTypeGroup group = iter.next();
    //            ProductTypeGroupID groupID = (ProductTypeGroupID) JDOHelper.getObjectId(group);
    //            result.addEntry(groupID);
    //            for (Iterator<ProductType> iterator = group.getProductTypes().iterator(); iterator.hasNext();) {
    //               ProductType type = iterator.next();
    //               //               ProductTypeID typeID = (ProductTypeID) JDOHelper.getObjectId(type);
    //               //               if (Authority.resolveSecuringAuthority(pm, type.getProductTypeLocal(), ResolveSecuringAuthorityStrategy.allow).containsRoleRef(getPrincipal(), RoleConstants.seeProductType))
    //               //                  result.addType(groupID, typeID);
    //               // The filtering is already done in the method ProductTypeGroup.getProductTypes() - no need to filter again.
    //               ProductTypeID typeID = (ProductTypeID) JDOHelper.getObjectId(type);
    //               result.addType(groupID, typeID);
    //            }
    //         }
    //         return result;
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTypeGroupIDSearchResultForProductTypeQueries(org.nightlabs.jdo.query.QueryCollection)
     */
    @RolesAllowed("_Guest_")
    @SuppressWarnings("unchecked")
    @Override
    public ProductTypeGroupIDSearchResult getProductTypeGroupIDSearchResultForProductTypeQueries(
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries) {
        long startTotal = System.currentTimeMillis();
        if (logger.isDebugEnabled())
            logger.debug("getProductTypeGroupIDSearchResultForProductTypeQueries: enter");

        if (productTypeQueries == null)
            return null;

        if (!ProductType.class.isAssignableFrom(productTypeQueries.getResultClass())) {
            throw new RuntimeException("Given QueryCollection has invalid return type! " + "Invalid return type= "
                    + productTypeQueries.getResultClassName());
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setGroups(FetchPlan.DEFAULT);

            if (!(productTypeQueries instanceof JDOQueryCollectionDecorator)) {
                productTypeQueries = new JDOQueryCollectionDecorator<AbstractProductTypeQuery>(productTypeQueries);
            }
            JDOQueryCollectionDecorator<AbstractProductTypeQuery> queries = (JDOQueryCollectionDecorator<AbstractProductTypeQuery>) productTypeQueries;

            queries.setPersistenceManager(pm);

            ProductTypeGroupIDSearchResult result = new ProductTypeGroupIDSearchResult();

            long startExecuteQueries = System.currentTimeMillis();
            Collection<ProductType> productTypes = (Collection<ProductType>) queries.executeQueries();
            if (logger.isDebugEnabled())
                logger.debug("getProductTypeGroupIDSearchResultForProductTypeQueries: executeQueries took "
                        + (System.currentTimeMillis() - startExecuteQueries) + " msec");

            for (ProductType productType : productTypes) {
                ProductTypeID productTypeID = (ProductTypeID) JDOHelper.getObjectId(productType);
                for (ProductTypeGroup productTypeGroup : productType.getProductTypeGroups()) {
                    ProductTypeGroupID productTypeGroupID = (ProductTypeGroupID) JDOHelper
                            .getObjectId(productTypeGroup);
                    result.addEntry(productTypeGroupID);
                    result.addType(productTypeGroupID, productTypeID);
                }
            }

            if (logger.isDebugEnabled())
                logger.debug("getProductTypeGroupIDSearchResultForProductTypeQueries: exit (took "
                        + (System.currentTimeMillis() - startTotal) + " msec)");

            return result;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getDeliveryQueuesById(java.util.Set, java.lang.String[], int)
     */
    @RolesAllowed("_Guest_")
    @Override
    public Collection<DeliveryQueue> getDeliveryQueuesById(Set<DeliveryQueueID> deliveryQueueIds,
            String[] fetchGroups, int fetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setFetchSize(fetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            Collection<DeliveryQueue> c = CollectionUtil.castCollection(pm.getObjectsById(deliveryQueueIds));
            return pm.detachCopyAll(c);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#storeDeliveryQueues(java.util.Collection, boolean, java.lang.String[], int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.editDeliveryQueue")
    @Override
    public Collection<DeliveryQueue> storeDeliveryQueues(Collection<DeliveryQueue> deliveryQueues, boolean get,
            String[] fetchGroups, int fetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setFetchSize(fetchDepth);
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);

            deliveryQueues = pm.makePersistentAll(deliveryQueues);

            if (get)
                return pm.detachCopyAll(deliveryQueues);
            else
                return null;
        } finally {
            pm.close();
        }
    }

    //   /**
    //    * Returns all {@link DeliveryQueue}s available.
    //    * @return All {@link DeliveryQueue}s available.
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    *
    //    * @param fetchGroups The desired fetch groups
    //    * @param fetchDepth The desired JDO fetch depth
    //    * @param includeDeleted Determines whether delivery queues marked as deleted are also returned.
    //    */
    //   public Collection<DeliveryQueue> getAvailableDeliveryQueues(String[] fetchGroups, int fetchDepth, boolean includeDeleted) {
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setMaxFetchDepth(fetchDepth);
    //
    //         if (fetchGroups != null)
    //            pm.getFetchPlan().setGroups(fetchGroups);
    //
    //         return pm.detachCopyAll(DeliveryQueue.getDeliveryQueues(pm, includeDeleted));
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getAvailableDeliveryQueueIDs(boolean)
     */
    @RolesAllowed("_Guest_")
    @Override
    public Collection<DeliveryQueueID> getAvailableDeliveryQueueIDs(boolean includeDefunct) {
        PersistenceManager pm = createPersistenceManager();
        try {
            //         return NLJDOHelper.getDetachedQueryResultAsList(pm, DeliveryQueue.getDeliveryQueueIDs(pm, includeDefunct));
            return new ArrayList<DeliveryQueueID>(DeliveryQueue.getDeliveryQueueIDs(pm, includeDefunct));
        } finally {
            pm.close();
        }
    }

    //   /**
    //    * Stores the given {@link DeliveryQueue}.
    //    * @param pq The {@link DeliveryQueue} to be stored.
    //    * @return A detached copy of the persisted {@link DeliveryQueue}.
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    */
    //   public DeliveryQueue storeDeliveryQueue(DeliveryQueue pq) {
    //      List<DeliveryQueue> tmp = new LinkedList<DeliveryQueue>();
    //      tmp.add(pq);
    //      return storeDeliveryQueues(tmp).get(0);
    //   }
    //
    //   /**
    //    * Stores all {@link DeliveryQueue}s in the given collection.
    //    * @param deliveryQueues The collection of the {@link DeliveryQueue}s to be stored.
    //    * @return A list of detached copies of the stored delivery queues.
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    */
    //   public List<DeliveryQueue> storeDeliveryQueues(Collection<DeliveryQueue> deliveryQueues) {
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         deliveryQueues = storeDeliveryQueues(deliveryQueues, pm);
    //         return (List<DeliveryQueue>) pm.detachCopyAll(deliveryQueues);
    //      } finally {
    //         pm.close();
    //      }
    //   }
    //
    //   /**
    //    * Returns all {@link DeliveryQueue}s available without the ones that have been marked as deleted.
    //    * @return All {@link DeliveryQueue}s available without the ones that have been marked as deleted.
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    *
    //    * @param fetchGroups The desired fetch groups
    //    * @param fetchDepth The desired JDO fetch depth
    //    */
    //   public Collection<DeliveryQueue> getAvailableDeliveryQueues(String[] fetchGroups, int fetchDepth) {
    //      return getAvailableDeliveryQueues(fetchGroups, fetchDepth, false);
    //   }
    //
    //   private List<DeliveryQueue> storeDeliveryQueues(Collection<DeliveryQueue> deliveryQueues, PersistenceManager pm) {
    //      List<DeliveryQueue> pqs = new LinkedList<DeliveryQueue>();
    //      for (DeliveryQueue clientPQ : deliveryQueues) {
    //         logger.debug("TicketingManagerBean.storeDeliveryQueue: Storing deliveryQueue " + clientPQ.getName().getText());
    //         clientPQ = pm.makePersistent(clientPQ);
    //         pqs.add(clientPQ);
    //      }
    //
    //      return pqs;
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getRepositoryIDs(org.nightlabs.jdo.query.QueryCollection)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryRepositories")
    @SuppressWarnings("unchecked")
    @Override
    public Set<AnchorID> getRepositoryIDs(QueryCollection<? extends AbstractJDOQuery> queries) {
        if (queries == null)
            return null;

        if (!Repository.class.isAssignableFrom(queries.getResultClass())) {
            throw new RuntimeException("Given QueryCollection has invalid return type! " + "Invalid return type= "
                    + queries.getResultClassName());
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            if (!(queries instanceof JDOQueryCollectionDecorator)) {
                queries = new JDOQueryCollectionDecorator<AbstractJDOQuery>(queries);
            }
            JDOQueryCollectionDecorator<AbstractJDOQuery> repoQueries = (JDOQueryCollectionDecorator<AbstractJDOQuery>) queries;

            repoQueries.setPersistenceManager(pm);
            Collection<? extends Repository> repositories = (Collection<? extends Repository>) repoQueries
                    .executeQueries();

            return NLJDOHelper.getObjectIDSet(repositories);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getRepositories(java.util.Collection, java.lang.String[], int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryRepositories")
    @Override
    public List<Repository> getRepositories(Collection<AnchorID> repositoryIDs, String[] fetchGroups,
            int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, repositoryIDs, Repository.class, fetchGroups,
                    maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#storeRepository(org.nightlabs.jfire.store.Repository, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.editRepository")
    @Override
    public Repository storeRepository(Repository repository, boolean get, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.storeJDO(pm, repository, get, fetchGroups, maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#storeUnit(org.nightlabs.jfire.store.Unit, boolean, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("_Guest_")
    @Override
    public Unit storeUnit(Unit unit, boolean get, String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.storeJDO(pm, unit, get, fetchGroups, maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    //   /**
    //    * This method is faster than {@link #getProductTransferIDs(Collection)}, because
    //    * it directly queries object-ids.
    //    *
    //    * @param productTransferIDQuery The query to execute.
    //    *
    //    * @ejb.interface-method
    //    * @!ejb.transaction type="Supports" @!This usually means that no transaction is opened which is significantly faster and recommended for all read-only EJB methods! Marco.
    //    * @ejb.permission role-name="_Guest_"
    //    */
    //   public List<TransferID> getProductTransferIDs(ProductTransferIDQuery productTransferIDQuery)
    //   {
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         productTransferIDQuery.setPersistenceManager(pm);
    //         Collection<TransferID> transferIDs = CollectionUtil.castCollection(productTransferIDQuery.getResult());
    //         return new ArrayList<TransferID>(transferIDs);
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTransferIDs(org.nightlabs.jdo.query.QueryCollection)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryProductTransfers")
    @Override
    public List<TransferID> getProductTransferIDs(
            QueryCollection<? extends ProductTransferQuery> productTransferQueries) {
        if (productTransferQueries == null)
            throw new IllegalArgumentException("productTransferQueries must not be null!");

        if (productTransferQueries.isEmpty())
            return new ArrayList<TransferID>(0);

        PersistenceManager pm = createPersistenceManager();
        try {
            if (ProductTransfer.class.isAssignableFrom(productTransferQueries.getResultClass())) {
                //            Collection<ProductTransfer> productTransfers = null;
                //            for (ProductTransferQuery productTransferQuery : productTransferQueries) {
                //               productTransferQuery.setPersistenceManager(pm);
                //               productTransferQuery.setCandidates(productTransfers);
                //               productTransfers = CollectionUtil.castCollection(productTransferQuery.getResult());
                //            }
                //            return NLJDOHelper.getObjectIDList(productTransfers);

                JDOQueryCollectionDecorator<? extends ProductTransferQuery> decorator = new JDOQueryCollectionDecorator<ProductTransferQuery>(
                        productTransferQueries);
                decorator.setPersistenceManager(pm);
                return NLJDOHelper.getObjectIDList(decorator.executeQueries());
            } else if (TransferID.class.isAssignableFrom(productTransferQueries.getResultClass())) {
                if (productTransferQueries.size() != 1)
                    throw new IllegalArgumentException(
                            "productTransferQueries has result-class TransferID, but contains more than 1 query. Since a query returning object-ids is not cascadable, this is illegal!");

                JDOQueryCollectionDecorator<? extends ProductTransferQuery> decorator = new JDOQueryCollectionDecorator<ProductTransferQuery>(
                        productTransferQueries);
                decorator.setPersistenceManager(pm);
                Collection<TransferID> productTransferIDs = CollectionUtil
                        .castCollection(decorator.executeQueries());
                return new ArrayList<TransferID>(productTransferIDs);
            } else
                throw new RuntimeException("Given QueryCollection has invalid return type! Invalid return type: "
                        + productTransferQueries.getResultClassName());
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTransfers(java.util.Collection, java.lang.String[], int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryProductTransfers")
    @Override
    public List<ProductTransfer> getProductTransfers(Collection<TransferID> productTransferIDs,
            String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, productTransferIDs, ProductTransfer.class, fetchGroups,
                    maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getRepositoryTypeIDs()
     */
    @RolesAllowed("_Guest_")
    @SuppressWarnings("unchecked")
    @Override
    public Set<RepositoryTypeID> getRepositoryTypeIDs() {
        PersistenceManager pm = createPersistenceManager();
        try {
            Query q = pm.newQuery(RepositoryType.class);
            q.setResult("JDOHelper.getObjectId(this)");
            return new HashSet<RepositoryTypeID>((Collection<? extends RepositoryTypeID>) q.execute());
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getRepositoryTypes(java.util.Collection, java.lang.String[], int)
     */
    @RolesAllowed("_Guest_")
    @Override
    public List<RepositoryType> getRepositoryTypes(Collection<RepositoryTypeID> repositoryTypeIDs,
            String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, repositoryTypeIDs, RepositoryType.class, fetchGroups,
                    maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    //   /**
    //    * @param productTypeGroupID The ID of the desired <code>ProductTypeGroup</code>.
    //    *
    //    * @ejb.interface-method
    //    * @ejb.transaction type="Required"
    //    * @ejb.permission role-name="_Guest_"
    //    */
    //   public ProductTypeGroup getProductTypeGroup(ProductTypeGroupID productTypeGroupID,
    //         String[] fetchGroups, int maxFetchDepth)
    //   throws ModuleException
    //   {
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         if (fetchGroups != null)
    //            pm.getFetchPlan().setGroups(fetchGroups);
    //         pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
    //         pm.getExtent(ProductTypeGroup.class);
    //         return (ProductTypeGroup) pm.detachCopy(pm.getObjectById(productTypeGroupID));
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTypeGroups(java.util.Collection, java.lang.String[], int)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Collection<ProductTypeGroup> getProductTypeGroups(Collection<ProductTypeGroupID> productTypeGroupIDs,
            String[] fetchGroups, int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            if (fetchGroups != null)
                pm.getFetchPlan().setGroups(fetchGroups);
            pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);

            pm.getExtent(ProductTypeGroup.class);
            ArrayList<ProductTypeGroup> productTypeGroups = new ArrayList<ProductTypeGroup>(
                    productTypeGroupIDs.size());
            for (ProductTypeGroupID productTypeGroupID : productTypeGroupIDs) {
                ProductTypeGroup productTypeGroup = (ProductTypeGroup) pm.getObjectById(productTypeGroupID);
                // The method ProductTypeGroup.getProductTypes() already filters the product-types by their
                // authorities. Therefore, we simply suppress those groups that contain no product type anymore after filtering.
                if (!productTypeGroup.getProductTypes().isEmpty())
                    productTypeGroups.add(productTypeGroup);
            }

            return pm.detachCopyAll(productTypeGroups);
        } finally {
            pm.close();
        }
    }

    // TODO: when all jpox bugs are fixed, implement generic storeProductType-Method
    //   /**
    //    * @return Returns a newly detached instance of <tt>ProductType</tt>
    //    * if <tt>get</tt> is true - otherwise <tt>null</tt>.
    //    *
    //    * @ejb.interface-method
    //    * @ejb.permission role-name="_Guest_"
    //    * @ejb.transaction type="Required"
    //    */
    //   public ProductType storeProductType(ProductType productType, boolean get, String[] fetchGroups, int maxFetchDepth)
    //   throws ModuleException
    //   {
    //      if (productType == null)
    //         throw new NullPointerException("productType");
    //
    //      PersistenceManager pm = getPersistenceManager();
    //      try {
    //         pm.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
    //         if (fetchGroups == null)
    //            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);
    //         else
    //            pm.getFetchPlan().setGroups(fetchGroups);
    //
    //         boolean priceCalculationNeeded = false;
    //         if (NLJDOHelper.exists(pm, productType)) {
    //            // if the nestedProductTypes changed, we need to recalculate prices
    //            // test first, whether they were detached
    //            Map<String, NestedProductTypeLocal> newNestedProductTypes = new HashMap<String, NestedProductTypeLocal>();
    //            try {
    //               for (NestedProductTypeLocal npt : productType.getNestedProductTypes()) {
    //                  newNestedProductTypes.put(npt.getInnerProductTypePrimaryKey(), npt);
    //                  npt.getQuantity();
    //               }
    //            } catch (JDODetachedFieldAccessException x) {
    //               newNestedProductTypes = null;
    //            }
    //
    //            if (newNestedProductTypes != null) {
    //               ProductType original = (ProductType) pm.getObjectById(JDOHelper.getObjectId(productType));
    //               priceCalculationNeeded = !ProductType.compareNestedProductTypes(original.getNestedProductTypes(), newNestedProductTypes);
    //            }
    //
    //            productType = (ProductType) pm.makePersistent(productType);
    //         }
    //         else {
    //            // TODO: ProductTypeActionHandler.getDefaultHome() should be abstract
    //            // and not static in implementation to make generic implementation possible
    //            productType = (ProductType) Store.getStore(pm).addProductType(
    //                  User.getUser(pm, getPrincipal()),
    //                  productType,
    //                  ProductTypeActionHandler.getProductTypeActionHandler(
    //                        pm, productType.getClass().getDefaultHome(pm, productType)));
    //
    //            // make sure the prices are correct
    //            priceCalculationNeeded = true;
    //         }
    //
    //         if (priceCalculationNeeded) {
    //            logger.info("storeProductType: price-calculation is necessary! Will recalculate the prices of " + JDOHelper.getObjectId(productType));
    //            if (productType.getPackagePriceConfig() != null && productType.getInnerPriceConfig() != null) {
    //               ((IResultPriceConfig)productType.getPackagePriceConfig()).adoptParameters(
    //                     productType.getInnerPriceConfig());
    //            }
    //
    //            // find out which productTypes package this one and recalculate their prices as well - recursively! siblings are automatically included in the package-recalculation
    //            HashSet<ProductTypeID> processedProductTypeIDs = new HashSet<ProductTypeID>();
    //            ProductTypeID productTypeID = (ProductTypeID) JDOHelper.getObjectId(productType);
    //            for (AffectedProductType apt : PriceConfigUtil.getAffectedProductTypes(pm, productType)) {
    //               if (!processedProductTypeIDs.add(apt.getProductTypeID()))
    //                  continue;
    //
    //               ProductType pt;
    //               if (apt.getProductTypeID().equals(productTypeID))
    //                  pt = productType;
    //               else
    //                  pt = (ProductType) pm.getObjectById(apt.getProductTypeID());
    //
    //               if (ProductType.PACKAGE_NATURE_OUTER == pt.getPackageNature() && pt.getPackagePriceConfig() != null) {
    //                  logger.info("storeProductType: price-calculation starting for: " + JDOHelper.getObjectId(pt));
    //
    //                  PriceCalculator priceCalculator = new PriceCalculator(pt, new CustomerGroupMapper(pm), new TariffMapper(pm));
    //                  priceCalculator.preparePriceCalculation();
    //                  priceCalculator.calculatePrices();
    //
    //                  logger.info("storeProductType: price-calculation complete for: " + JDOHelper.getObjectId(pt));
    //               }
    //            }
    //         }
    //         else
    //            logger.info("storeProductType: price-calculation is NOT necessary! Stored ProductType without recalculation: " + JDOHelper.getObjectId(productType));
    //
    //         // take care about the inheritance
    //         productType.applyInheritance();
    //         // imho, the recalculation of the prices for the inherited ProductTypes is already implemented in JFireTrade. Marco.
    //
    //         if (!get)
    //            return null;
    //
    //         return (ProductType) pm.detachCopy(productType);
    //      } finally {
    //         pm.close();
    //      }
    //   }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getReceptionNoteIDs(org.nightlabs.jdo.query.QueryCollection)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryReceptionNotes")
    @SuppressWarnings("unchecked")
    @Override
    public Set<ReceptionNoteID> getReceptionNoteIDs(QueryCollection<? extends AbstractJDOQuery> queries) {
        if (queries == null)
            return null;

        if (!ReceptionNote.class.isAssignableFrom(queries.getResultClass())) {
            throw new RuntimeException("Given QueryCollection has invalid return type! " + "Invalid return type= "
                    + queries.getResultClassName());
        }

        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            JDOQueryCollectionDecorator<AbstractJDOQuery> decoratedQueries;

            if (queries instanceof JDOQueryCollectionDecorator) {
                decoratedQueries = (JDOQueryCollectionDecorator<AbstractJDOQuery>) queries;
            } else {
                decoratedQueries = new JDOQueryCollectionDecorator<AbstractJDOQuery>(queries);
            }

            decoratedQueries.setPersistenceManager(pm);
            Collection<ReceptionNote> receptionNotes = (Collection<ReceptionNote>) decoratedQueries
                    .executeQueries();

            return NLJDOHelper.getObjectIDSet(receptionNotes);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getReceptionNotes(java.util.Set, java.lang.String[], int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.queryReceptionNotes")
    @Override
    public List<ReceptionNote> getReceptionNotes(Set<ReceptionNoteID> receptionNoteIDs, String[] fetchGroups,
            int maxFetchDepth) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return NLJDOHelper.getDetachedObjectList(pm, receptionNoteIDs, ReceptionNote.class, fetchGroups,
                    maxFetchDepth);
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getReservations(org.nightlabs.jfire.store.id.ProductTypeID, java.lang.String, int)
     */
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Set<OfferID> getReservations(ProductTypeID productTypeID, String fetchGroups, int maxFetchDepth) {
        // TODO; Maybe needs other role
        PersistenceManager pm = createPersistenceManager();
        try {
            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            QueryCollection<AbstractSearchQuery> queryCollection = new QueryCollection<AbstractSearchQuery>(
                    Offer.class);
            OfferQuery offerQuery = new OfferQuery();
            offerQuery.setProductTypeID(productTypeID);
            queryCollection.add(offerQuery);

            StatableQuery statableQuery = new StatableQuery();
            statableQuery.setStatableClass(Offer.class);
            // TODO set finalized as criteria for StatableQuery
            queryCollection.add(statableQuery);

            if (!(queryCollection instanceof JDOQueryCollectionDecorator)) {
                queryCollection = new JDOQueryCollectionDecorator<AbstractSearchQuery>(queryCollection);
            }
            JDOQueryCollectionDecorator<AbstractSearchQuery> queries = (JDOQueryCollectionDecorator<AbstractSearchQuery>) queryCollection;

            queries.setPersistenceManager(pm);

            Collection<? extends ProductType> productTypes = CollectionUtil
                    .castCollection(queries.executeQueries());

            productTypes = Authority.filterIndirectlySecuredObjects(pm, productTypes, getPrincipal(),
                    RoleConstants.seeProductType, ResolveSecuringAuthorityStrategy.allow);

            return NLJDOHelper.getObjectIDSet(productTypes);
        } finally {
            pm.close();
        }
    }

    private static final String calculateProductTypeAvailabilityPercentage_multiTxJobID = "calculateProductTypeAvailabilityPercentage";
    private static final int calculateProductTypeAvailabilityPercentage_chunkProductTypeQty = 3;

    /**
     * The process in {@link #calculateProductTypeAvailabilityPercentage(TaskID)} breaks after this duration has been reached.
     * Hence, it might take longer (if it is close to this duration and another chunk is begun).
     */
    private static final long calculateProductTypeAvailabilityPercentage_breakDurationMSec = 90L * 1000L;

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#calculateProductTypeAvailabilityPercentage(org.nightlabs.jfire.timer.id.TaskID)
     */
    @RolesAllowed("_System_")
    @Override
    public void calculateProductTypeAvailabilityPercentage(TaskID taskID) throws Exception {
        long startTimestamp = System.currentTimeMillis();
        boolean forceAtLeastOneChunk = true;
        PersistenceManager pm = createPersistenceManager();
        try {
            Map<Class<? extends ProductType>, ProductTypeActionHandler> productTypeClass2productTypeActionHandler = new HashMap<Class<? extends ProductType>, ProductTypeActionHandler>();

            Map<ProductTypeActionHandlerID, Map<ProductTypeID, Set<UserID>>> productTypeActionHandlerID2productTypeID2userIDs = CollectionUtil
                    .castMap((Map<?, ?>) MultiTxJob.popMultiTxJobPartData(pm,
                            calculateProductTypeAvailabilityPercentage_multiTxJobID));
            if (productTypeActionHandlerID2productTypeID2userIDs != null
                    && productTypeActionHandlerID2productTypeID2userIDs.isEmpty())
                productTypeActionHandlerID2productTypeID2userIDs = null;

            //         Map<ProductTypeActionHandler, Map<ProductType, Set<User>>> productTypeActionHandler2productType2users = new HashMap<ProductTypeActionHandler, Map<ProductType,Set<User>>>();

            if (productTypeActionHandlerID2productTypeID2userIDs != null) {
                if (logger.isDebugEnabled())
                    logger.debug(
                            "calculateProductTypeAvailabilityPercentage: entered with MultiTxJob data pending => won't query new data.");
            } else {
                if (logger.isDebugEnabled())
                    logger.debug(
                            "calculateProductTypeAvailabilityPercentage: entered without MultiTxJob data pending => querying fresh data.");

                productTypeActionHandlerID2productTypeID2userIDs = new HashMap<ProductTypeActionHandlerID, Map<ProductTypeID, Set<UserID>>>();
                forceAtLeastOneChunk = false;

                Query q = pm.newQuery(ProductTypePermissionFlagSet.class);
                q.setFilter("this.closed == false && this.expired == false");
                Collection<? extends ProductTypePermissionFlagSet> c = CollectionUtil
                        .castCollection((Collection<?>) q.execute());
                for (ProductTypePermissionFlagSet productTypePermissionFlagSet : c) {
                    ProductType productType = productTypePermissionFlagSet.getProductType();
                    Class<? extends ProductType> productTypeClass = productType.getClass();
                    ProductTypeID productTypeID = (ProductTypeID) JDOHelper.getObjectId(productType);
                    User user = productTypePermissionFlagSet.getUser();
                    UserID userID = (UserID) JDOHelper.getObjectId(user);

                    ProductTypeActionHandler productTypeActionHandler = productTypeClass2productTypeActionHandler
                            .get(productTypeClass);
                    if (productTypeActionHandler == null) {
                        productTypeActionHandler = ProductTypeActionHandler.getProductTypeActionHandler(pm,
                                productTypeClass);
                        productTypeClass2productTypeActionHandler.put(productTypeClass, productTypeActionHandler);
                    }
                    ProductTypeActionHandlerID productTypeActionHandlerID = (ProductTypeActionHandlerID) JDOHelper
                            .getObjectId(productTypeActionHandler);

                    Map<ProductTypeID, Set<UserID>> productTypeID2userIDs = productTypeActionHandlerID2productTypeID2userIDs
                            .get(productTypeActionHandlerID);
                    if (productTypeID2userIDs == null) {
                        productTypeID2userIDs = new HashMap<ProductTypeID, Set<UserID>>();
                        productTypeActionHandlerID2productTypeID2userIDs.put(productTypeActionHandlerID,
                                productTypeID2userIDs);
                    }

                    Set<UserID> userIDs = productTypeID2userIDs.get(productTypeID);
                    if (userIDs == null) {
                        userIDs = new HashSet<UserID>();
                        productTypeID2userIDs.put(productTypeID, userIDs);
                    }

                    userIDs.add(userID);
                }
            }

            long productTypesDoneCount = 0;
            mainLoop: for (Iterator<Map.Entry<ProductTypeActionHandlerID, Map<ProductTypeID, Set<UserID>>>> it1 = productTypeActionHandlerID2productTypeID2userIDs
                    .entrySet().iterator(); it1.hasNext();) {
                Map.Entry<ProductTypeActionHandlerID, Map<ProductTypeID, Set<UserID>>> me1 = it1.next();
                ProductTypeActionHandlerID productTypeActionHandlerID = me1.getKey();
                Map<ProductTypeID, Set<UserID>> productTypeID2userIDs = me1.getValue();
                ProductTypeActionHandler productTypeActionHandler = (ProductTypeActionHandler) pm
                        .getObjectById(productTypeActionHandlerID);

                Map<ProductType, Set<User>> productType2users = new HashMap<ProductType, Set<User>>(
                        calculateProductTypeAvailabilityPercentage_chunkProductTypeQty);
                Iterator<Map.Entry<ProductTypeID, Set<UserID>>> it2 = productTypeID2userIDs.entrySet().iterator();

                while (it2.hasNext()) {

                    if (!forceAtLeastOneChunk) {
                        if (System.currentTimeMillis()
                                - startTimestamp > calculateProductTypeAvailabilityPercentage_breakDurationMSec)
                            break mainLoop;
                    } else
                        forceAtLeastOneChunk = false;

                    for (int i = 0; i < calculateProductTypeAvailabilityPercentage_chunkProductTypeQty; ++i) {
                        if (!it2.hasNext())
                            continue;

                        Map.Entry<ProductTypeID, Set<UserID>> me2 = it2.next();
                        it2.remove();

                        if (me1.getValue().isEmpty())
                            it1.remove();

                        ProductType productType = (ProductType) pm.getObjectById(me2.getKey());
                        Set<UserID> userIDs = me2.getValue();
                        Set<User> users = NLJDOHelper.getObjectSet(pm, userIDs, User.class);
                        productType2users.put(productType, users);
                        ++productTypesDoneCount;
                    }

                    Map<ProductType, Map<User, Double>> percentages = productTypeActionHandler
                            .calculateProductTypeAvailabilityPercentage(productType2users);
                    for (Map.Entry<ProductType, Set<User>> me2 : productType2users.entrySet()) {
                        ProductType productType = me2.getKey();
                        for (User user : me2.getValue()) {
                            Map<User, Double> user2percentage = percentages.get(productType);
                            if (user2percentage == null)
                                throw new IllegalStateException("ProductTypeActionHandler "
                                        + productTypeActionHandler + " in organisation \"" + getOrganisationID()
                                        + "\" returned an incomplete result in method calculateProductTypeAvailabilityPercentage(...): No entry for ProductType: "
                                        + productType);

                            Double percentage = user2percentage.get(user);
                            if (percentage == null)
                                throw new IllegalStateException("ProductTypeActionHandler "
                                        + productTypeActionHandler + " in organisation \"" + getOrganisationID()
                                        + "\" returned an incomplete result in method calculateProductTypeAvailabilityPercentage(...) for ProductType \""
                                        + productType + "\": No entry for user: " + user);

                            ProductTypePermissionFlagSet productTypePermissionFlagSet = (ProductTypePermissionFlagSet) pm
                                    .getObjectById(ProductTypePermissionFlagSetID.create(productType, user));
                            productTypePermissionFlagSet.setAvailabilityPercentage(percentage);
                        }
                    }

                } // while (it.hasNext()) {
            } // mainLoop

            if (!productTypeActionHandlerID2productTypeID2userIDs.isEmpty())
                MultiTxJob.createMultiTxJobPart(pm, calculateProductTypeAvailabilityPercentage_multiTxJobID,
                        productTypeActionHandlerID2productTypeID2userIDs);

            if (logger.isDebugEnabled()) {
                long productTypesToDoCount = 0;
                for (Map.Entry<ProductTypeActionHandlerID, Map<ProductTypeID, Set<UserID>>> me1 : productTypeActionHandlerID2productTypeID2userIDs
                        .entrySet()) {
                    productTypesToDoCount += me1.getValue().size();
                }

                logger.debug("calculateProductTypeAvailabilityPercentage: exiting after having processed "
                        + productTypesDoneCount + " product types (still " + productTypesToDoCount + " to do).");
            }

        } finally {
            pm.close();
        }
    }

    private void initTimerTaskCalculateProductTypeAvailabilityPercentage(PersistenceManager pm) throws Exception {
        TaskID taskID = TaskID.create(getOrganisationID(), Task.TASK_TYPE_ID_SYSTEM,
                "calculateProductTypeAvailabilityPercentage");
        try {
            pm.getObjectById(taskID);
            return; // no JDOObjectNotFoundException => it exists already => return without creating it
        } catch (JDOObjectNotFoundException x) {
            // fine - it does not exist => create it below
        }
        Task task = new Task(taskID, User.getUser(pm, getOrganisationID(), User.USER_ID_SYSTEM),
                StoreManagerLocal.class, "calculateProductTypeAvailabilityPercentage");
        task = pm.makePersistent(task);
        task.getTimePatternSet().createTimePattern("*", "*", "*", "*", "*", "9-59/10");

        task.getName().setText(Locale.ENGLISH.getLanguage(), "Calculate product type availability");
        task.getDescription().setText(Locale.ENGLISH.getLanguage(),
                "This task calculates the availability percentage of all active product types.");

        task.getName().setText(Locale.GERMAN.getLanguage(), "Berechnung der Produkttyp-Verfgbarkeit");
        task.getDescription().setText(Locale.GERMAN.getLanguage(),
                "Dieser Task berechnet den Produkttyp-Verfgbarkeits-Prozentsatz fr alle aktiven Produkttypen.");
        task.setEnabled(true);
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getMyProductTypePermissionFlagSetIDs(java.util.Collection)
     */
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @RolesAllowed("_Guest_")
    @Override
    public Set<ProductTypePermissionFlagSetID> getMyProductTypePermissionFlagSetIDs(
            Collection<? extends ProductTypeID> productTypeIDs) {
        PersistenceManager pm = createPersistenceManager();
        try {
            User user = User.getUser(pm, getPrincipal());

            Collection<? extends ProductType> productTypes = CollectionUtil
                    .castCollection(pm.getObjectsById(productTypeIDs));

            Set<ProductTypePermissionFlagSetID> result = new HashSet<ProductTypePermissionFlagSetID>();
            for (ProductType productType : productTypes) {
                ProductTypePermissionFlagSet productTypePermissionFlagSet = ProductTypePermissionFlagSet
                        .getProductTypePermissionFlagSet(pm, productType, user, false);
                if (productTypePermissionFlagSet == null)
                    continue;

                result.add((ProductTypePermissionFlagSetID) JDOHelper.getObjectId(productTypePermissionFlagSet));
            }
            return result;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getProductTypePermissionFlagSets(java.util.Collection)
     */
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @RolesAllowed("_Guest_")
    @Override
    public Collection<ProductTypePermissionFlagSet> getProductTypePermissionFlagSets(
            Collection<? extends ProductTypePermissionFlagSetID> productTypePermissionFlagSetIDs) {
        PersistenceManager pm = createPersistenceManager();
        try {
            User user = User.getUser(pm, getPrincipal());

            List<ProductTypePermissionFlagSet> result = new ArrayList<ProductTypePermissionFlagSet>();
            Collection<? extends ProductTypePermissionFlagSet> ptpfss = CollectionUtil
                    .castCollection(pm.getObjectsById(productTypePermissionFlagSetIDs));
            for (ProductTypePermissionFlagSet ptpfs : ptpfss) {
                if (user.equals(ptpfs.getUser()))
                    result.add(ptpfs);
            }

            pm.getFetchPlan().setMaxFetchDepth(1);
            pm.getFetchPlan().setGroup(FetchPlan.DEFAULT);

            return pm.detachCopyAll(result);
        } finally {
            pm.close();
        }
    }

    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public long getRootProductTypeCount(Class<? extends ProductType> productTypeClass, boolean subclasses) {
        PersistenceManager pm = createPersistenceManager();
        try {
            return ProductType.getRootProductTypeCount(pm, productTypeClass, subclasses);
        } finally {
            pm.close();
        }
    }

    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Set<ProductTypeID> getRootProductTypeIDs(Class<? extends ProductType> productTypeClass,
            boolean subclasses) {
        PersistenceManager pm = createPersistenceManager();
        try {
            Collection<? extends ProductType> productTypes = ProductType.getRootProductTypes(pm, productTypeClass,
                    subclasses);

            productTypes = Authority.filterIndirectlySecuredObjects(pm, productTypes, getPrincipal(),
                    RoleConstants.seeProductType, ResolveSecuringAuthorityStrategy.allow);

            return NLJDOHelper.getObjectIDSet(productTypes);
        } finally {
            pm.close();
        }
    }

    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Map<ProductTypeID, Long> getChildProductTypeCounts(Collection<ProductTypeID> parentProductTypeIDs) {
        PersistenceManager pm = createPersistenceManager();
        try {
            Map<ProductTypeID, Long> result = new HashMap<ProductTypeID, Long>(parentProductTypeIDs.size());
            for (ProductTypeID parentProductTypeID : parentProductTypeIDs) {
                long count = ProductType.getChildProductTypeCount(pm, parentProductTypeID);
                result.put(parentProductTypeID, count);
            }
            return result;
        } finally {
            pm.close();
        }
    }

    /* (non-Javadoc)
     * @see org.nightlabs.jfire.store.StoreManagerRemote#getChildProductTypeIDs(org.nightlabs.jfire.store.id.ProductTypeID)
     */
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Collection<ProductTypeID> getChildProductTypeIDs(ProductTypeID parentProductTypeID) {
        PersistenceManager pm = createPersistenceManager();
        try {
            Collection<? extends ProductType> productTypes = getChildProductTypes(pm, parentProductTypeID, null,
                    true);
            return NLJDOHelper.getObjectIDList(productTypes);
        } finally {
            pm.close();
        }
    }

    private Collection<? extends ProductType> getChildProductTypes(PersistenceManager pm,
            ProductTypeID parentProductTypeID,
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries, boolean sort) {
        final String languageID = NLLocale.getDefault().getLanguage();
        Collection<? extends ProductType> productTypes;

        if (productTypeQueries != null && !productTypeQueries.isEmpty()) {
            if (!(productTypeQueries instanceof JDOQueryCollectionDecorator<?>)) {
                productTypeQueries = new JDOQueryCollectionDecorator<AbstractProductTypeQuery>(productTypeQueries);
            }
            @SuppressWarnings("unchecked")
            JDOQueryCollectionDecorator<AbstractProductTypeQuery> queries = (JDOQueryCollectionDecorator<AbstractProductTypeQuery>) productTypeQueries;

            queries.setPersistenceManager(pm);

            ////         queries.setCandidates(productTypes);
            //         // Unfortunately, this doesn't exist and I don't want to start more refactorings in the 1.0 branch now :-(
            //         // I hope, the following (not so clean) workaround works. Marco.
            //         for (AbstractProductTypeQuery q : queries) {
            //            q.setCandidates(productTypes);
            //         }

            for (AbstractProductTypeQuery q : queries) {
                q.setExtendedProductTypeID(parentProductTypeID);
                q.setFieldEnabled(AbstractProductTypeQuery.FieldName.extendedProductTypeID, true);
            }

            @SuppressWarnings("unchecked")
            Collection<? extends ProductType> pts = (Collection<? extends ProductType>) queries.executeQueries();
            productTypes = pts;
        } else {
            productTypes = ProductType.getChildProductTypes(pm, parentProductTypeID);
            sort = false; // prevent sorting again - the result of the above method is already sorted.
        }

        productTypes = Authority.filterIndirectlySecuredObjects(pm, productTypes, getPrincipal(),
                RoleConstants.seeProductType, ResolveSecuringAuthorityStrategy.allow);

        if (sort) {
            // TODO DataNucleus WORKAROUND: Sorting in JDOQL causes objects to be skipped (not found) when they do not have a name!
            // sort
            long loadStart = System.currentTimeMillis();
            productTypes = new ArrayList<ProductType>(productTypes);
            long loadDuration = System.currentTimeMillis() - loadStart;

            long sortStart = System.currentTimeMillis();
            @SuppressWarnings("unchecked")
            List<ProductType> pts = (List<ProductType>) productTypes;
            Collections.sort(pts, new Comparator<ProductType>() {
                @Override
                public int compare(ProductType o1, ProductType o2) {
                    return o1.getName().getText(languageID).compareTo(o2.getName().getText(languageID));
                }
            });
            if (logger.isDebugEnabled())
                logger.debug("getChildProductTypes: Loading " + productTypes.size() + " product types took "
                        + loadDuration + " msec and sorting with languageID=" + languageID + " took "
                        + (System.currentTimeMillis() - sortStart) + " msec.");
        }

        return productTypes;
    }

    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Map<ProductTypeID, Long> getChildProductTypeCounts(Collection<ProductTypeID> parentProductTypeIDs,
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries) {
        if (productTypeQueries == null || productTypeQueries.isEmpty())
            return getChildProductTypeCounts(parentProductTypeIDs);

        PersistenceManager pm = createPersistenceManager();
        try {
            Map<ProductTypeID, Long> result = new HashMap<ProductTypeID, Long>(parentProductTypeIDs.size());

            for (ProductTypeID parentProductTypeID : parentProductTypeIDs) {
                Collection<? extends ProductType> productTypes = getChildProductTypes(pm, parentProductTypeID,
                        Util.cloneSerializable(productTypeQueries), false);
                result.put(parentProductTypeID, Long.valueOf(productTypes.size()));
            }

            return result;
        } finally {
            pm.close();
        }
    }

    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Collection<ProductTypeID> getChildProductTypeIDs(ProductTypeID parentProductTypeID,
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries) {
        PersistenceManager pm = createPersistenceManager();
        try {
            Collection<? extends ProductType> productTypes = getChildProductTypes(pm, parentProductTypeID,
                    productTypeQueries, true);
            return NLJDOHelper.getObjectIDList(productTypes);
        } finally {
            pm.close();
        }
    }

    /**
     * @return the index position of the child's {@link ProductTypeID} with respect to the parent. If the child's {@link ProductTypeID} is
     * not found (i.e. that it is not a child of the given parent) then -1 is returned.
     */
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public int getChildProductTypeIDsPositionInList(ProductTypeID parentProductTypeID,
            ProductTypeID childProductTypeID,
            QueryCollection<? extends AbstractProductTypeQuery> productTypeQueries) {
        Collection<ProductTypeID> childProductTypeIDs = productTypeQueries != null
                ? getChildProductTypeIDs(parentProductTypeID, productTypeQueries)
                : getChildProductTypeIDs(parentProductTypeID);
        if (childProductTypeIDs == null || childProductTypeIDs.isEmpty()
                || !childProductTypeIDs.contains(childProductTypeID))
            return -1;

        int index = 0;
        for (ProductTypeID productTypeID : childProductTypeIDs) {
            if (productTypeID.equals(childProductTypeID))
                break;

            index++;
        }

        return index;
    }

    /**
     * Takes a {@link ProductTypeID}, searches for all {@link Article}s with the productTypeID reference, order them by their createDTs,
     * then depending on the desired type of {@link ArticleContainer}, return their unique IDs in a Set accordingly.
     * @return So, in effect, if we set isOrderByDescendingArticleDT to true, then returned Set of {@link ArticleContainerID}s are all
     * most recent ones. Otherwise, they are refer to the oldest {@link ArticleContainer}s.
     */
    @SuppressWarnings("unchecked")
    @RolesAllowed("org.nightlabs.jfire.store.seeProductType")
    @Override
    public Set<ArticleContainerID> getArticleContainerIDsByProductTypeID(ProductTypeID productTypeId,
            Class<? extends ArticleContainer> articleContainerClass, boolean isOrderByDescendingArticleDT) {
        PersistenceManager pm = createPersistenceManager();
        try {
            Query q = getQueryByArticleClass(pm, articleContainerClass);
            if (q == null)
                return null;

            q.setOrdering(isOrderByDescendingArticleDT ? "this.createDT DESC" : "this.createDT ASC");
            return NLJDOHelper.getObjectIDSet((Collection<? extends ArticleContainer>) q.execute(productTypeId)); // If unique entries are not desired, dont use getObjectIDSet(). Instead getObjectIDList().

        } finally {
            pm.close();
        }
    }

    /**
     * Sets up the {@link Query} for {@link ArticleContainer}-type classes.
     * @return null if articleContainerClass is not recognised.
     */
    private Query getQueryByArticleClass(PersistenceManager pm,
            Class<? extends ArticleContainer> articleContainerClass) {
        if (logger.isDebugEnabled())
            logger.debug("+++++++++++++ >>> Check: articleContainerClass.getName() = "
                    + articleContainerClass.getName());

        String resultType = "";
        if (articleContainerClass.getName().equals(Offer.class.getName()))
            resultType = "this.offer";
        else if (articleContainerClass.getName().equals(Order.class.getName()))
            resultType = "this.order";
        else if (articleContainerClass.getName().equals(Invoice.class.getName()))
            resultType = "this.invoice";
        else if (articleContainerClass.getName().equals(DeliveryNote.class.getName()))
            resultType = "this.deliveryNote";
        else if (articleContainerClass.getName().equals(ReceptionNote.class.getName()))
            resultType = "this.receptionNote";
        // Are there anymore known ArticleContainer class? Can we not hardcode these? We can use static String-fields in Article like in PropertySet...

        if (resultType.isEmpty())
            return null;

        Query q = pm.newQuery(Article.class);
        q.setResult(resultType);
        q.setFilter(resultType + " != null && JDOHelper.getObjectId(this.productType) == :productTypeId");

        return q;
    }

}