org.oscm.serviceprovisioningservice.bean.ServiceProvisioningServiceBean.java Source code

Java tutorial

Introduction

Here is the source code for org.oscm.serviceprovisioningservice.bean.ServiceProvisioningServiceBean.java

Source

/*******************************************************************************
 *  Copyright FUJITSU LIMITED 2016 
 *******************************************************************************/

package org.oscm.serviceprovisioningservice.bean;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Currency;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.interceptor.Interceptors;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;

import org.apache.commons.validator.GenericValidator;
import org.oscm.accountservice.assembler.OrganizationAssembler;
import org.oscm.accountservice.local.MarketingPermissionServiceLocal;
import org.oscm.applicationservice.local.ApplicationServiceLocal;
import org.oscm.communicationservice.local.CommunicationServiceLocal;
import org.oscm.configurationservice.local.ConfigurationServiceLocal;
import org.oscm.converter.ParameterizedTypes;
import org.oscm.converter.PriceConverter;
import org.oscm.dataservice.local.DataService;
import org.oscm.dataservice.local.QueryBasedObjectFactory;
import org.oscm.domobjects.CatalogEntry;
import org.oscm.domobjects.Category;
import org.oscm.domobjects.CategoryToCatalogEntry;
import org.oscm.domobjects.DomainObject;
import org.oscm.domobjects.Event;
import org.oscm.domobjects.ImageResource;
import org.oscm.domobjects.Marketplace;
import org.oscm.domobjects.OperationParameter;
import org.oscm.domobjects.Organization;
import org.oscm.domobjects.OrganizationRefToPaymentType;
import org.oscm.domobjects.Parameter;
import org.oscm.domobjects.ParameterDefinition;
import org.oscm.domobjects.ParameterOption;
import org.oscm.domobjects.ParameterSet;
import org.oscm.domobjects.PlatformUser;
import org.oscm.domobjects.PriceModel;
import org.oscm.domobjects.PricedEvent;
import org.oscm.domobjects.PricedOption;
import org.oscm.domobjects.PricedParameter;
import org.oscm.domobjects.PricedProductRole;
import org.oscm.domobjects.Product;
import org.oscm.domobjects.ProductReference;
import org.oscm.domobjects.ProductToPaymentType;
import org.oscm.domobjects.RevenueShareModel;
import org.oscm.domobjects.RoleDefinition;
import org.oscm.domobjects.SteppedPrice;
import org.oscm.domobjects.Subscription;
import org.oscm.domobjects.SupportedCurrency;
import org.oscm.domobjects.Tag;
import org.oscm.domobjects.TechnicalProduct;
import org.oscm.domobjects.TechnicalProductOperation;
import org.oscm.domobjects.TriggerDefinition;
import org.oscm.domobjects.TriggerProcess;
import org.oscm.domobjects.TriggerProcessParameter;
import org.oscm.domobjects.enums.LocalizedObjectTypes;
import org.oscm.i18nservice.bean.LocalizerFacade;
import org.oscm.i18nservice.local.ImageResourceServiceLocal;
import org.oscm.i18nservice.local.LocalizerServiceLocal;
import org.oscm.interceptor.AuditLogDataInterceptor;
import org.oscm.interceptor.DateFactory;
import org.oscm.interceptor.ExceptionMapper;
import org.oscm.interceptor.InvocationDateContainer;
import org.oscm.internal.intf.ServiceProvisioningService;
import org.oscm.internal.intf.SubscriptionService;
import org.oscm.internal.types.enumtypes.EventType;
import org.oscm.internal.types.enumtypes.ImageType;
import org.oscm.internal.types.enumtypes.ImageType.ImageOwnerType;
import org.oscm.internal.types.enumtypes.OrganizationRoleType;
import org.oscm.internal.types.enumtypes.ParameterType;
import org.oscm.internal.types.enumtypes.ParameterValueType;
import org.oscm.internal.types.enumtypes.PerformanceHint;
import org.oscm.internal.types.enumtypes.PriceModelType;
import org.oscm.internal.types.enumtypes.PricingPeriod;
import org.oscm.internal.types.enumtypes.ServiceAccessType;
import org.oscm.internal.types.enumtypes.ServiceStatus;
import org.oscm.internal.types.enumtypes.ServiceType;
import org.oscm.internal.types.enumtypes.SubscriptionStatus;
import org.oscm.internal.types.enumtypes.TriggerType;
import org.oscm.internal.types.enumtypes.UserRoleType;
import org.oscm.internal.types.exception.AddMarketingPermissionException;
import org.oscm.internal.types.exception.BillingAdapterNotFoundException;
import org.oscm.internal.types.exception.ConcurrentModificationException;
import org.oscm.internal.types.exception.CurrencyException;
import org.oscm.internal.types.exception.DeletionConstraintException;
import org.oscm.internal.types.exception.ImportException;
import org.oscm.internal.types.exception.MailOperationException;
import org.oscm.internal.types.exception.NonUniqueBusinessKeyException;
import org.oscm.internal.types.exception.ObjectNotFoundException;
import org.oscm.internal.types.exception.OperationNotPermittedException;
import org.oscm.internal.types.exception.OperationPendingException;
import org.oscm.internal.types.exception.OrganizationAuthoritiesException;
import org.oscm.internal.types.exception.PaymentInformationException;
import org.oscm.internal.types.exception.PriceModelException;
import org.oscm.internal.types.exception.SaaSSystemException;
import org.oscm.internal.types.exception.ServiceCompatibilityException;
import org.oscm.internal.types.exception.ServiceNotPublishedException;
import org.oscm.internal.types.exception.ServiceOperationException;
import org.oscm.internal.types.exception.ServiceOperationException.Reason;
import org.oscm.internal.types.exception.ServiceStateException;
import org.oscm.internal.types.exception.SubscriptionStateException;
import org.oscm.internal.types.exception.TechnicalServiceActiveException;
import org.oscm.internal.types.exception.TechnicalServiceMultiSubscriptions;
import org.oscm.internal.types.exception.TechnicalServiceNotAliveException;
import org.oscm.internal.types.exception.UnchangeableAllowingOnBehalfActingException;
import org.oscm.internal.types.exception.UpdateConstraintException;
import org.oscm.internal.types.exception.ValidationException;
import org.oscm.internal.types.exception.ValidationException.ReasonEnum;
import org.oscm.internal.vo.BaseVO;
import org.oscm.internal.vo.VOCatalogEntry;
import org.oscm.internal.vo.VOCompatibleService;
import org.oscm.internal.vo.VOCustomerService;
import org.oscm.internal.vo.VOEventDefinition;
import org.oscm.internal.vo.VOImageResource;
import org.oscm.internal.vo.VOLocalizedText;
import org.oscm.internal.vo.VOMarketplace;
import org.oscm.internal.vo.VOOrganization;
import org.oscm.internal.vo.VOParameter;
import org.oscm.internal.vo.VOParameterDefinition;
import org.oscm.internal.vo.VOParameterOption;
import org.oscm.internal.vo.VOPriceModel;
import org.oscm.internal.vo.VOPriceModelLocalization;
import org.oscm.internal.vo.VOPricedEvent;
import org.oscm.internal.vo.VOPricedOption;
import org.oscm.internal.vo.VOPricedParameter;
import org.oscm.internal.vo.VOPricedRole;
import org.oscm.internal.vo.VORoleDefinition;
import org.oscm.internal.vo.VOService;
import org.oscm.internal.vo.VOServiceActivation;
import org.oscm.internal.vo.VOServiceDetails;
import org.oscm.internal.vo.VOServiceEntry;
import org.oscm.internal.vo.VOServiceLocalization;
import org.oscm.internal.vo.VOServiceOperationParameter;
import org.oscm.internal.vo.VOSteppedPrice;
import org.oscm.internal.vo.VOSubscriptionDetails;
import org.oscm.internal.vo.VOTechnicalService;
import org.oscm.internal.vo.VOTechnicalServiceOperation;
import org.oscm.landingpageService.local.LandingpageServiceLocal;
import org.oscm.logging.Log4jLogger;
import org.oscm.logging.LoggerFactory;
import org.oscm.marketplace.assembler.MarketplaceAssembler;
import org.oscm.permission.PermissionCheck;
import org.oscm.serviceprovisioningservice.assembler.EventAssembler;
import org.oscm.serviceprovisioningservice.assembler.ParameterAssembler;
import org.oscm.serviceprovisioningservice.assembler.ParameterOptionAssembler;
import org.oscm.serviceprovisioningservice.assembler.PriceModelAssembler;
import org.oscm.serviceprovisioningservice.assembler.PricedOptionAssembler;
import org.oscm.serviceprovisioningservice.assembler.PricedProductRoleAssembler;
import org.oscm.serviceprovisioningservice.assembler.ProductAssembler;
import org.oscm.serviceprovisioningservice.assembler.SteppedPriceAssembler;
import org.oscm.serviceprovisioningservice.assembler.TagAssembler;
import org.oscm.serviceprovisioningservice.assembler.TechnicalProductAssembler;
import org.oscm.serviceprovisioningservice.auditlog.PriceModelAuditLogCollector;
import org.oscm.serviceprovisioningservice.auditlog.ServiceAuditLogCollector;
import org.oscm.serviceprovisioningservice.local.ServiceProvisioningServiceLocal;
import org.oscm.serviceprovisioningservice.local.ServiceProvisioningServiceLocalizationLocal;
import org.oscm.serviceprovisioningservice.local.TagServiceLocal;
import org.oscm.serviceprovisioningservice.verification.PricedParameterChecks;
import org.oscm.serviceprovisioningservice.verification.ServiceVisibilityCheck;
import org.oscm.sessionservice.local.SessionServiceLocal;
import org.oscm.string.Strings;
import org.oscm.subscriptionservice.auditlog.SubscriptionAuditLogCollector;
import org.oscm.tenantprovisioningservice.bean.TenantProvisioningServiceBean;
import org.oscm.triggerservice.bean.TriggerProcessIdentifiers;
import org.oscm.triggerservice.local.TriggerMessage;
import org.oscm.triggerservice.local.TriggerProcessMessageData;
import org.oscm.triggerservice.local.TriggerQueueServiceLocal;
import org.oscm.triggerservice.validator.TriggerProcessValidator;
import org.oscm.types.enumtypes.EmailType;
import org.oscm.types.enumtypes.LogMessageIdentifier;
import org.oscm.types.enumtypes.PlatformParameterIdentifiers;
import org.oscm.types.enumtypes.TriggerProcessParameterName;
import org.oscm.validation.ArgumentValidator;
import org.oscm.validation.ImageValidator;
import org.oscm.validator.BLValidator;
import org.oscm.validator.ProductValidator;
import org.oscm.vo.BaseAssembler;

/**
 * Session Bean implementation class ServiceProvisioningServiceBean
 */
@Stateless
@Remote(ServiceProvisioningService.class)
@Local(ServiceProvisioningServiceLocal.class)
@Interceptors({ InvocationDateContainer.class, ExceptionMapper.class, AuditLogDataInterceptor.class })
public class ServiceProvisioningServiceBean implements ServiceProvisioningService, ServiceProvisioningServiceLocal {

    private static final Log4jLogger logger = LoggerFactory.getLogger(ServiceProvisioningServiceBean.class);

    @EJB(beanInterface = SessionServiceLocal.class)
    private SessionServiceLocal pm;

    @EJB(beanInterface = ApplicationServiceLocal.class)
    ApplicationServiceLocal appManager;

    @EJB(beanInterface = DataService.class)
    DataService dm;

    @EJB(beanInterface = LocalizerServiceLocal.class)
    LocalizerServiceLocal localizer;

    @EJB(beanInterface = ServiceProvisioningServiceLocalizationLocal.class)
    ServiceProvisioningServiceLocalizationLocal spsLocalizer;

    @EJB(beanInterface = ImageResourceServiceLocal.class)
    ImageResourceServiceLocal irm;

    @EJB
    private TenantProvisioningServiceBean tenantProvisioning;

    @EJB(beanInterface = TriggerQueueServiceLocal.class)
    TriggerQueueServiceLocal triggerQS;

    @EJB(beanInterface = TagServiceLocal.class)
    TagServiceLocal tagService;

    @EJB(beanInterface = MarketingPermissionServiceLocal.class)
    private MarketingPermissionServiceLocal ms;

    @EJB(beanInterface = SubscriptionService.class)
    SubscriptionService subscriptionService;

    @EJB
    CommunicationServiceLocal commService;

    @EJB
    LandingpageServiceLocal landingpageService;

    @EJB
    PriceModelAuditLogCollector priceModelAudit;

    @EJB
    ServiceAuditLogCollector serviceAudit;

    @EJB
    SubscriptionAuditLogCollector subscriptionAudit;

    @EJB
    ConfigurationServiceLocal configurationService;

    @EJB
    BillingAdapterLocalBean billingAdapterLocalBean;

    @Resource
    private SessionContext sessionCtx;

    private boolean isLocalizedTextChanged;
    private boolean isDescriptionChanged;
    private boolean isShortDescriptionChanged;
    private boolean isCustomTabNameChanged;

    private static String DEFINEIPDOWNGRADE_ON = "ON";
    private static String DEFINEIPDOWNGRADE_OFF = "OFF";
    private static String BOOLEANVALUEYES = "YES";
    private static String BOOLEANVALUENO = "NO";
    public static BigDecimal DEFAULT_PRICE_VALUE = BigDecimal.ZERO;
    public static Long DEFAULT_STEPPED_PRICE_LIMIT = Long.valueOf(0);

    @Override
    public List<VOService> getServicesForMarketplace(String marketplaceId) {
        return getServicesForMarketplace(marketplaceId, PerformanceHint.ALL_FIELDS);
    }

    public List<VOService> getServicesForMarketplace(String marketplaceId, PerformanceHint performanceHint) {

        ArgumentValidator.notEmptyString("marketplaceId", marketplaceId);

        Query query = dm.createNamedQuery("Product.getProductsForCustomerOnMarketplace");
        query.setParameter("customer", dm.getCurrentUser().getOrganization());
        query.setParameter("marketplaceId", marketplaceId);
        List<Product> productList = filterProducts(ParameterizedTypes.list(query.getResultList(), Product.class),
                marketplaceId);

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        ProductAssembler.prefetchData(productList, facade, performanceHint);
        List<VOService> voServices = new ArrayList<>();
        for (Product product : productList) {
            voServices.add(ProductAssembler.toVOProduct(product, facade, performanceHint));
        }

        return voServices;
    }

    @Override
    public VOServiceEntry getServiceForMarketplace(Long serviceKey, String marketplaceId, String locale)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notEmptyString("marketplaceId", marketplaceId);
        ArgumentValidator.notNull("serviceKey", serviceKey);

        String localizerLocale;
        boolean subscriptionLimitReached = false;

        Product product = dm.getReference(Product.class, serviceKey.longValue());

        // Check if the product is a subscription copy
        verifyNoSubscriptionCopy(product);

        if (dm.getCurrentUserIfPresent() != null) {
            PlatformUser currentUser = dm.getCurrentUser();

            // Set the locale
            if (locale == null) {
                localizerLocale = currentUser.getLocale();
            } else {
                localizerLocale = locale;
            }

            // show suspended products if user is owner of the given marketplace
            // and has MARKETPLACE_OWNER role
            boolean returnSuspended = false;
            Organization org = currentUser.getOrganization();
            List<Marketplace> userMps = org.getMarketplaces();
            for (Marketplace marketplace : userMps) {
                if (marketplace.getMarketplaceId().equals(marketplaceId)) {
                    returnSuspended = currentUser.hasRole(UserRoleType.MARKETPLACE_OWNER);
                    break;
                }
            }

            if (product.getTargetCustomer() != null) {
                if (product.getTargetCustomer() == org) {
                    // The current product is a customer specific product (CSP)
                    // for this customer
                    if (product.getStatus() == ServiceStatus.ACTIVE
                            || (product.getStatus() == ServiceStatus.SUSPENDED && returnSuspended)) {
                        Product template = product.getTemplate();
                        if (!existsCatalogEntryForMarketplace(template, marketplaceId)) {
                            return null;
                        }
                    } else {
                        // The product is not active.
                        return null;
                    }
                } else {
                    // The user tries to read a CSP of another user ==> not
                    // allowed
                    OperationNotPermittedException onp = new OperationNotPermittedException(
                            "User is not allowed to access customer specific product.");
                    logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                            LogMessageIdentifier.WARN_ACCESS_PRODUCT_FAILED_NOT_TARGET_CUSTOMER,
                            currentUser.getUserId(), Long.toString(product.getKey()));
                    throw onp;
                }
            } else {
                // the current product is not a customer specific product

                // Published on the current MPL?
                if (!existsCatalogEntryForMarketplace(product, marketplaceId)) {
                    return null;
                }

                // Check if there is a copy (=CSP) for the current customer
                Product customerCopy = getCopyForCustomer(product, org);
                if (customerCopy != null) {
                    // There is a CSP for the current product, check if we can
                    // use it
                    if (customerCopy.getStatus() == ServiceStatus.ACTIVE
                            || (product.getStatus() == ServiceStatus.SUSPENDED && returnSuspended)) {
                        // The product is active => use it
                        product = customerCopy;
                    } else {
                        // CSP exists but is not visible -> "hide" global
                        // product
                        return null;
                    }
                } else {
                    // No CSP available
                    if (product.getStatus() != ServiceStatus.ACTIVE
                            && !(product.getStatus() == ServiceStatus.SUSPENDED && returnSuspended)) {
                        return null;
                    }
                }
            }
            // Check if the current user has already subscribed to a product
            // which is based on the same technical service as the current
            // product is based on.
            subscriptionLimitReached = isSubscriptionLimitReached(product);
        } else {
            // No user is logged in => anonymous access

            ArgumentValidator.notNull("locale", locale);
            localizerLocale = locale;

            // Check if it's a customer specific product
            if (product.getType() == ServiceType.CUSTOMER_TEMPLATE) {
                // anonymous access to customer specific product is not allowed
                OperationNotPermittedException onp = new OperationNotPermittedException(
                        "Anonymous access to customer specific product not allowed.");
                logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                        LogMessageIdentifier.WARN_ANONYMOUS_ACCESS_NOT_ALLOWED, Long.toString(product.getKey()));
                throw onp;
            }

            if (product.getStatus() != ServiceStatus.ACTIVE) {
                // Only active product
                return null;
            }

            CatalogEntry catalogEntry = getCatalogEntryForMarketplace(product, marketplaceId);
            if (catalogEntry == null) {
                // No catalog entry for the service (not published yet)
                return null;
            }

            if (!catalogEntry.isAnonymousVisible()) {
                // Not in the public catalog
                return null;
            }
            if (catalogEntry.getMarketplace() == null
                    || !marketplaceId.equals(catalogEntry.getMarketplace().getMarketplaceId())) {
                // Not published to any/the correct marketplace
                return null;
            }
        }

        LocalizerFacade facade = new LocalizerFacade(localizer, localizerLocale);
        VOServiceEntry result = ProductAssembler.toVOServiceEntry(product, facade, subscriptionLimitReached);

        return result;
    }

    Product getCopyForCustomer(Product template, Organization customer) {
        Query query = dm.createNamedQuery("Product.getCopyForCustomer");
        query.setParameter("template", template);
        query.setParameter("customer", customer);
        List<Product> resultList = ParameterizedTypes.list(query.getResultList(), Product.class);
        if (resultList.size() <= 0) {
            return null;
        }
        return resultList.get(0);
    }

    /**
     * Checks if the passed product is a copy which was created for a specific
     * subscription.
     */
    void verifyNoSubscriptionCopy(Product product) throws OperationNotPermittedException {
        if (product.getTemplate() == null) {
            return;
        }
        if (ServiceType.isSubscription(product.getType())) {
            OperationNotPermittedException onp = new OperationNotPermittedException(
                    "Access to product copy not allowed.");
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                    LogMessageIdentifier.WARN_ACCESS_PRODUCT_COPY_NOT_ALLOWED, Long.toString(product.getKey()));
            throw onp;
        }
    }

    /**
     * Returns the catalog entry for the marketplace identified by the passed
     * marketplace id. Returns <code>null</code> if no entry exists.
     */
    private CatalogEntry getCatalogEntryForMarketplace(Product product, String marketplaceId) {
        ArgumentValidator.notEmptyString("marketplaceId", marketplaceId);
        List<CatalogEntry> catalogEntries = product.getCatalogEntries();
        for (CatalogEntry catalogEntry : catalogEntries) {
            Marketplace mpl = catalogEntry.getMarketplace();
            if (mpl != null && mpl.getMarketplaceId().equals(marketplaceId)) {
                return catalogEntry;
            }
        }
        return null;
    }

    /**
     * If a catalog entry for the passed marketplace exists the function returns
     * <code>true</code> otherwise <code>false</code>.
     */
    boolean existsCatalogEntryForMarketplace(Product product, String marketplaceId) {
        ArgumentValidator.notEmptyString("marketplaceId", marketplaceId);
        return getCatalogEntryForMarketplace(product, marketplaceId) != null ? true : false;
    }

    /**
     * The result list may contain marketing products that are available for all
     * organizations, as well as some that are particularly dedicated to the
     * current customer and have been created as copy of those original ones.
     * Remove those from the list also remove inactive products
     * 
     * @param products
     *            the list which should be filtered
     * @param marketplaceId
     *            the marketplace identifier
     */
    List<Product> filterProducts(List<Product> products, String marketplaceId) {
        Set<Long> prodKeysToBeRemoved = new HashSet<>();
        Map<Long, Product> prodKeysToBeReplaced = new HashMap<>();
        PlatformUser currentUser = dm.getCurrentUserIfPresent();

        boolean currentOrgIsMpOwner = isOrganizationMarketplaceOwner(currentUser, marketplaceId);
        for (Product product : products) {
            Product template = product.getTemplate();
            boolean replaceTemplate = false;
            if (product.getStatus() != ServiceStatus.ACTIVE
                    && !(currentOrgIsMpOwner && product.getStatus() == ServiceStatus.SUSPENDED)) {
                // remove the customer specific product if not visible to the
                // customer
                prodKeysToBeRemoved.add(Long.valueOf(product.getKey()));
                if (template != null) {
                    // also remove the template if the customer specific is not
                    // active
                    prodKeysToBeRemoved.add(Long.valueOf(template.getKey()));
                }
            } else if (template != null && product.getType() == ServiceType.CUSTOMER_TEMPLATE) {
                // if the customer specific one wasn't removed but it has a
                // template, replace the template by the specific copy
                prodKeysToBeReplaced.put(Long.valueOf(template.getKey()), product);
                // the original appearance of the copy has to be removed
                // since the copy will re-appear at its template's position
                prodKeysToBeRemoved.add(Long.valueOf(product.getKey()));
                replaceTemplate = true;
            }
            if (template != null && !replaceTemplate && template.getStatus() != ServiceStatus.ACTIVE
                    && !(currentOrgIsMpOwner && template.getStatus() == ServiceStatus.SUSPENDED)) {
                // if the template isn't visible to the customer and won't be
                // replaced, remove it
                prodKeysToBeRemoved.add(Long.valueOf(template.getKey()));
            }
            if (isSubscriptionLimitReached(product)) {
                prodKeysToBeRemoved.add(Long.valueOf(product.getKey()));
            }
        }

        cleanUpProducts(products, prodKeysToBeRemoved, prodKeysToBeReplaced);
        return products;
    }

    boolean isOrganizationMarketplaceOwner(PlatformUser currentUser, String marketplaceId) {
        if (currentUser != null && currentUser.isOrganizationAdmin()) {
            List<Marketplace> ownedMarketplaces = currentUser.getOrganization().getMarketplaces();
            for (Marketplace marketplace : ownedMarketplaces) {
                if (marketplace.getMarketplaceId().equals(marketplaceId)) {
                    return true;
                }
            }
        }
        return false;
    }

    private void cleanUpProducts(List<Product> products, Set<Long> prodKeysToBeRemoved,
            Map<Long, Product> prodKeysToBeReplaced) {
        ListIterator<Product> productsIterator = products.listIterator();
        while (productsIterator.hasNext()) {
            Long productKey = Long.valueOf(productsIterator.next().getKey());
            if (prodKeysToBeRemoved.contains(productKey) && !prodKeysToBeReplaced.containsKey(productKey)) {
                // remove obsolete products (only if they won't be replaced)
                productsIterator.remove();
            } else {
                Product specificCopy = prodKeysToBeReplaced.get(productKey);
                if (specificCopy != null) {
                    // replace templates by specific copies
                    productsIterator.set(specificCopy);
                }
            }
        }
    }

    @Override
    public List<VOService> getRelatedServicesForMarketplace(VOService service, String marketplaceId, String locale)
            throws ObjectNotFoundException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notEmptyString("marketplaceId", marketplaceId);

        List<VOService> voList = new ArrayList<>();

        Product prod = dm.getReference(Product.class, service.getKey());
        List<Product> resultList;
        Query query;
        String localizerLocale;

        if (dm.getCurrentUserIfPresent() != null) {
            PlatformUser currentUser = dm.getCurrentUser();
            Organization currentUsersOrg = currentUser.getOrganization();
            if (locale == null) {
                localizerLocale = currentUser.getLocale();
            } else {
                localizerLocale = locale;
            }

            query = dm.createNamedQuery("Product.getRelatedProductsForMarketplace");
            query.setParameter("customer", currentUsersOrg);
            query.setParameter("marketplaceId", marketplaceId);
            query.setParameter("technicalProduct", prod.getTechnicalProduct());
            query.setParameter("vendor", prod.getVendor());
        } else {
            ArgumentValidator.notNull("locale", locale);
            localizerLocale = locale;

            query = dm.createNamedQuery("Product.getRelatedPublicProductsForMarketplace");
            query.setParameter("marketplaceId", marketplaceId);
            query.setParameter("technicalProduct", prod.getTechnicalProduct());
            query.setParameter("vendor", prod.getVendor());
        }
        // Remove services which should be not visible to the current user
        resultList = filterProducts(ParameterizedTypes.list(query.getResultList(), Product.class), marketplaceId);

        // Finally remove the passed service
        resultList.remove(prod);

        // Build the VO list
        LocalizerFacade facade = new LocalizerFacade(localizer, localizerLocale);
        for (Product product : resultList) {
            voList.add(ProductAssembler.toVOProduct(product, facade));
        }

        return voList;
    }

    boolean hasOneSubscription(Product product) {
        Query query = dm.createNamedQuery("Subscription.numberOfVisibleSubscriptions");
        query.setParameter("productKey", Long.valueOf(product.getTechnicalProduct().getKey()));
        query.setParameter("orgKey", Long.valueOf(dm.getCurrentUser().getOrganization().getKey()));
        long result = ((Long) query.getSingleResult()).longValue();
        return result > 0;
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public boolean isSubscriptionLimitReached(Product product) {
        if (dm.getCurrentUserIfPresent() != null && product.getTechnicalProduct().isOnlyOneSubscriptionAllowed()) {
            return hasOneSubscription(product);
        }
        return false;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public List<VOService> getSuppliedServices() {
        return getSuppliedServices(PerformanceHint.ALL_FIELDS);
    }

    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public List<VOService> getSuppliedServices(PerformanceHint performanceHint) {

        Organization currentUsersOrg = dm.getCurrentUser().getOrganization();
        EnumSet<ServiceType> serviceTypes = getServiceTypesForOrg(currentUsersOrg);
        List<Product> productList = getProductsOfSupplier(currentUsersOrg, serviceTypes);
        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        ProductAssembler.prefetchData(productList, facade, performanceHint);
        List<VOService> voList = new ArrayList<>();
        for (Product product : productList) {
            voList.add(ProductAssembler.toVOProduct(product, facade, performanceHint));
        }

        return voList;
    }

    private EnumSet<ServiceType> getServiceTypesForOrg(Organization currentUsersOrg) {
        EnumSet<ServiceType> serviceTypes;
        if (currentUsersOrg.hasRole(OrganizationRoleType.SUPPLIER)) {
            serviceTypes = EnumSet.of(ServiceType.TEMPLATE);
        } else {
            serviceTypes = EnumSet.of(ServiceType.PARTNER_TEMPLATE);
        }
        return serviceTypes;
    }

    /**
     * Retrieves the marketing products a supplier has defined so far. Obsolete
     * and deleted products are removed from the list.
     * 
     * @param supplier
     *            The supplier organization for which the products have to be
     *            retrieved.
     * @return A list of subscribable products.
     */
    private List<Product> getProductsOfSupplier(Organization supplier, EnumSet<ServiceType> serviceTypes) {
        // the used query only returns those products which are not a copy of
        // another product
        Query query = null;
        query = dm.createNamedQuery("Product.getProductTemplatesForVendor");
        query.setParameter("vendorKey", Long.valueOf(supplier.getKey()));
        query.setParameter("productTypes", serviceTypes);
        query.setParameter("filterOutWithStatus", EnumSet.of(ServiceStatus.OBSOLETE, ServiceStatus.DELETED));
        @SuppressWarnings("unchecked")
        List<Product> result = query.getResultList();
        return result;
    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public String importTechnicalServices(byte[] xml) throws ImportException, OperationNotPermittedException,
            TechnicalServiceActiveException, UpdateConstraintException, TechnicalServiceMultiSubscriptions,
            UnchangeableAllowingOnBehalfActingException, BillingAdapterNotFoundException {

        ArgumentValidator.notNull("xml", xml);

        TechnicalProductImportParser parser = new TechnicalProductImportParser(dm, localizer,
                dm.getCurrentUser().getOrganization(), pm, tenantProvisioning, tagService, ms,
                configurationService);
        try {
            parser.parse(xml);
        } catch (TechnicalServiceActiveException | UpdateConstraintException | TechnicalServiceMultiSubscriptions
                | UnchangeableAllowingOnBehalfActingException | BillingAdapterNotFoundException e) {
            sessionCtx.setRollbackOnly();
            throw e;
        }

        if (!parser.isXmlValid()) {
            ImportException e = new ImportException(parser.getMessage());
            sessionCtx.setRollbackOnly();
            throw e;
        }

        return parser.getMessage();
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public VOService activateService(VOService service)
            throws ServiceStateException, ObjectNotFoundException, OrganizationAuthoritiesException,
            OperationNotPermittedException, ServiceOperationException, TechnicalServiceNotAliveException,
            ServiceNotPublishedException, OperationPendingException, ConcurrentModificationException {

        // Activate service
        ServiceVisibilityCheck visChecker = new ServiceVisibilityCheck(dm);
        VOService voProduct = setActivationState(service, true, null, visChecker);

        // Check constraint about visibility
        try {
            visChecker.validate();
        } catch (ServiceOperationException e) {
            sessionCtx.setRollbackOnly();
            throw e;
        }

        return voProduct;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void activateServiceInt(TriggerProcess tp) throws ObjectNotFoundException, ServiceOperationException,
            TechnicalServiceNotAliveException, ServiceStateException, OrganizationAuthoritiesException,
            OperationNotPermittedException, ServiceNotPublishedException, ConcurrentModificationException {

        TriggerProcessParameter tpParam = tp.getParamValueForName(TriggerProcessParameterName.PRODUCT);
        VOService product = tpParam.getValue(VOService.class);

        // obtain the user that should be used for authority checks
        PlatformUser user = tp.getUser();

        Product prod = validateForProductActivation(product);
        setStatus(prod, product, ServiceStatus.ACTIVE, ServiceStatus.INACTIVE, user);

        // Update visibility (if catalog entries are given)
        TriggerProcessParameter tpCatEntries = tp.getParamValueForName(TriggerProcessParameterName.CATALOG_ENTRIES);
        if (tpCatEntries != null) {
            List<VOCatalogEntry> entries = ParameterizedTypes.list(tpCatEntries.getValue(List.class),
                    VOCatalogEntry.class);
            if (entries != null && !entries.isEmpty()) {
                // Set visibility states of entries
                updateCatalogEntryVisibility(prod, entries);
            }
        }

        triggerQS.sendAllNonSuspendingMessages(TriggerMessage.create(TriggerType.ACTIVATE_SERVICE,
                tp.getTriggerProcessParameters(), dm.getCurrentUser().getOrganization()));

    }

    /**
     * Validates the input parameters and availability of the technical product
     * for an activation of a product.
     * 
     * @param product
     *            The product to be activated.
     * @throws ObjectNotFoundException
     *             Thrown in case the product does not exist.
     * @throws TechnicalServiceNotAliveException
     *             Thrown in case the technical product cannot be reached.
     * @throws ServiceNotPublishedException
     *             Thrown in case the service is currently not published on any
     *             marketplace.
     */
    private Product validateForProductActivation(VOService product) throws ObjectNotFoundException,
            ServiceOperationException, TechnicalServiceNotAliveException, ServiceNotPublishedException {

        Product prod = dm.getReference(Product.class, product.getKey());
        PriceModel priceModel;
        if (prod.getVendor().getGrantedRoleTypes().contains(OrganizationRoleType.SUPPLIER)) {
            priceModel = prod.getPriceModel();
        } else {
            priceModel = prod.getTemplate().getPriceModel();
        }
        if (priceModel == null) {
            ServiceOperationException sof = new ServiceOperationException(
                    ServiceOperationException.Reason.MISSING_PRICE_MODEL);
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, sof,
                    LogMessageIdentifier.WARN_PRODUCT_AVTIVATION_FAILED_MISSING_PRICE_MODEL,
                    Long.toString(prod.getKey()));
            throw sof;
        }
        if (prod.getCatalogEntries() != null) {
            for (CatalogEntry ce : prod.getCatalogEntries()) {
                if (ce.getMarketplace() == null) {

                    Object[] params = new Object[] { prod.getProductId() };
                    ServiceNotPublishedException snp = new ServiceNotPublishedException(params);
                    logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, snp,
                            LogMessageIdentifier.WARN_PRODUCT_AVTIVATION_FAILED_NOT_PUBLISHED_MARKETPLACE,
                            Long.toString(prod.getKey()));
                    throw snp;
                }
            }
        }
        try {
            appManager.validateCommunication(prod.getTechnicalProduct());
        } catch (TechnicalServiceNotAliveException e) {
            TechnicalServiceNotAliveException ex = new TechnicalServiceNotAliveException(
                    TechnicalServiceNotAliveException.Reason.SUPPLIER,
                    new Object[] { prod.getTechnicalProduct().getTechnicalProductId() }, e);
            logger.logWarn(Log4jLogger.SYSTEM_LOG, ex, LogMessageIdentifier.WARN_TECH_SERVICE_NOT_AVAILABLE,
                    prod.getTechnicalProduct().getTechnicalProductId());
            throw ex;
        }

        return prod;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public VOService deactivateService(VOService service) throws ServiceStateException, ObjectNotFoundException,
            OrganizationAuthoritiesException, OperationNotPermittedException, ServiceOperationException,
            OperationPendingException, ConcurrentModificationException {

        ServiceVisibilityCheck visChecker = new ServiceVisibilityCheck(dm);
        VOService voProduct = null;
        try {
            // Deactivate service
            voProduct = setActivationState(service, false, null, visChecker);

            // Check constraint about visibility
            try {
                visChecker.validate();
            } catch (ServiceOperationException e) {
                sessionCtx.setRollbackOnly();
                throw e;
            }

        } catch (TechnicalServiceNotAliveException e) {
            // Can't occur during deactivate
            SaaSSystemException sse = new SaaSSystemException(e);
            logger.logError(Log4jLogger.SYSTEM_LOG, sse, LogMessageIdentifier.ERROR_DEACTIVATE_SERVICE);
            throw sse;
        } catch (ServiceNotPublishedException e) {
            // Can't occur during deactivate
            SaaSSystemException sse = new SaaSSystemException(e);
            logger.logError(Log4jLogger.SYSTEM_LOG, sse, LogMessageIdentifier.ERROR_DEACTIVATE_SERVICE);
            throw sse;
        } catch (ConcurrentModificationException e) {
            sessionCtx.setRollbackOnly();
            throw e;
        }

        return voProduct;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void deactivateServiceInt(TriggerProcess tp)
            throws ObjectNotFoundException, ServiceStateException, OrganizationAuthoritiesException,
            OperationNotPermittedException, ServiceOperationException, ConcurrentModificationException {

        VOService product = tp.getParamValueForName(TriggerProcessParameterName.PRODUCT).getValue(VOService.class);
        Product prod = dm.getReference(Product.class, product.getKey());
        setStatus(prod, product, ServiceStatus.INACTIVE, ServiceStatus.ACTIVE, tp.getUser());

        // Update visibility (if catalog entries are given)
        TriggerProcessParameter tpCatEntries = tp.getParamValueForName(TriggerProcessParameterName.CATALOG_ENTRIES);
        if (tpCatEntries != null) {
            List<VOCatalogEntry> entries = ParameterizedTypes.list(tpCatEntries.getValue(List.class),
                    VOCatalogEntry.class);
            if (entries != null && !entries.isEmpty()) {
                // Set visibility states of entries
                updateCatalogEntryVisibility(prod, entries);
            }
        }

        triggerQS.sendAllNonSuspendingMessages(TriggerMessage.create(TriggerType.DEACTIVATE_SERVICE,
                tp.getTriggerProcessParameters(), dm.getCurrentUser().getOrganization()));

    }

    /**
     * Sets the product state to the given product and all of its subscribable
     * copies. Before that the product is checked for a valid state and if it
     * has not been changed in the meantime.
     * 
     * @param prod
     *            the product to set the state for read from the database
     * @param product
     *            the voproduct passed in
     * @param newStatus
     *            the new state to set
     * @param requiredState
     *            the state in which the product has to be or <code>null</code>
     * @param user
     *            The user on behalf of which the current operation is executed.
     * @throws ServiceStateException
     *             in case the product isn't in the required state
     * @throws OperationNotPermittedException
     *             in case the current organization is not the owner of the
     *             product
     */
    private void setStatus(Product prod, VOService product, ServiceStatus newStatus, ServiceStatus requiredState,
            PlatformUser user)
            throws ServiceStateException, OperationNotPermittedException, ServiceOperationException {
        validateForProductStatusChange(prod, product, newStatus, requiredState, user);
        if (prod.getStatus() != newStatus) {
            prod.setStatus(newStatus);
        }
    }

    /**
     * Validates if the current caller is permitted to change the state of the
     * product.
     * 
     * @param prod
     *            The product to change the status of.
     * @param product
     *            The value object for the product to be changed.
     * @param newStatus
     *            The status to be set.
     * @param requiredState
     *            The required status of the product.
     * @param user
     *            The user that performs the status change.
     * @throws OperationNotPermittedException
     *             Thrown in case the caller tries to modify the object of
     *             another organization.
     * @throws ServiceStateException
     *             Thrown in case the product state change fails as the current
     *             product state does not allow the intended modification.
     */
    private void validateForProductStatusChange(Product prod, VOService product, ServiceStatus newStatus,
            ServiceStatus requiredState, PlatformUser user)
            throws OperationNotPermittedException, ServiceOperationException, ServiceStateException {
        Organization organization = user.getOrganization();

        // ensure the product belongs to the supplier
        PermissionCheck.owns(prod, organization, logger, sessionCtx);
        if (prod.getOwningSubscription() != null) {
            ServiceOperationException pof = new ServiceOperationException(Reason.STATE_CHANGE_FAILED_USED_BY_SUB);
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, pof,
                    LogMessageIdentifier.WARN_SUPPLIER_CHANGE_SUBSCRIPTION_STATE_FAILED,
                    Long.toString(organization.getKey()), Long.toString(product.getKey()));
            throw pof;
        }
        if (prod.getStatus() != newStatus) {
            if (requiredState != null && prod.getStatus() != requiredState) {
                throw new ServiceStateException(requiredState, prod.getStatus());
            }
        }
    }

    private List<TechnicalProduct> getTechnicalProductsInt(Organization organization,
            OrganizationRoleType organizationRoleType) {
        List<TechnicalProduct> tProds = new ArrayList<>();
        if (organizationRoleType == OrganizationRoleType.SUPPLIER) {
            // retrieve the technical products of all referenced technology
            // providers
            List<Organization> providers = organization.getTechnologyProviders();
            for (Organization provider : providers) {
                tProds.addAll(provider.getTechnicalProducts());
            }
        } else if (organizationRoleType == OrganizationRoleType.TECHNOLOGY_PROVIDER) {
            // retrieve the technical products of the current user's
            // organization
            tProds.addAll(organization.getTechnicalProducts());
        }
        return tProds;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "TECHNOLOGY_MANAGER" })
    public List<VOTechnicalService> getTechnicalServices(OrganizationRoleType role)
            throws OrganizationAuthoritiesException {
        return getTechnicalServices(role, PerformanceHint.ALL_FIELDS);
    }

    @RolesAllowed({ "SERVICE_MANAGER", "TECHNOLOGY_MANAGER" })
    public List<VOTechnicalService> getTechnicalServices(OrganizationRoleType role,
            PerformanceHint performanceHint) {

        ArgumentValidator.notNull("role", role);

        List<VOTechnicalService> result = new ArrayList<>();
        PlatformUser currentUser = dm.getCurrentUser();

        Organization currentUsersOrg = currentUser.getOrganization();

        boolean excludeNonConfigurableParamDefs = false;
        // retrieve technical products
        List<TechnicalProduct> tProds = new ArrayList<>();
        if (role == OrganizationRoleType.SUPPLIER) {
            // retrieve the technical products of all referenced technology
            // providers
            tProds.addAll(ms.getTechnicalServicesForSupplier(currentUsersOrg));
            // a supplier must not see the non-configurable parameter
            // definitions
            excludeNonConfigurableParamDefs = true;
        } else if (role == OrganizationRoleType.TECHNOLOGY_PROVIDER) {
            // retrieve the technical products of the current user's
            // organization
            tProds.addAll(currentUsersOrg.getTechnicalProducts());
        }

        // convert the objects to value objects
        if (performanceHint == PerformanceHint.ONLY_IDENTIFYING_FIELDS) {
            for (TechnicalProduct tProd : tProds) {
                result.add(TechnicalProductAssembler.toVOTechnicalProduct(tProd, null, null, null, false,
                        PerformanceHint.ONLY_IDENTIFYING_FIELDS));
            }
        } else {
            LocalizerFacade facade = new LocalizerFacade(localizer, currentUser.getLocale());
            for (TechnicalProduct tProd : tProds) {
                List<ParameterDefinition> paramDefs = getPlatformParameterDefinitions(tProd);
                List<Event> platformEvents = getPlatformEvents(tProd);
                result.add(TechnicalProductAssembler.toVOTechnicalProduct(tProd, paramDefs, platformEvents, facade,
                        excludeNonConfigurableParamDefs));
            }
        }

        return result;
    }

    @Override
    public void validateTechnicalServiceCommunication(VOTechnicalService technicalService)
            throws ObjectNotFoundException, OperationNotPermittedException, TechnicalServiceNotAliveException {

        ArgumentValidator.notNull("technicalService", technicalService);

        PlatformUser currentUser = dm.getCurrentUser();
        Organization currentUserOrg = currentUser.getOrganization();

        // load the technical product
        TechnicalProduct techProduct = dm.getReference(TechnicalProduct.class, technicalService.getKey());

        // check that the current organization is allowed to access the
        // technical product
        boolean accessable = false;
        if (currentUserOrg.hasRole(OrganizationRoleType.TECHNOLOGY_PROVIDER)) {
            accessable = getTechnicalProductsInt(currentUserOrg, OrganizationRoleType.TECHNOLOGY_PROVIDER)
                    .contains(techProduct);
        }
        if (!accessable && currentUserOrg.hasRole(OrganizationRoleType.SUPPLIER)) {
            accessable = getTechnicalProductsInt(currentUserOrg, OrganizationRoleType.SUPPLIER)
                    .contains(techProduct);
        }
        if (!accessable) {
            OperationNotPermittedException onp = new OperationNotPermittedException(
                    "User is not permitted to access the technical product '" + technicalService.getKey() + "'.");
            logger.logError(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                    LogMessageIdentifier.ERROR_USER_ACCESS_TECHNICAL_PRODUCT_NOT_PERMITTED,
                    Long.toString(technicalService.getKey()));
            throw onp;
        }

        appManager.validateCommunication(techProduct);

    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public void deleteTechnicalService(VOTechnicalService technicalService) throws OrganizationAuthoritiesException,
            ObjectNotFoundException, DeletionConstraintException, ConcurrentModificationException {

        ArgumentValidator.notNull("technicalService", technicalService);

        PlatformUser currentUser = dm.getCurrentUser();
        Organization currentUserOrg = currentUser.getOrganization();
        if (!currentUserOrg.hasRole(OrganizationRoleType.TECHNOLOGY_PROVIDER)) {
            OrganizationAuthoritiesException ioa = new OrganizationAuthoritiesException(
                    "Delete technical product +'" + technicalService.getKey() + "' failed.",
                    new Object[] { OrganizationRoleType.TECHNOLOGY_PROVIDER });
            logger.logError(Log4jLogger.SYSTEM_LOG, ioa, LogMessageIdentifier.ERROR_DELETE_TECHNICAL_PRODUCT_FAILED,
                    Long.toString(technicalService.getKey()));
            throw ioa;
        }

        final TechnicalProduct technicalProduct = dm.getReference(TechnicalProduct.class,
                technicalService.getKey());

        verifyTechnicalServiceIsUpToDate(technicalService, technicalProduct, false);

        TechnicalProductCleaner cleaner = new TechnicalProductCleaner(dm, tenantProvisioning);
        cleaner.cleanupTechnicalProduct(technicalProduct);

        ms.removeMarketingPermissions(technicalProduct);
        dm.remove(technicalProduct);
        tagService.deleteOrphanedTags();
    }

    /**
     * Verifies that the technical service is up to date - the versions of it
     * and all depending objects match the ones stored on server side.
     * 
     * @param technicalService
     *            The value object.
     * @param technicalProduct
     *            The domain object.
     * @param ignoreNonConfigurableParameters
     *            Flag indicating whether to consider non-configurable
     *            parameters or not.
     * @throws ConcurrentModificationException
     */
    void verifyTechnicalServiceIsUpToDate(VOTechnicalService technicalService,
            final TechnicalProduct technicalProduct, boolean ignoreNonConfigurableParameters)
            throws ConcurrentModificationException {
        BaseAssembler.verifyVersionAndKey(technicalProduct, technicalService);

        List<Event> events = technicalProduct.getEvents();
        List<VOEventDefinition> eventDefinitions = technicalService.getEventDefinitions();
        verifyListConsistency(eventDefinitions, events);

        List<ParameterDefinition> parameterDefinitions = new ArrayList<>(
                technicalProduct.getParameterDefinitions());
        // filter out the non-configurable parameter definitions, if required
        Iterator<ParameterDefinition> iterator = parameterDefinitions.iterator();
        while (iterator.hasNext()) {
            ParameterDefinition currentParamDef = iterator.next();
            if (!currentParamDef.isConfigurable() && ignoreNonConfigurableParameters) {
                iterator.remove();
            }
        }
        List<VOParameterDefinition> voParamDefs = technicalService.getParameterDefinitions();
        verifyListConsistency(voParamDefs, parameterDefinitions);

        List<RoleDefinition> roleDefinitions = technicalProduct.getRoleDefinitions();
        List<VORoleDefinition> voRoleDefinitions = technicalService.getRoleDefinitions();
        verifyListConsistency(voRoleDefinitions, roleDefinitions);

        List<TechnicalProductOperation> technicalProductOperations = technicalProduct
                .getTechnicalProductOperations();
        List<VOTechnicalServiceOperation> technicalServiceOperations = technicalService
                .getTechnicalServiceOperations();
        verifyListConsistency(technicalServiceOperations, technicalProductOperations);
    }

    /**
     * Verifies the list of value objects against the list of domain objects. If
     * the value object element list contains an entry that has a version clash
     * with the corresponding domain object or in case one of the domain objects
     * is not considered as value object (so it might have been added on server
     * side later), a ConcurrentModificationException is thrown.
     * 
     * @param voList
     *            The value object element list to check.
     * @param doList
     *            The domain object element list to compare against.
     * @throws ConcurrentModificationException
     */
    private void verifyListConsistency(List<? extends BaseVO> voList, List<? extends DomainObject<?>> doList)
            throws ConcurrentModificationException {
        Map<Long, DomainObject<?>> keyToDomainObject = new HashMap<>();
        for (DomainObject<?> domainObject : doList) {
            keyToDomainObject.put(Long.valueOf(domainObject.getKey()), domainObject);
        }
        for (BaseVO valueObject : voList) {
            DomainObject<?> correspondingDomainObject = keyToDomainObject
                    .remove(Long.valueOf(valueObject.getKey()));
            if (correspondingDomainObject != null) {
                BaseAssembler.verifyVersionAndKey(correspondingDomainObject, valueObject);
            }
        }
        if (!keyToDomainObject.isEmpty()) {
            ConcurrentModificationException cme = new ConcurrentModificationException(String.format(
                    "Technical service has changed, object '%s' was not contained in input, but potentially added in meantime.",
                    keyToDomainObject.values().iterator().next()));
            DomainObject<?> object = keyToDomainObject.values().iterator().next();
            logger.logWarn(Log4jLogger.SYSTEM_LOG, cme, LogMessageIdentifier.WARN_OBJECT_CREATED_CONCURRENTLY,
                    (object == null ? "" : object.getClass().getSimpleName()));
            throw cme;
        }
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails createService(VOTechnicalService technicalService, VOService service,
            VOImageResource voImageResource)
            throws OrganizationAuthoritiesException, ObjectNotFoundException, OperationNotPermittedException,
            ValidationException, NonUniqueBusinessKeyException, ConcurrentModificationException {

        ArgumentValidator.notNull("technicalService", technicalService);
        ArgumentValidator.notNull("service", service);

        Product product = null;

        TechnicalProduct technicalProduct = dm.getReference(TechnicalProduct.class, technicalService.getKey());
        verifyTechnicalServiceIsUpToDate(technicalService, technicalProduct, true);
        try {
            product = prepareMarketingProduct(technicalService.getKey(), service, true);
        } catch (ServiceStateException | ConcurrentModificationException e) {
            // this must not happen
            SaaSSystemException sse = new SaaSSystemException(e);
            logger.logError(Log4jLogger.SYSTEM_LOG, sse, LogMessageIdentifier.ERROR_CREATE_SERVICE);
            throw sse;
        }

        processImage(product.getKey(), voImageResource);

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());

        VOServiceDetails createdProduct = getServiceDetails(product, facade);

        serviceAudit.defineService(dm, product, technicalService.getTechnicalServiceId(),
                service.getShortDescription(), service.getDescription(), dm.getCurrentUser().getLocale());

        return createdProduct;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails updateService(VOServiceDetails service, VOImageResource imageResource)
            throws ObjectNotFoundException, OrganizationAuthoritiesException, OperationNotPermittedException,
            ValidationException, NonUniqueBusinessKeyException, ServiceStateException,
            ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);

        Product storedService = dm.getReference(Product.class, service.getKey());
        List<Product> customerProducts = getCustomerSpecificCopyProducts(storedService);
        validateProductStatus(customerProducts);

        Product product = prepareMarketingProduct(storedService.getTechnicalProduct().getKey(), service, false);

        processImage(product.getKey(), imageResource);

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        VOServiceDetails createdProduct = getServiceDetails(product, facade);

        if (service.getVersion() < createdProduct.getVersion() || isLocalizedTextChanged) {
            serviceAudit.updateService(dm, storedService, isShortDescriptionChanged, isDescriptionChanged,
                    isCustomTabNameChanged, dm.getCurrentUser().getLocale());
        }

        updateCustomerSpecificService(product, customerProducts, createdProduct);

        return createdProduct;
    }

    void validateProductStatus(List<Product> customerProducts) throws ServiceStateException {
        for (Product prod : customerProducts) {
            ProductValidator.validateInactiveOrSuspended(ProductAssembler.getProductId(prod), prod.getStatus());
        }
    }

    void updateCustomerSpecificService(Product templateProduct, List<Product> customerProducts,
            VOServiceDetails createdProduct) throws ValidationException {
        for (Product prod : customerProducts) {
            Product product = ProductAssembler.updateCustomerTemplateProduct(prod, createdProduct);
            updateParametersForCustomerTemplate(templateProduct, product);
            dm.flush();
        }
    }

    void updateParametersForCustomerTemplate(Product templateProduct, Product customerTemplateProduct) {
        ParameterSet customerTemplateParameterSet = customerTemplateProduct.getParameterSet();
        ParameterSet templateParameterSet = templateProduct.getParameterSet();
        Map<Long, Parameter> obsoleteParameters = new HashMap<>();
        if (customerTemplateParameterSet != null) {
            for (final Parameter p : customerTemplateParameterSet.getParameters()) {
                obsoleteParameters.put(Long.valueOf(p.getParameterDefinition().getKey()), p);
            }
        }
        if (customerTemplateParameterSet == null) {
            customerTemplateProduct.setParameterSet(new ParameterSet());
            customerTemplateParameterSet = customerTemplateProduct.getParameterSet();
        }
        if (templateParameterSet == null) {
            templateParameterSet = new ParameterSet();
        }

        List<Parameter> customerTemplateParameters = customerTemplateParameterSet.getParameters();
        for (Parameter param : templateParameterSet.getParameters()) {
            final Parameter oldParam = obsoleteParameters
                    .remove(Long.valueOf(param.getParameterDefinition().getKey()));
            if (oldParam == null) {
                // add new param
                customerTemplateParameters.add(param.copy(customerTemplateParameterSet));
            } else {
                // update param
                if (isDifferentFromExistingValue(param, oldParam)) {
                    oldParam.setConfigurable(param.isConfigurable());
                    oldParam.setValue(param.getValue());
                }
                if (!oldParam.isConfigurable()) {
                    removePricedParameters(oldParam);
                }
            }
        }
        // remove obsolete params
        for (Parameter obsoleteParameter : obsoleteParameters.values()) {
            customerTemplateParameters.remove(obsoleteParameter);
            removePricedParameters(obsoleteParameter);
            dm.remove(obsoleteParameter);
        }
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public VOServiceDetails getServiceDetails(Product product, LocalizerFacade facade) {
        TechnicalProduct tp = product.getTechnicalProduct();
        List<ParameterDefinition> platformParameters = getPlatformParameterDefinitions(tp);
        List<Event> platformEvents = getPlatformEvents(tp);
        VOServiceDetails createdProduct = ProductAssembler.toVOProductDetails(product, platformParameters,
                platformEvents, isImageDefined(product), facade);

        return createdProduct;
    }

    /**
     * Modifies or creates a marketing product according to the given
     * parameters. The modifications will be stored directly.
     * 
     * @param technicalProductKey
     *            The key of the technical product the marketing product is
     *            based on.
     * @param productToModify
     *            The value object representation of the object as is should
     *            finally look like
     * @param isCreation
     *            Indicates whether the product has to be created from scratch
     *            or if it should only be updated.
     * @return The modified product.
     * @throws ObjectNotFoundException
     *             Thrown in case the technical product or the marketing product
     *             (in case of an update) cannot be found.
     * @throws OperationNotPermittedException
     *             Thrown in case an attempt is made to use parameters defined
     *             for another technical product or in case the specified
     *             technical product is not related to the marketing product.
     * @throws ValidationException
     *             Thrown in case the product identifier is not valid.
     * @throws NonUniqueBusinessKeyException
     *             Thrown in case a product with the same identifier does
     *             already exist.
     * @throws ServiceStateException
     *             Thrown in case the product is not in the state
     *             {@link ServiceStatus#INACTIVE}
     * @throws ConcurrentModificationException
     * @throws DeletionConstraintException
     */
    private Product prepareMarketingProduct(long technicalProductKey, VOService productToModify, boolean isCreation)
            throws ObjectNotFoundException, OperationNotPermittedException, ValidationException,
            NonUniqueBusinessKeyException, ServiceStateException, ConcurrentModificationException {

        // 1. ensure that caller has required authority
        PlatformUser currentUser = dm.getCurrentUser();
        Organization currentUserOrg = currentUser.getOrganization();

        // 2. ensure user as permitted to create a marketing product based on
        // the specified technical product
        TechnicalProduct tProd = dm.getReference(TechnicalProduct.class, technicalProductKey);
        PermissionCheck.hasMarketingPermission(tProd, currentUserOrg, dm, logger);

        // 3. Now create the marketing product without the parameter set and
        // without price model
        Product product = null;
        String oldProductId = null;
        if (isCreation) {
            product = ProductAssembler.toNewTemplateProduct(productToModify, tProd, currentUserOrg);
        } else {
            product = dm.getReference(Product.class, productToModify.getKey());
            ProductValidator.validateInactiveOrSuspended(ProductAssembler.getProductId(product),
                    product.getStatus());
            oldProductId = product.getProductId();
            if (!oldProductId.equals(productToModify.getServiceId())) {
                validateChangedId(productToModify.getServiceId(), currentUserOrg);
            }
            product = ProductAssembler.updateProduct(product, productToModify);
        }

        // handle the parameter settings
        List<Parameter> parametersToLog = modifyParameters(productToModify, currentUser, tProd, product,
                isCreation);

        // now store the product. due to the cascade definitions, the parameters
        // will be stored as well.
        if (isCreation) {
            dm.persist(product);
            CatalogEntry catalogEntry = QueryBasedObjectFactory.createCatalogEntry(product, null);
            copyOperatorPriceModel(catalogEntry, currentUserOrg.getOperatorPriceModel());
            dm.persist(catalogEntry);
            copyDefaultPaymentEnablement(product, currentUserOrg);
        }
        for (Parameter param : parametersToLog) {
            logUpdateServiceParameters(dm, product, param);
        }
        dm.flush();
        // store localized information, currently only for user's locale
        String productName = productToModify.getName();
        String productDescription = productToModify.getDescription();
        String productShortDescription = productToModify.getShortDescription();
        String productCustomTabName = productToModify.getCustomTabName();
        String userLocale = currentUser.getLocale();
        String oldDescription = localizer.getLocalizedTextFromDatabase(userLocale, product.getKey(),
                LocalizedObjectTypes.PRODUCT_MARKETING_DESC);
        String oldShortDescription = localizer.getLocalizedTextFromDatabase(userLocale, product.getKey(),
                LocalizedObjectTypes.PRODUCT_SHORT_DESCRIPTION);
        String oldProductName = localizer.getLocalizedTextFromDatabase(userLocale, product.getKey(),
                LocalizedObjectTypes.PRODUCT_MARKETING_NAME);
        String oldCustomTabName = localizer.getLocalizedTextFromDatabase(userLocale, product.getKey(),
                LocalizedObjectTypes.PRODUCT_CUSTOM_TAB_NAME);

        isLocalizedTextChanged = false;
        if (productName != null && !productName.equals(oldProductName)) {
            isLocalizedTextChanged = true;
            BLValidator.isName(ProductAssembler.FIELD_NAME_NAME, productName, false);
            localizer.storeLocalizedResource(userLocale, product.getKey(),
                    LocalizedObjectTypes.PRODUCT_MARKETING_NAME, productName);
        }

        isDescriptionChanged = false;
        if (productDescription != null && !productDescription.equals(oldDescription)) {
            isLocalizedTextChanged = true;
            isDescriptionChanged = true;
            localizer.storeLocalizedResource(userLocale, product.getKey(),
                    LocalizedObjectTypes.PRODUCT_MARKETING_DESC, productDescription);
        }

        isShortDescriptionChanged = false;
        if (productShortDescription != null && !productShortDescription.equals(oldShortDescription)) {
            isLocalizedTextChanged = true;
            isShortDescriptionChanged = true;
            localizer.storeLocalizedResource(userLocale, product.getKey(),
                    LocalizedObjectTypes.PRODUCT_SHORT_DESCRIPTION, productShortDescription);
        }

        isCustomTabNameChanged = false;
        if (productCustomTabName != null && !productCustomTabName.equals(oldCustomTabName)) {
            isLocalizedTextChanged = true;
            isCustomTabNameChanged = true;
            localizer.storeLocalizedResource(userLocale, product.getKey(),
                    LocalizedObjectTypes.PRODUCT_CUSTOM_TAB_NAME, productCustomTabName);
        }

        if (isCreation) {
            // copy the technical product description to marketing description
            // in all locales except the users one
            List<VOLocalizedText> techDescriptions = localizer.getLocalizedValues(tProd.getKey(),
                    LocalizedObjectTypes.TEC_PRODUCT_TECHNICAL_DESC);

            if (techDescriptions != null) {
                long key = product.getKey();
                for (VOLocalizedText desc : techDescriptions) {
                    String locale = desc.getLocale();
                    if (!userLocale.equals(locale)) {
                        localizer.storeLocalizedResource(locale, key, LocalizedObjectTypes.PRODUCT_MARKETING_DESC,
                                desc.getText());
                    }
                }
            }
        }
        // before returning, refresh the product reference, as the parameters
        // must be contained
        dm.flush();
        dm.refresh(product);

        return product;
    }

    private void validateChangedId(String serviceId, Organization currentUserOrg)
            throws NonUniqueBusinessKeyException {
        Product productTmpl = new Product();
        productTmpl.setVendor(currentUserOrg);
        productTmpl.setProductId(serviceId);
        dm.validateBusinessKeyUniqueness(productTmpl);
    }

    void copyOperatorPriceModel(CatalogEntry catalogEntry, RevenueShareModel operatorPriceModel) {
        RevenueShareModel operatorPriceModelCopy = operatorPriceModel.copy();
        catalogEntry.setOperatorPriceModel(operatorPriceModelCopy);

        try {
            dm.persist(operatorPriceModelCopy);
        } catch (NonUniqueBusinessKeyException e) {
            // must not happen because a RevenueShareModel has no business key
            SaaSSystemException sse = new SaaSSystemException("Caught unexpected NonUniqueBusinessKeyException", e);
            logger.logError(Log4jLogger.SYSTEM_LOG, sse, LogMessageIdentifier.ERROR_UNEXPECTED_BK_VIOLATION);
            throw sse;
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void copyDefaultPaymentEnablement(Product product, Organization vendor) {
        List<OrganizationRefToPaymentType> types = vendor.getDefaultServicePaymentTypes();
        for (OrganizationRefToPaymentType ref : types) {
            ProductToPaymentType ptpt = new ProductToPaymentType(product, ref.getPaymentType());
            product.getPaymentTypes().add(ptpt);
            try {
                dm.persist(ptpt);
            } catch (NonUniqueBusinessKeyException e) {
                // must not happen as the product is a new one without
                // references to payment types
                SaaSSystemException sse = new SaaSSystemException("Caught unexpected NonUniqueBusinessKeyException",
                        e);
                logger.logError(Log4jLogger.SYSTEM_LOG, sse, LogMessageIdentifier.ERROR_UNEXPECTED_BK_VIOLATION);
                throw sse;
            }
        }
    }

    /**
     * Adapts the parameter settings for a given product according to the
     * specified input parameters. Matching parameters will be updated, new ones
     * will be added. Those that do not exist in the input will be removed.
     * 
     * @param productToModify
     *            The value object representation of the product. It contains
     *            the values that have to be persisted.
     * @param currentUser
     *            The user that invoked the operation.
     * @param tProd
     *            The technical product the marketing product to be changed is
     *            based upon.
     * @param product
     *            The product domain object that corresponds to the product
     *            input parameter.
     * @param isCreation
     *            true if define service,false if update service
     * @throws ObjectNotFoundException
     *             Thrown in case the parameter definitions cannot be found.
     * @throws OperationNotPermittedException
     *             Thrown in case an attempt is made to store parameters that
     *             are based on another technical product's parameter
     *             definitions.
     * @throws ValidationException
     *             Thrown in case the parameter values could not match the
     *             specified datatype.
     * @throws ConcurrentModificationException
     * @throws GeneralSecurityException
     * @throws DeletionConstraintException
     */
    private List<Parameter> modifyParameters(VOService productToModify, PlatformUser currentUser,
            TechnicalProduct tProd, Product product, boolean isCreation) throws ObjectNotFoundException,
            OperationNotPermittedException, ValidationException, ConcurrentModificationException {
        boolean isDirectAccess = tProd.getAccessType() == ServiceAccessType.DIRECT;
        List<VOParameter> parameters = productToModify.getParameters();
        ParameterSet currentParameterSet = product.getParameterSet();
        // create a temporary set of all currently existing parameters.
        // Whenever one is contained in the input, it will be removed from
        // the set. Finally all remaining ones are obsolete and must be
        // removed.
        Map<Long, Parameter> obsoleteParameters = new HashMap<>();
        List<Parameter> parametersToLog = new ArrayList<>();
        // only determine obsolete parameters if there is a parameterset
        if (currentParameterSet != null) {
            for (final Parameter p : currentParameterSet.getParameters()) {
                obsoleteParameters.put(Long.valueOf(p.getKey()), p);
            }
        }
        if (parameters != null && !parameters.isEmpty()) {
            if (currentParameterSet == null) {
                product.setParameterSet(new ParameterSet());
                currentParameterSet = product.getParameterSet();
            }
            for (VOParameter parameter : parameters) {
                if (parameter == null) {
                    continue;
                }
                ParameterDefinition paramDef = null;
                try {
                    paramDef = dm.getReference(ParameterDefinition.class,
                            parameter.getParameterDefinition().getKey());
                } catch (ObjectNotFoundException e) {
                    sessionCtx.setRollbackOnly();
                    logger.logWarn(Log4jLogger.SYSTEM_LOG, e,
                            LogMessageIdentifier.WARN_MARKETING_PRODUCT_CREATION_FAILED);
                    throw e;
                }
                if (!paramDef.isConfigurable()) {
                    OperationNotPermittedException onp = new OperationNotPermittedException(String.format(
                            "Cannot create parameter for parameter definition '%s' as the definition is non-configurable! User was: '%s'.",
                            Long.valueOf(paramDef.getKey()), Long.valueOf(currentUser.getKey())));
                    logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                            LogMessageIdentifier.WARN_NON_CONFIGURABLE_PARAMETER_DEFINITION,
                            String.valueOf(paramDef.getKey()), String.valueOf(currentUser.getKey()));
                    throw onp;
                }

                // validate the parameters
                ParameterAssembler.validateParameter(parameter, paramDef);
                // now ensure that all product related parameters are really
                // belonging to the specified technical product
                if (paramDef.getParameterType() == ParameterType.SERVICE_PARAMETER
                        && paramDef.getTechnicalProduct().getKey() != tProd.getKey()) {
                    sessionCtx.setRollbackOnly();
                    OperationNotPermittedException onp = new OperationNotPermittedException(
                            "Creation of marketing product failed");
                    logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                            LogMessageIdentifier.WARN_MARKETING_PRODUCT_CREATION_FAILED_NOT_ACCESSIBLE_PRODUCT,
                            Long.toString(currentUser.getKey()));
                    throw onp;
                }
                if (paramDef.getValueType() == ParameterValueType.PWD) {
                    parameter.setValue(parameter.getValue());
                }
                // only store the parameter if its value is not null, remove
                // those that have null values and remove all stored parameters
                // that are not present in the input
                List<Parameter> storedParameters = currentParameterSet.getParameters();
                if (isParameterToBeSaved(isDirectAccess, parameter, paramDef)) {
                    final Parameter existingParameter = obsoleteParameters.remove(Long.valueOf(parameter.getKey()));
                    if (existingParameter == null) {
                        final Parameter param = ParameterAssembler.toParameter(parameter);
                        param.setParameterDefinition(paramDef);
                        param.setParameterSet(currentParameterSet);
                        storedParameters.add(param);
                        if (!isCreation || isDifferentFromDefaultValue(param)) {
                            parametersToLog.add(param);
                        }
                    } else {
                        boolean isChanged = isDifferentFromExistingValue(existingParameter,
                                ParameterAssembler.toParameter(parameter));
                        ParameterAssembler.updateParameter(existingParameter, parameter);
                        existingParameter.setParameterDefinition(paramDef);
                        if (isChanged) {
                            parametersToLog.add(existingParameter);
                        }
                        if (!existingParameter.isConfigurable()) {
                            // remove the priced parameters as price modeling
                            // has to be done within the base prices
                            removePricedParameters(existingParameter);
                        }
                    }
                }
            }
        }
        // finally remove all obsolete parameters and their priced parameters
        if (currentParameterSet != null) {
            for (Parameter obsoleteParameter : obsoleteParameters.values()) {
                removePricedParameters(obsoleteParameter);
                dm.remove(obsoleteParameter);
                currentParameterSet.getParameters().remove(obsoleteParameter);
                Parameter parameterToLog = obsoleteParameter.copy(obsoleteParameter.getParameterSet());
                parameterToLog.setConfigurable(false);
                parameterToLog.setValue("");
                parametersToLog.add(parameterToLog);
            }
        }
        return parametersToLog;
    }

    /**
     * When defining service, if the parameter's value and userOption are equals
     * with the default, it will not be logged, otherwise it will be logged.
     */
    boolean isDifferentFromDefaultValue(Parameter parameter) {
        String defaultValue = parameter.getParameterDefinition().getDefaultValue() == null ? ""
                : parameter.getParameterDefinition().getDefaultValue();
        boolean defaultUserOption = false;
        String inputValue = parameter.getValue() == null ? "" : parameter.getValue();
        boolean inputUserOption = parameter.isConfigurable();
        if (defaultValue.equals(inputValue) && defaultUserOption == inputUserOption) {
            return false;
        }
        return true;
    }

    /**
     * When updating service, if the parameter's value and userOption are equals
     * with the existing, it will not be logged. Otherwise it will be logged.
     */
    boolean isDifferentFromExistingValue(Parameter oldPara, Parameter newPara) {
        String oldValue = oldPara.getValue() == null ? "" : oldPara.getValue();
        boolean oldUserOption = oldPara.isConfigurable();
        String newValue = newPara.getValue() == null ? "" : newPara.getValue();
        boolean newUserOption = newPara.isConfigurable();
        if (oldValue.equals(newValue) && oldUserOption == newUserOption) {
            return false;
        }
        return true;
    }

    /**
     * Log update service parameters, including inserted parameters and updated
     * parameters
     */
    private void logUpdateServiceParameters(DataService dataService, Product product, Parameter parameter) {
        String parameterValue = "";
        if (parameter.getParameterDefinition().getValueType().equals(ParameterValueType.BOOLEAN)) {
            if (parameter.getBooleanValue()) {
                parameterValue = BOOLEANVALUEYES;
            } else {
                parameterValue = BOOLEANVALUENO;
            }

        } else if (parameter.getParameterDefinition().getValueType().equals(ParameterValueType.ENUMERATION)
                && parameter.getValue() != null && !parameter.getValue().isEmpty()) {
            parameterValue = localizer.getLocalizedTextFromDatabase(dm.getCurrentUser().getLocale(),
                    parameter.getParameterOption(parameter.getValue()).getKey(),
                    LocalizedObjectTypes.OPTION_PARAMETER_DEF_DESC);
        } else {
            parameterValue = parameter.getValue();
        }
        serviceAudit.updateServiceParameters(dataService, product,
                parameter.getParameterDefinition().getParameterId(), parameterValue, parameter.isConfigurable());
    }

    /**
     * Removes all existing {@link PricedParameter}s based on the provided
     * {@link Parameter}. Only to be used on Products that are not billing
     * relevant - so templates that have no subscription assigned
     * 
     * @param parameter
     *            the {@link Parameter} to remove the {@link PricedParameter}s
     *            for
     */
    private void removePricedParameters(Parameter parameter) {
        final Query query = dm.createNamedQuery("PricedParameter.getForParameter");
        query.setParameter("parameter", parameter);

        List<PricedParameter> list = ParameterizedTypes.list(query.getResultList(), PricedParameter.class);
        for (PricedParameter pricedParameter : list) {
            dm.remove(pricedParameter);
        }

    }

    /**
     * Checks if the parameter can be saved. This is the case for parameters
     * that have a value set or if they are configurable and they are no user
     * related platform parameters for technical products with access type
     * DIRECT.
     * 
     * @param isDirectAccess
     *            <code>true</code> if the technical product uses direct access
     * @param parameter
     *            the value object to check the parameter value and the
     *            configurable flag at
     * @param paramDef
     *            the domain object to get the parameter definition id from
     * @return
     */
    private boolean isParameterToBeSaved(boolean isDirectAccess, VOParameter parameter,
            ParameterDefinition paramDef) {
        return (!GenericValidator.isBlankOrNull(parameter.getValue()) || parameter.isConfigurable())
                && !(isDirectAccess
                        && (PlatformParameterIdentifiers.CONCURRENT_USER.equals(paramDef.getParameterId())
                                || PlatformParameterIdentifiers.NAMED_USER.equals(paramDef.getParameterId())));
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails getServiceForCustomer(VOOrganization customer, VOService service)
            throws OperationNotPermittedException, ObjectNotFoundException {

        ArgumentValidator.notNull("customer", customer);
        ArgumentValidator.notNull("service", service);

        Organization org = dm.getCurrentUser().getOrganization();

        // now determine customer and product domain objects
        Organization cust = null;
        Product prod = null;
        try {
            cust = dm.getReference(Organization.class, customer.getKey());
            prod = dm.getReference(Product.class, service.getKey());
        } catch (ObjectNotFoundException e) {
            logger.logWarn(Log4jLogger.SYSTEM_LOG, e,
                    LogMessageIdentifier.WARN_PRODUCT_RETRIEVAL_FOR_CUSTOMER_FAILED,
                    Long.toString(customer.getKey()), Long.toString(org.getKey()));
            throw e;
        }

        // product must belong to supplier
        PermissionCheck.owns(prod, org, logger, sessionCtx);
        // customer must be registered to supplier
        PermissionCheck.supplierOfCustomer(org, cust, logger, sessionCtx);

        Query query = dm.createNamedQuery("Product.getForCustomerAndTemplate");
        query.setParameter("customer", cust);
        query.setParameter("template", prod);

        Product customerProduct = null;
        customerProduct = getProductFromQueryResult(cust, query);

        VOServiceDetails voCustomerProduct = null;
        if (customerProduct != null) {
            LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
            voCustomerProduct = getServiceDetails(customerProduct, facade);
        }

        return voCustomerProduct;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails getServiceForSubscription(VOOrganization customer, String subscriptionId)
            throws OrganizationAuthoritiesException, ObjectNotFoundException {

        ArgumentValidator.notNull("customer", customer);
        ArgumentValidator.notNull("subscriptionId", subscriptionId);

        Organization org = dm.getCurrentUser().getOrganization();

        // now determine the customer domain object
        Organization cust = null;
        try {
            cust = dm.getReference(Organization.class, customer.getKey());
        } catch (ObjectNotFoundException e) {
            logger.logWarn(Log4jLogger.SYSTEM_LOG, e,
                    LogMessageIdentifier.WARN_PRODUCT_RETRIEVAL_FOR_CUSTOMER_AND_SUBSCRIPTION_FAILED,
                    Long.toString(customer.getKey()), subscriptionId, Long.toString(org.getKey()));
            throw e;
        }

        Query query = dm.createNamedQuery("Product.getForCustomerAndSubId");
        query.setParameter("customer", cust);
        query.setParameter("subscriptionId", subscriptionId);

        Product subscriptionSpecificProduct = null;
        subscriptionSpecificProduct = getProductFromQueryResult(cust, query);

        VOServiceDetails result = null;
        if (subscriptionSpecificProduct != null) {
            LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
            result = getServiceDetails(subscriptionSpecificProduct, facade);
        }

        return result;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public VOServiceDetails getServiceDetails(VOService service)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);

        Organization org = dm.getCurrentUser().getOrganization();

        Product storedProduct = dm.getReference(Product.class, service.getKey());

        if (storedProduct.getType() == ServiceType.PARTNER_SUBSCRIPTION
                && org.getGrantedRoleTypes().contains(OrganizationRoleType.SUPPLIER)) {
            PermissionCheck.owns(storedProduct.getTemplate().getTemplate(), org, logger, sessionCtx);
        } else {
            PermissionCheck.owns(storedProduct, org, logger, sessionCtx);
        }

        if (storedProduct.getStatus() == ServiceStatus.DELETED) {
            return null;
        }

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        VOServiceDetails result = getServiceDetails(storedProduct, facade);

        return result;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails savePriceModel(VOServiceDetails service, VOPriceModel priceModel)
            throws ObjectNotFoundException, OperationNotPermittedException, CurrencyException, ValidationException,
            ServiceStateException, PriceModelException, ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notNull("priceModel", priceModel);

        // check here if the Currencies of the product price models are ok
        validateCurrencyUniqunessOfMigrationPath(priceModel, service);

        Product product = prepareProductWithPriceModel(service, priceModel, null, ServiceType.TEMPLATE, null);

        dm.flush();
        dm.refresh(product);

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        VOServiceDetails result = getServiceDetails(product, facade);

        return result;
    }

    void validateCurrencyUniqunessOfMigrationPath(VOPriceModel priceModel, VOService referencedService)
            throws PriceModelException {

        Product referencedProduct = dm.find(Product.class, referencedService.getKey());
        if (referencedProduct != null) {
            // service is referenced service
            List<ProductReference> compatibleProductList = referencedProduct.getCompatibleProductsTarget();
            for (ProductReference prodRef : compatibleProductList) {
                PriceModel referenceModel = prodRef.getSourceProduct().getPriceModel();
                if (!isCompatibleCurrency(referenceModel, priceModel)) {
                    PriceModelException pme = new PriceModelException(
                            PriceModelException.Reason.UNMODIFIABLE_CURRENCY);
                    logger.logWarn(Log4jLogger.SYSTEM_LOG, pme,
                            LogMessageIdentifier.WARN_SAVE_PRICE_MODEL_FAILED_NOT_SAME_CURRENCY_COMPATIBLE_PRODUCT,
                            referenceModel.getCurrency().getCurrencyISOCode(), priceModel.getCurrencyISOCode(),
                            Long.toString(prodRef.getSourceProduct().getKey()), Long.toString(prodRef.getKey()),
                            dm.getCurrentUser().getUserId());
                    throw pme;
                }
            }
            // service is source of referenced service
            compatibleProductList = referencedProduct.getCompatibleProducts();
            for (ProductReference prodRef : compatibleProductList) {
                PriceModel referenceModel = prodRef.getTargetProduct().getPriceModel();
                if (!isCompatibleCurrency(referenceModel, priceModel)) {
                    PriceModelException pme = new PriceModelException(
                            PriceModelException.Reason.UNMODIFIABLE_CURRENCY);
                    logger.logWarn(Log4jLogger.SYSTEM_LOG, pme,
                            LogMessageIdentifier.WARN_SAVE_PRICE_MODEL_FAILED_NOT_SAME_CURRENCY_COMPATIBLE_PRODUCT,
                            referenceModel.getCurrency().getCurrencyISOCode(), priceModel.getCurrencyISOCode(),
                            Long.toString(prodRef.getTargetProduct().getKey()), Long.toString(prodRef.getKey()),
                            dm.getCurrentUser().getUserId());
                    throw pme;
                }
            }
        }
    }

    /**
     * Loop through the list of stepped prices and update the values for free
     * amount and additional price (the limit of the predecessor defines the
     * values).
     * 
     * @param list
     *            the list to update
     */
    void updateFreeAmountAndAdditionalPrice(List<SteppedPrice> list) {

        Collections.sort(list, new SteppedPriceComparator());
        int size = list.size();
        for (int i = 1; i < size; i++) {
            SteppedPrice prevStep = list.get(i - 1);
            if (prevStep.getLimit() == null) {
                list.get(i).setFreeEntityCount(0);
                list.get(i).setAdditionalPrice(BigDecimal.ZERO.setScale(PriceConverter.NORMALIZED_PRICE_SCALING));
            } else {
                list.get(i).setFreeEntityCount(prevStep.getLimit().longValue());
                list.get(i)
                        .setAdditionalPrice((BigDecimal.valueOf(prevStep.getLimit().longValue())
                                .subtract(BigDecimal.valueOf(prevStep.getFreeEntityCount())))
                                        .multiply(prevStep.getPrice()).add(prevStep.getAdditionalPrice())
                                        .setScale(PriceConverter.NORMALIZED_PRICE_SCALING, RoundingMode.HALF_UP));
            }
        }
        if (size > 0) {
            list.get(0).setFreeEntityCount(0);
            list.get(0).setAdditionalPrice(BigDecimal.ZERO.setScale(PriceConverter.NORMALIZED_PRICE_SCALING));
            list.get(size - 1).setLimit(null);
        }

    }

    /**
     * Prepares and stores a product by setting a price model on it, considering
     * all the settings of the provided parameters.
     * 
     * @param voProductDetails
     *            The details on the product to be updated.
     * @param voPriceModel
     *            The price model information to be set.
     * @param targetCustomer
     *            The target customer to be set in the copy. Will be ignored if
     *            no copy is required.
     * @param subscription
     *            The subscription of the price model.
     * 
     * @return The updated product.
     * @throws ObjectNotFoundException
     *             Thrown in case the product cannot be found.
     * @throws OperationNotPermittedException
     *             Thrown in case the operation cannot be performed, as an
     *             attempt is made to illegally use data.
     * @throws CurrencyException
     *             Thrown in case the specified currency is not supported by the
     *             server.
     * @throws ValidationException
     *             Thrown in case a price for the price model or one of its
     *             events is negative.
     * @throws ServiceStateException
     *             Thrown in case we don't create a copy and the product's state
     *             isn't {@link ServiceStatus#INACTIVE}
     * @throws ConcurrentModificationException
     */
    Product prepareProductWithPriceModel(VOServiceDetails voProductDetails, VOPriceModel voPriceModel,
            Organization targetCustomer, ServiceType productType, Subscription subscription)
            throws ObjectNotFoundException, OperationNotPermittedException, CurrencyException, ValidationException,
            ServiceStateException, ConcurrentModificationException {

        // ensure the current user is a supplier
        PlatformUser currentUser = dm.getCurrentUser();
        Organization org = currentUser.getOrganization();

        Product product = dm.getReference(Product.class, voProductDetails.getKey());
        validateExternalServiceMustBeFree(voPriceModel, product.getTechnicalProduct().getAccessType());

        boolean isCreatePriceModel = product.getPriceModel() == null;
        boolean priceModelCreatedInTransaction = false;
        // indicate whether price model for service exists as a template for
        // price model for customer
        boolean isTemplateExistsForCustomer = false;
        if (productType == ServiceType.CUSTOMER_TEMPLATE) {
            if (product.getTemplate() == null) {
                priceModelCreatedInTransaction = true;
                product = copyCustomerProduct(targetCustomer, currentUser, product);
                if (!isCreatePriceModel) {
                    isTemplateExistsForCustomer = true;
                }
            }
        }

        // ensure the product belongs to the vendor
        PermissionCheck.ownsPriceModel(product, org, logger, sessionCtx);

        if (product.getOwningSubscription() == null) {
            ProductValidator.validateInactiveOrSuspended(ProductAssembler.getProductId(product),
                    product.getStatus());
        }

        boolean newPriceModelCreated = false;
        PriceModel priceModel = product.getPriceModel();
        if (priceModel != null) {
            newPriceModelCreated = priceModelCreatedInTransaction;
            // Validate that the price model has not been changed concurrently.
            if (!priceModelCreatedInTransaction) {
                BaseAssembler.verifyVersionAndKey(priceModel, voPriceModel);
            }
        } else {
            // if there is no price model, the product is not a copy and thus a
            // new price model must be created
            priceModel = new PriceModel();
            newPriceModelCreated = true;
        }

        if (product.getTechnicalProduct().getAccessType() == ServiceAccessType.DIRECT) {
            PriceModelAssembler.validateForDirectAccess(voPriceModel);
        }

        // validate the price model
        PriceModelAssembler.validatePriceModelSettings(voPriceModel);

        // handle all data not related to prices first (just the product
        // reference)
        priceModel.setProduct(product);

        if (voPriceModel.isChargeable()) {
            setPriceModelToChargeable(voPriceModel, priceModel, priceModelCreatedInTransaction, isCreatePriceModel,
                    productType, targetCustomer, subscription, isTemplateExistsForCustomer);
        } else {

            if (voPriceModel.isExternal()) {
                setPriceModelToExternal(voPriceModel, priceModel);
                priceModel.setExternal(true);
                priceModel.setUuid(voPriceModel.getUuid());
            } else {
                setPriceModelToFree(voPriceModel, priceModel);
            }
        }

        product.setPriceModel(priceModel);

        persistProduct(currentUser, product);

        dm.flush();
        boolean descriptionChanged = localizePriceModel(voPriceModel, currentUser, priceModel);

        // copy license information from technical service and
        // just update license for current local
        // when price model is already has copied license information just
        // update value for the current local
        boolean licenseChanged = false;
        if (!ServiceType.isSubscription(productType)) {
            licenseChanged = saveLicenseInformationForPriceModel(product.getTechnicalProduct().getKey(),
                    priceModel.getKey(), voPriceModel, currentUser, newPriceModelCreated);
        }
        if (licenseChanged || descriptionChanged) {
            if (ServiceType.isTemplate(productType)) {
                priceModelAudit.addLogEntryForAuditLogData(dm, product, product.getTargetCustomer(),
                        currentUser.getLocale(), descriptionChanged, licenseChanged);
            } else if (ServiceType.isSubscription(productType)) {
                subscriptionAudit.addLogEntryForAuditLogData(dm, product.getOwningSubscription(),
                        currentUser.getLocale(), descriptionChanged, licenseChanged);
            }
        }
        consolidateSteppedPrices(priceModel);

        return product;
    }

    void setPriceModelToFree(VOPriceModel voPriceModel, PriceModel priceModel) {
        PriceModelType oldPriceModelType = priceModel.getType();
        PriceModelHandler priceModelHandler = new PriceModelHandler(dm, priceModel,
                DateFactory.getInstance().getTransactionTime());
        priceModelHandler.resetToNonChargeable(PriceModelType.FREE_OF_CHARGE);
        priceModelAudit.editPriceModelTypeToFree(dm, priceModel, voPriceModel.getKey(), oldPriceModelType);
    }

    void setPriceModelToExternal(VOPriceModel voPriceModel, PriceModel priceModel) {
        PriceModelHandler priceModelHandler = new PriceModelHandler(dm, priceModel,
                DateFactory.getInstance().getTransactionTime());
        priceModelHandler.resetToNonChargeable(PriceModelType.UNKNOWN);
    }

    private Product copyCustomerProduct(Organization targetCustomer, PlatformUser currentUser, Product product) {
        product = product.copyForCustomer(targetCustomer);
        try {
            dm.persist(product);
            if (targetCustomer == null && product.getTemplate() == null) {
                // only templates get a CatalogEntry
                CatalogEntry catalogEntry = QueryBasedObjectFactory.createCatalogEntry(product, null);
                dm.persist(catalogEntry);
            }
        } catch (NonUniqueBusinessKeyException e) {
            SaaSSystemException sse = new SaaSSystemException("The product copy for product '" + product.getKey()
                    + "' cannot be stored, as the business key already exists.", e);
            logger.logError(Log4jLogger.SYSTEM_LOG, sse,
                    LogMessageIdentifier.ERROR_CREATE_CUSTOMER_FOR_SPECIFIC_PRICEMODEL_FAILED,
                    Long.toString(currentUser.getKey()));
            throw sse;
        }
        return product;
    }

    void setPriceModelToChargeable(VOPriceModel voPriceModel, PriceModel priceModel,
            boolean priceModelCreatedInTransaction, boolean isCreatePriceModel, ServiceType productType,
            Organization targetCustomer, Subscription subscription, boolean isTemplateExistsForCustomer)
            throws ValidationException, OperationNotPermittedException, ConcurrentModificationException,
            CurrencyException {

        // remember old values, needed for auditlog
        SupportedCurrency oldCurrency = priceModel.getCurrency();
        PriceModelType oldPriceModelType = priceModel.getType();
        BigDecimal oldSubscriptionPrice = priceModel.getPricePerPeriod();
        BigDecimal oldOneTimeFee = priceModel.getOneTimeFee();
        BigDecimal oldUserPrice = priceModel.getPricePerUserAssignment();
        int oldFreePeriod = priceModel.getFreePeriod();
        PricingPeriod oldPricingPeriod = priceModel.getPeriod();

        // business code
        SupportedCurrency currency = getSupportedCurrency(voPriceModel, priceModel.getProduct());
        priceModel.setCurrency(currency);
        priceModel.setType(voPriceModel.getType());
        priceModel.setPeriod(voPriceModel.getPeriod());
        priceModel.setPricePerPeriod(voPriceModel.getPricePerPeriod());
        priceModel.setPricePerUserAssignment(voPriceModel.getPricePerUserAssignment());
        priceModel.setOneTimeFee(voPriceModel.getOneTimeFee());
        priceModel.setFreePeriod(voPriceModel.getFreePeriod());

        Product product = priceModel.getProduct();
        setEvents(voPriceModel, priceModel, product, priceModelCreatedInTransaction);

        List<Parameter> parameters = new ArrayList<>();
        if (product.getParameterSet() != null) {
            parameters = product.getParameterSet().getParameters();
        }
        List<VOPricedParameter> selectedParameters = voPriceModel.getSelectedParameters();
        List<PricedParameter> pricedParameters = convertAndValidateParameters(voPriceModel.getKey(),
                selectedParameters, parameters, priceModel, priceModelCreatedInTransaction, targetCustomer,
                subscription, isTemplateExistsForCustomer);

        setPricedParametersForPriceModel(voPriceModel.getKey(), priceModel, pricedParameters, selectedParameters,
                priceModelCreatedInTransaction, targetCustomer);

        List<VOPricedRole> roleSpecificUserPrices = voPriceModel.getRoleSpecificUserPrices();
        validatePricedProductRoles(roleSpecificUserPrices, priceModel.getProduct());
        setRoleSpecificPrices(voPriceModel.getKey(), priceModel, null, null, roleSpecificUserPrices,
                priceModelCreatedInTransaction, targetCustomer, subscription, null);

        List<SteppedPrice> steppedPrices = convertAndValidateSteppedPrices(voPriceModel.getKey(),
                voPriceModel.getSteppedPrices(), priceModel, null, null, priceModelCreatedInTransaction);
        if (!steppedPrices.isEmpty() && BigDecimal.ZERO.compareTo(priceModel.getPricePerUserAssignment()) != 0) {
            ValidationException ve = new ValidationException(ValidationException.ReasonEnum.STEPPED_USER_PRICING,
                    "pricePerUserAssignment", new Object[] {});
            logger.logWarn(Log4jLogger.SYSTEM_LOG, ve,
                    LogMessageIdentifier.WARN_STEPPED_PRICING_MIXED_WITH_BASEPRICE, product.getProductId(),
                    "price model", String.valueOf(voPriceModel.getKey()));

            throw ve;
        }

        priceModelAudit.editPriceModelTypeToChargeable(dm, priceModel, voPriceModel.getKey(), oldCurrency,
                oldPriceModelType, oldFreePeriod, oldPricingPeriod);
        priceModelAudit.editSubscriptionPrice(dm, priceModel, oldSubscriptionPrice, isCreatePriceModel);
        if (productType.equals(ServiceType.TEMPLATE) || productType.equals(ServiceType.CUSTOMER_TEMPLATE)) {
            priceModelAudit.editOneTimeFee(dm, priceModel, oldOneTimeFee, isCreatePriceModel);
        }
        if (voPriceModel.getSteppedPrices() != null && voPriceModel.getSteppedPrices().isEmpty()) {
            priceModelAudit.editUserPrice(dm, priceModel, oldUserPrice, isCreatePriceModel);
        }
    }

    void setEvents(VOPriceModel voPriceModel, PriceModel priceModel, Product product,
            boolean priceModelCreatedInTransaction)
            throws ValidationException, OperationNotPermittedException, ConcurrentModificationException {
        TechnicalProduct tp = product.getTechnicalProduct();
        List<Event> events = new ArrayList<>(tp.getEvents());
        events.addAll(getPlatformEvents(tp));
        convertAndValidateEvents(voPriceModel.getKey(), voPriceModel.getConsideredEvents(), events, priceModel,
                priceModelCreatedInTransaction);
    }

    SupportedCurrency getSupportedCurrency(VOPriceModel priceModel, Product product) throws CurrencyException {
        // load the currency information
        SupportedCurrency currency = null;
        if (priceModel.getCurrencyISOCode() != null) {
            currency = new SupportedCurrency();
            currency.setCurrency(Currency.getInstance(priceModel.getCurrencyISOCode()));
            currency = (SupportedCurrency) dm.find(currency);
        }

        if (priceModel.isChargeable()) {
            if (currency == null) {
                // currency is not supported by the system, so throw an
                // exception
                CurrencyException uc = new CurrencyException("Creation of price model failed.",
                        new Object[] { priceModel.getCurrencyISOCode() });
                logger.logWarn(Log4jLogger.SYSTEM_LOG, uc,
                        LogMessageIdentifier.WARN_PRODUCT_CREATION_FAILED_CURRENCY_NOT_SUPPORTED,
                        Long.toString(product.getKey()), priceModel.getCurrencyISOCode(),
                        Long.toString(dm.getCurrentUser().getKey()));
                throw uc;
            }
        }

        return currency;
    }

    private void persistProduct(PlatformUser currentUser, Product product) {
        try {
            dm.persist(product);
            dm.flush();
        } catch (NonUniqueBusinessKeyException e) {
            // the operation is done internally, the user does not know
            // about the copying process.
            // If the persisting-step fails, it is due to internal problems,
            // the user cannot do anything, so throw a system exception
            SaaSSystemException sse = new SaaSSystemException("The product copy for product '" + product.getKey()
                    + "' cannot be stored, as the business key already exists.", e);
            logger.logError(Log4jLogger.SYSTEM_LOG, sse,
                    LogMessageIdentifier.ERROR_CREATE_CUSTOMER_FOR_SPECIFIC_PRICEMODEL_FAILED,
                    Long.toString(currentUser.getKey()));
            throw sse;
        }
    }

    boolean localizePriceModel(VOPriceModel priceModel, PlatformUser currentUser, PriceModel priceModelToStore) {
        boolean localizeChanged = false;
        final String currentDescription = normalize(localizer.getLocalizedTextFromDatabase(currentUser.getLocale(),
                priceModel.getKey(), LocalizedObjectTypes.PRICEMODEL_DESCRIPTION));

        final String newDescription = normalize(priceModel.getDescription());
        if (!currentDescription.equals(newDescription)) {
            if (priceModel.isChargeable()) {
                localizer.storeLocalizedResource(currentUser.getLocale(), priceModelToStore.getKey(),
                        LocalizedObjectTypes.PRICEMODEL_DESCRIPTION, newDescription);
            } else {
                localizer.removeLocalizedValues(priceModel.getKey(), LocalizedObjectTypes.PRICEMODEL_DESCRIPTION);
            }
            localizeChanged = true;
        }
        return localizeChanged;
    }

    private String normalize(String str) {
        return (str == null) ? "" : str;
    }

    /**
     * updates all stepped prices set additional price and free entity count
     */
    private void consolidateSteppedPrices(PriceModel priceModel) {
        updateFreeAmountAndAdditionalPrice(priceModel.getSteppedPrices());
        for (PricedEvent event : priceModel.getConsideredEvents()) {
            updateFreeAmountAndAdditionalPrice(event.getSteppedPrices());
        }
        for (PricedParameter parameter : priceModel.getSelectedParameters()) {
            updateFreeAmountAndAdditionalPrice(parameter.getSteppedPrices());
        }
    }

    boolean saveLicenseInformationForPriceModel(long productKey, long priceModelKey, VOPriceModel priceModel,
            PlatformUser currentUser, boolean isCreateNewPriceModel) {
        final String userLocale = currentUser.getLocale();

        List<VOLocalizedText> oldLicenses;
        oldLicenses = getOldLicenses(productKey, priceModel.getKey(), isCreateNewPriceModel);
        String newLicense = priceModel.getLicense();
        if (newLicense == null) {
            newLicense = "";
        }
        for (VOLocalizedText localizedText : oldLicenses) {
            String oldLicense = localizedText.getText();
            String locale = localizedText.getLocale();
            if (locale.equals(userLocale)) {
                continue;
            }
            localizer.storeLocalizedResource(locale, priceModelKey, LocalizedObjectTypes.PRICEMODEL_LICENSE,
                    oldLicense);
        }
        localizer.storeLocalizedResource(userLocale, priceModelKey, LocalizedObjectTypes.PRICEMODEL_LICENSE,
                newLicense);
        return !newLicense.isEmpty();
    }

    private List<VOLocalizedText> getOldLicenses(long productKey, long priceModelKey,
            boolean isCreateNewPriceModel) {
        if (isCreateNewPriceModel) {
            return localizer.getLocalizedValues(productKey, LocalizedObjectTypes.PRODUCT_LICENSE_DESC);
        }
        return localizer.getLocalizedValues(priceModelKey, LocalizedObjectTypes.PRICEMODEL_LICENSE);
    }

    /**
     * Validates the value object input for the priced product roles.
     * 
     * @param roleSpecificUserPrices
     *            The value objects.
     * @param product
     *            The related product.
     * @throws OperationNotPermittedException
     *             Thrown in case a defined role definition is not compatible to
     *             the product.
     */
    void validatePricedProductRoles(List<VOPricedRole> roleSpecificUserPrices, Product product)
            throws OperationNotPermittedException {
        Map<Long, RoleDefinition> keyRoleMap = new HashMap<>();
        for (RoleDefinition rd : product.getTechnicalProduct().getRoleDefinitions()) {
            keyRoleMap.put(Long.valueOf(rd.getKey()), rd);
        }
        Set<Long> roleDefinitionKeys = new HashSet<>();
        for (VOPricedRole voPpr : roleSpecificUserPrices) {
            long roleDefinitionKey = voPpr.getRole().getKey();
            if (!roleDefinitionKeys.add(Long.valueOf(roleDefinitionKey))) {
                continue;
            }
            RoleDefinition rdToSet = keyRoleMap.get(Long.valueOf(roleDefinitionKey));
            if (rdToSet == null) {
                OperationNotPermittedException onp = new OperationNotPermittedException(String.format(
                        "User '%s' tried to define a price for role definition '%s' which is not supported for product '%s'.",
                        Long.valueOf(dm.getCurrentUser().getKey()), Long.valueOf(voPpr.getRole().getKey()),
                        Long.valueOf(product.getKey())));
                logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                        LogMessageIdentifier.WARN_USER_DEFINE_PRICE_FOR_ROLE_FAILED_NOT_SUPPORTED,
                        Long.toString(dm.getCurrentUser().getKey()), Long.toString(voPpr.getRole().getKey()),
                        Long.toString(product.getKey()));
                throw onp;
            }
        }
    }

    /**
     * Sets defined role specific user prices for the given price model.
     * 
     * @param priceModel
     *            The price model to be enhanced.
     * @param pricedParameter
     *            The priced parameter to be enhanced. Only used if no price
     *            model is set.
     * @param pricedOption
     *            The priced option to be enhanced. Only used if no price model
     *            and priced parameter is set.
     * @param roleSpecificUserPrices
     *            The user prices to be stored.
     * @param targetCustomer
     *            The targetCustomer of the price model.
     * @param subscription
     *            The subscription of the price model.
     * @param priceModelCreatedInTransaction
     *            Indicates whether the price model was created in this
     * @param oldPricedProductRoles
     *            The list of PricedProductRole of template price model. Only
     *            used if price model for service exists as a template for price
     *            model for customer
     * @throws ValidationException
     *             Thrown in case a negative price is specified for the price
     *             per user field.
     * @throws ConcurrentModificationException
     *             Thrown in case the value object's version does not match the
     *             domain object's.
     * @throws OperationNotPermittedException
     *             Thrown in case a passed priced role value object key does not
     *             belong to the domain object.
     */
    void setRoleSpecificPrices(long voPriceModelKey, PriceModel priceModel, PricedParameter pricedParameter,
            PricedOption pricedOption, List<VOPricedRole> roleSpecificUserPrices,
            boolean priceModelCreatedInTransaction, Organization targetCustomer, Subscription subscription,
            List<PricedProductRole> oldPricedProductRoles)
            throws ValidationException, ConcurrentModificationException, OperationNotPermittedException {

        List<RoleDefinition> roleDefinitions = new ArrayList<>();
        List<PricedProductRole> existingProductRolePrices = new ArrayList<>();
        if (priceModel != null) {
            roleDefinitions = priceModel.getProduct().getTechnicalProduct().getRoleDefinitions();
            existingProductRolePrices = priceModel.getRoleSpecificUserPrices();
        } else if (pricedParameter != null) {
            roleDefinitions = pricedParameter.getPriceModel().getProduct().getTechnicalProduct()
                    .getRoleDefinitions();
            existingProductRolePrices = pricedParameter.getRoleSpecificUserPrices();
        } else if (pricedOption != null) {
            roleDefinitions = pricedOption.getPricedParameter().getPriceModel().getProduct().getTechnicalProduct()
                    .getRoleDefinitions();
            existingProductRolePrices = pricedOption.getRoleSpecificUserPrices();
        }

        Map<Long, RoleDefinition> roleDefinitionMap = new HashMap<>();
        for (RoleDefinition rd : roleDefinitions) {
            roleDefinitionMap.put(Long.valueOf(rd.getKey()), rd);
        }

        List<PricedProductRole> productRolePrices = new ArrayList<>();

        Map<Long, PricedProductRole> pprMap = new HashMap<>();
        for (PricedProductRole ppr : existingProductRolePrices) {
            pprMap.put(Long.valueOf(ppr.getRoleDefinition().getKey()), ppr);
        }

        // now parse the input
        for (VOPricedRole voPricedProductRole : roleSpecificUserPrices) {
            Long roleKey = Long.valueOf(voPricedProductRole.getRole().getKey());
            RoleDefinition roleDefinition = roleDefinitionMap.get(roleKey);
            PricedProductRole pricedProductRole;
            boolean ifLogRequired = (voPriceModelKey != 0);
            if (pprMap.containsKey(roleKey) && roleKey.longValue() != 0) {
                pricedProductRole = pprMap.remove(roleKey);
                validateDomainObjectKey(voPricedProductRole, pricedProductRole, priceModelCreatedInTransaction);
                voPricedProductRole.setKey(pricedProductRole.getKey());
                updatePricedProductRole(voPriceModelKey, voPricedProductRole, pricedProductRole, priceModel,
                        roleDefinition, pricedParameter, targetCustomer, subscription, ifLogRequired);

            } else {
                pricedProductRole = createPricedProductRole(voPriceModelKey, voPricedProductRole, priceModel,
                        roleDefinition, pricedParameter, pricedOption, targetCustomer, subscription,
                        oldPricedProductRoles);
            }
            productRolePrices.add(pricedProductRole);
        }

        // remove the remaining priced product roles, as they are obsolete
        Collection<PricedProductRole> obsoletePricedProductRoles = pprMap.values();
        for (PricedProductRole obsoleteRole : obsoletePricedProductRoles) {
            dm.remove(obsoleteRole);
        }

        // now update the affected domain object
        if (priceModel != null) {
            priceModel.setRoleSpecificUserPrices(productRolePrices);
        } else if (pricedParameter != null) {
            pricedParameter.setRoleSpecificUserPrices(productRolePrices);
        } else if (pricedOption != null) {
            pricedOption.setRoleSpecificUserPrices(productRolePrices);
        }

    }

    void updatePricedProductRole(long voPriceModelKey, VOPricedRole pricedProductRole,
            PricedProductRole pprToUpdate, PriceModel priceModel, RoleDefinition roleDefinition,
            PricedParameter pricedParameter, Organization targetCustomer, Subscription subscription,
            boolean ifNeedLog) throws ValidationException, ConcurrentModificationException {

        BigDecimal oldPricePerUser = pprToUpdate.getPricePerUser();
        PricedProductRoleAssembler.updatePricedProductRole(pricedProductRole, pprToUpdate);
        pprToUpdate.setRoleDefinition(roleDefinition);

        if (priceModel != null) {
            priceModelAudit.editServiceRolePrice(dm, voPriceModelKey, priceModel, pprToUpdate, oldPricePerUser,
                    targetCustomer, subscription);
        } else if (pricedParameter != null && ifNeedLog) {
            priceModelAudit.editParameterUserRolePrice(dm, voPriceModelKey, pprToUpdate, oldPricePerUser);
        } else {
            priceModelAudit.editParameterOptionUserRolePrice(dm, pprToUpdate, oldPricePerUser);
        }
    }

    PricedProductRole createPricedProductRole(long voPriceModelKey, VOPricedRole pricedProductRole,
            PriceModel priceModel, RoleDefinition roleDefinition, PricedParameter pricedParameter,
            PricedOption pricedOption, Organization targetCustomer, Subscription subscription,
            List<PricedProductRole> oldPricedProductRoles) throws ValidationException {

        PricedProductRole pprToCreate = PricedProductRoleAssembler.toPricedProductRole(pricedProductRole);
        pprToCreate.setRoleDefinition(roleDefinition);
        BigDecimal oldPrice = findOldPrice(oldPricedProductRoles, roleDefinition);
        pprToCreate.setPricedParameter(pricedParameter);
        pprToCreate.setPriceModel(priceModel);
        pprToCreate.setPricedOption(pricedOption);

        if (priceModel != null) {
            priceModelAudit.editServiceRolePrice(dm, voPriceModelKey, priceModel, pprToCreate, DEFAULT_PRICE_VALUE,
                    targetCustomer, subscription);
        } else if (pricedParameter != null) {
            priceModelAudit.editParameterUserRolePrice(dm, voPriceModelKey, pprToCreate, DEFAULT_PRICE_VALUE);
        } else if (pricedOption != null) {
            priceModelAudit.editParameterOptionUserRolePrice(dm, pprToCreate, oldPrice);
        }
        return pprToCreate;
    }

    private BigDecimal findOldPrice(List<PricedProductRole> oldPricedProductRoles, RoleDefinition roleDefinition) {
        BigDecimal oldPrice = DEFAULT_PRICE_VALUE;
        if (oldPricedProductRoles != null) {
            for (PricedProductRole ppr : oldPricedProductRoles) {
                if (ppr.getRoleDefinition().getKey() == roleDefinition.getKey()) {
                    oldPrice = ppr.getPricePerUser();
                }
            }
        }
        return oldPrice;
    }

    /**
     * Validates the {@link VOPricedEvent}s and checks if they are defined based
     * on the event definitions from the products technical products. Tries to
     * match each {@link VOPricedEvent} to one of the existing
     * {@link PricedEvent}s (retrieved from the provided {@link PriceModel}) -
     * if a matching domain object was found, it will be updated and added to
     * the result list; if not, a new one will be created and added to the
     * result list. All remaining domain objects, where no matching value object
     * was found for, will be removed with the data manager.
     * 
     * @param voPricedEvents
     *            the {@link VOPricedEvent}s specifying the new values - can be
     *            a mixture of existing and new priced events
     * @param eventDefinitions
     *            a list of {@link Event} that belongs to the products technical
     *            product used for validating if the {@link PricedEvent}s to
     *            save belong to this set of {@link Event}s
     * @param priceModel
     *            the {@link PriceModel} to get the currently existing
     *            {@link PricedEvent}s from and the one to set to the created
     *            {@link PricedEvent}s
     * @param priceModelCreatedInTransaction
     *            Indicates whether the price model was created in this
     *            transaction or not.
     * 
     * @return the list of {@link PricedEvent} that has to be set to the
     *         {@link PriceModel}
     * @throws ValidationException
     *             in case of an validation error when converting the value
     *             object to a domain object
     * @throws OperationNotPermittedException
     *             in case the priced event does not belong to an event of the
     *             products technical product
     * @throws ConcurrentModificationException
     *             Thrown in case an event's value object's version does not
     *             match the current domain object.
     */
    List<PricedEvent> convertAndValidateEvents(long voPriceModelKey, List<VOPricedEvent> voPricedEvents,
            List<Event> eventDefinitions, PriceModel priceModel, boolean priceModelCreatedInTransaction)
            throws ValidationException, OperationNotPermittedException, ConcurrentModificationException {
        // helper map to get existing priced events
        Map<Long, PricedEvent> keyToPricedEvent = new HashMap<>();
        Map<Event, PricedEvent> eventToPricedEvent = new HashMap<>();
        List<PricedEvent> existing = priceModel.getConsideredEvents();
        for (PricedEvent sp : existing) {
            keyToPricedEvent.put(Long.valueOf(sp.getKey()), sp);
            eventToPricedEvent.put(sp.getEvent(), sp);
        }
        // helper map for checking if the event belongs to the product
        Map<Long, Event> keyToEvent = new LinkedHashMap<>();
        for (Event event : eventDefinitions) {
            keyToEvent.put(Long.valueOf(event.getKey()), event);
        }

        String userId = dm.getCurrentUser().getUserId();
        List<PricedEvent> result = new ArrayList<>();
        for (VOPricedEvent voPricedEvent : voPricedEvents) {
            long eventDefKey = voPricedEvent.getEventDefinition().getKey();
            Event event = keyToEvent.remove(Long.valueOf(eventDefKey));
            if (event == null) {
                OperationNotPermittedException onp = new OperationNotPermittedException("Event conversion failed");
                logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                        LogMessageIdentifier.WARN_EVENTS_NOT_BELONG_CORRECT_TECHNICAL_PRODUCT, userId);
                throw onp;
            }

            PricedEvent pricedEvent = null;
            if (keyToPricedEvent.containsKey(Long.valueOf(voPricedEvent.getKey()))) {
                pricedEvent = keyToPricedEvent.remove(Long.valueOf(voPricedEvent.getKey()));
                updatePricedEvent(voPricedEvent, pricedEvent, event, priceModel);
            } else {
                PricedEvent existingPricedEvent = eventToPricedEvent.remove(event);
                validateDomainObjectKey(voPricedEvent, existingPricedEvent, priceModelCreatedInTransaction);
                pricedEvent = createPricedEvent(voPricedEvent, event, priceModel);
            }

            List<SteppedPrice> steppedPrices = convertAndValidateSteppedPrices(voPriceModelKey,
                    voPricedEvent.getSteppedPrices(), priceModel, pricedEvent, null,
                    priceModelCreatedInTransaction);

            validateSteppedPrices(event, pricedEvent, steppedPrices);
            result.add(pricedEvent);
        }

        // set new priced events, to delete the references for events
        // which should be removed
        priceModel.setConsideredEvents(result);
        removePricedEvents(voPriceModelKey, keyToPricedEvent.values());

        return result;
    }

    void updatePricedEvent(VOPricedEvent voPricedEvent, PricedEvent pricedEvent, Event event, PriceModel priceModel)
            throws ValidationException, ConcurrentModificationException {

        BigDecimal oldPrice = pricedEvent.getEventPrice();
        pricedEvent = EventAssembler.updatePricedEvent(voPricedEvent, pricedEvent);
        pricedEvent.setEvent(event);
        pricedEvent.setPriceModel(priceModel);
        priceModelAudit.editEventPrice(dm, pricedEvent, oldPrice);
    }

    PricedEvent createPricedEvent(VOPricedEvent voPricedEvent, Event event, PriceModel priceModel)
            throws ValidationException {

        PricedEvent pricedEvent = EventAssembler.toPricedEvent(voPricedEvent);
        pricedEvent.setEvent(event);
        pricedEvent.setPriceModel(priceModel);
        priceModelAudit.editEventPrice(dm, pricedEvent, DEFAULT_PRICE_VALUE);
        return pricedEvent;
    }

    void removePricedEvents(long voPriceModelKey, Collection<PricedEvent> pricedEvents) {

        for (PricedEvent pricedEvent : pricedEvents) {
            priceModelAudit.removeEventPrice(dm, voPriceModelKey, pricedEvent);
            dm.remove(pricedEvent);
        }
    }

    /**
     * Exception in case event price and stepped prices set
     */
    private void validateSteppedPrices(Event event, PricedEvent pe, List<SteppedPrice> steppedPrices)
            throws ValidationException {
        if (!steppedPrices.isEmpty() && BigDecimal.ZERO.compareTo(pe.getEventPrice()) != 0) {
            ValidationException ve = new ValidationException(ValidationException.ReasonEnum.STEPPED_EVENT_PRICING,
                    "eventPrice", new Object[] { event.getEventIdentifier() });
            logger.logWarn(Log4jLogger.SYSTEM_LOG, ve,
                    LogMessageIdentifier.WARN_STEPPED_PRICING_MIXED_WITH_BASEPRICE,
                    pe.getPriceModel().getProduct().getProductId(), "priced event", String.valueOf(pe.getKey()));
            throw ve;
        }
    }

    /**
     * Throw an OperationNotPermittedException if the key of the value object is
     * not equal to the key of the domain object. The validation is only
     * performed if the flag priceModelCreatedInTransaction is <code>true</code>
     * .
     * 
     * @param vo
     *            The value object representation.
     * @param domainObject
     *            The domain object representation.
     * @param priceModelCreatedInTransaction
     *            Indicates whether a key-clash can be ignored or not.
     * @throws OperationNotPermittedException
     *             Thrown if the key values do not match.
     */
    private void validateDomainObjectKey(BaseVO vo, DomainObject<?> domainObject,
            boolean priceModelCreatedInTransaction) throws OperationNotPermittedException {
        if (domainObject != null && !priceModelCreatedInTransaction && domainObject.getKey() != vo.getKey()) {
            PlatformUser user = dm.getCurrentUser();
            OperationNotPermittedException onp = new OperationNotPermittedException(
                    "Saving the price model failed.");
            logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                    LogMessageIdentifier.WARN_STORE_DOMAIN_OBJECT_FAILED_WRONG_TECHNICAL_KEY, user.getUserId(),
                    String.valueOf(domainObject));
            throw onp;
        }
    }

    /**
     * Converts the list of {@link VOSteppedPrice} to be set to a list of
     * {@link SteppedPrice} that can be saved. One of the three parent entities
     * must be passed. Stepped prices that shall be attached on a priced
     * parameter will be validated according to their parameter based
     * constraints (numeric parameters only; no limits out of the defined
     * range).
     * 
     * @param voSteppedPrices
     *            the list of stepped prices to be set
     * @param priceModel
     *            The parent price model if the stepped pricing is done on the
     *            price per user of the price model
     * @param pricedEvent
     *            The parent priced event if the stepped pricing is done based
     *            on the number of occurrence of an event
     * @param pricedParameter
     *            the parent priced parameter if the stepped pricing is done
     *            based on the value of a parameter
     * @param priceModelCreatedInTransaction
     *            Indicates whether the price model was created in this
     *            transaction or not.
     * @return the list of {@link SteppedPrice} to be set at the parent
     * @throws ValidationException
     *             in case of stepped pricing on a parameter of a non numeric
     *             type or violated range limits
     * @throws ConcurrentModificationException
     *             Thrown in case the passed value object's version does not
     *             match the domain object.
     * @throws OperationNotPermittedException
     *             Thrown in case the passed value object key does not belong to
     *             a domain object.
     */
    List<SteppedPrice> convertAndValidateSteppedPrices(long voPriceModelKey, List<VOSteppedPrice> voSteppedPrices,
            PriceModel priceModel, PricedEvent pricedEvent, PricedParameter pricedParameter,
            boolean priceModelCreatedInTransaction)
            throws ValidationException, ConcurrentModificationException, OperationNotPermittedException {

        List<SteppedPrice> existing = new ArrayList<>();

        if (pricedEvent == null && pricedParameter == null && priceModel != null) {
            existing = priceModel.getSteppedPrices();
        } else if (pricedEvent != null) {
            existing = pricedEvent.getSteppedPrices();
        } else if (pricedParameter != null) {
            existing = pricedParameter.getSteppedPrices();
        }

        Map<Long, SteppedPrice> keyToSteppedPrice = new HashMap<>();

        if (existing != null) {
            for (SteppedPrice sp : existing) {
                keyToSteppedPrice.put(Long.valueOf(sp.getKey()), sp);
            }
        }
        List<SteppedPrice> result = new ArrayList<>();
        if (voSteppedPrices != null) {
            for (VOSteppedPrice voSteppedPrice : voSteppedPrices) {
                if (pricedParameter != null) {
                    validateSteppedPriceContraints(pricedParameter, voSteppedPrice);
                }

                SteppedPrice steppedPrice;
                if (keyToSteppedPrice.containsKey(Long.valueOf(voSteppedPrice.getKey()))) {

                    steppedPrice = keyToSteppedPrice.remove(Long.valueOf(voSteppedPrice.getKey()));
                    updateSteppedPrice(voSteppedPrice, steppedPrice, priceModel, pricedEvent, pricedParameter);

                } else {
                    if (!priceModelCreatedInTransaction && voSteppedPrice.getKey() != 0) {
                        OperationNotPermittedException onp = new OperationNotPermittedException(
                                "Priced parameter with invalid key.");
                        logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                                LogMessageIdentifier.WARN_PRICED_PARAMETER_WITH_INVALID_KEY);
                        throw onp;
                    }
                    steppedPrice = createSteppedPrice(voSteppedPrice, priceModel, pricedEvent, pricedParameter);
                }
                result.add(steppedPrice);
            }
        }

        // set stepped prices before remove entities, to remove the references
        if (pricedEvent == null && pricedParameter == null && priceModel != null) {
            priceModel.setSteppedPrices(result);
        } else if (pricedEvent != null) {
            pricedEvent.setSteppedPrices(result);
        } else if (pricedParameter != null) {
            pricedParameter.setSteppedPrices(result);
        }

        removeSteppedPrices(voPriceModelKey, keyToSteppedPrice.values(), pricedEvent, pricedParameter, priceModel);

        return result;
    }

    SteppedPrice createSteppedPrice(VOSteppedPrice voSteppedPrice, PriceModel priceModel, PricedEvent pricedEvent,
            PricedParameter pricedParameter) throws ValidationException {

        SteppedPrice steppedPrice = SteppedPriceAssembler.toSteppedPrice(voSteppedPrice);
        setSteppedPrice(steppedPrice, priceModel, pricedEvent, pricedParameter);

        if (pricedEvent == null && pricedParameter == null && priceModel != null) {
            priceModelAudit.insertUserSteppedPrice(dm, steppedPrice);
        } else if (pricedEvent != null) {
            priceModelAudit.insertEventSteppedPrice(dm, steppedPrice);
        } else if (pricedParameter != null) {
            priceModelAudit.insertParameterSteppedPrice(dm, steppedPrice);
        }
        return steppedPrice;
    }

    private void setSteppedPrice(SteppedPrice steppedPrice, PriceModel priceModel, PricedEvent pricedEvent,
            PricedParameter pricedParameter) {

        steppedPrice.setPricedEvent(pricedEvent);
        steppedPrice.setPricedParameter(pricedParameter);
        if (pricedEvent != null || pricedParameter != null) {
            steppedPrice.setPriceModel(null);
        } else {
            steppedPrice.setPriceModel(priceModel);
        }
    }

    void updateSteppedPrice(VOSteppedPrice voSteppedPrice, SteppedPrice steppedPrice, PriceModel priceModel,
            PricedEvent pricedEvent, PricedParameter pricedParameter)
            throws ValidationException, ConcurrentModificationException {

        Long oldLimit = steppedPrice.getLimit();
        BigDecimal oldPrice = steppedPrice.getPrice();
        SteppedPriceAssembler.updateSteppedPrice(voSteppedPrice, steppedPrice);
        setSteppedPrice(steppedPrice, priceModel, pricedEvent, pricedParameter);

        if (pricedEvent == null && pricedParameter == null && priceModel != null) {
            priceModelAudit.editUserSteppedPrice(dm, steppedPrice, oldPrice, oldLimit);
        } else if (pricedEvent != null) {
            priceModelAudit.editEventSteppedPrice(dm, steppedPrice, oldPrice, oldLimit);
        } else if (pricedParameter != null) {
            priceModelAudit.editParameterSteppedPrice(dm, steppedPrice, oldPrice, oldLimit);
        }
    }

    void removeSteppedPrices(long voPriceModelKey, Collection<SteppedPrice> steppedPrices, PricedEvent pricedEvent,
            PricedParameter pricedParameter, PriceModel priceModel) {
        for (SteppedPrice steppedPrice : steppedPrices) {
            // log
            if (voPriceModelKey > 0) {
                /*
                 * For priceModel creation : do not log stepped prices again. It
                 * is already done in createSteppedPrices <br/> For VOPriceModel
                 * update : log removed stepped prices
                 */
                if (pricedEvent == null && pricedParameter == null && priceModel != null) {
                    priceModelAudit.removeUserSteppedPrice(dm, steppedPrice);
                } else if (pricedEvent != null) {
                    priceModelAudit.removeEventSteppedPrice(dm, steppedPrice);
                } else if (pricedParameter != null) {
                    priceModelAudit.removeParameterSteppedPrice(dm, steppedPrice);
                }
            }

            dm.remove(steppedPrice);
        }
    }

    /**
     * Validates the stepped price - priced parameter constraints:
     * <ul>
     * <li>stepped pricing can only be done on parameters of type
     * {@link ParameterValueType#DURATION}, {@link ParameterValueType#INTEGER}
     * and {@link ParameterValueType#LONG}</li>
     * <li>the step limits must be within the data type limits and the defined
     * parameter minimum and maximum value</li>
     * </ul>
     * 
     * @param pricedParameter
     *            the priced parameter the stepped prices will be attached to
     * @param steppedPrice
     *            the stepped price
     * @throws ValidationException
     *             in case of an validation error
     */
    void validateSteppedPriceContraints(PricedParameter pricedParameter, VOSteppedPrice steppedPrice)
            throws ValidationException {

        ParameterDefinition paramDef = pricedParameter.getParameter().getParameterDefinition();
        ParameterValueType type = paramDef.getValueType();
        if (type == ParameterValueType.BOOLEAN || type == ParameterValueType.ENUMERATION
                || type == ParameterValueType.STRING || type == ParameterValueType.DURATION) {
            // Value based stepped parameter prices can only be defined
            // for parameter types INTEGER and LONG. for all
            // other types the value based stepped prices list must be
            // empty for this check value is already tested: param !=
            // null
            throw new ValidationException(ReasonEnum.STEPPED_PRICING,
                    VOSteppedPrice.FIELD_NAME_PRICED_PARAMETER_VALUE, new Object[] { steppedPrice });
        }

        if (steppedPrice.getLimit() == null) {
            return;
        }
        // If the stepped prices are defined for a parameter value, it must be
        // checked that the limits are in the defined range for the parameter
        // value (minValue, maxValue) or in the logical range  as limit is a
        // long value, e. g. for parameters of type INTEGER, the limit must be
        // also within the java integer range.
        long limit = steppedPrice.getLimit().longValue();
        Long minValue = paramDef.getMinimumValue();
        Long maxValue = paramDef.getMaximumValue();
        if (type == ParameterValueType.INTEGER) {
            BLValidator.isInteger(SteppedPriceAssembler.FIELD_NAME_LIMIT, String.valueOf(limit));
            BLValidator.isInRange(SteppedPriceAssembler.FIELD_NAME_LIMIT, limit, minValue, maxValue);
        } else if (type == ParameterValueType.LONG) {
            BLValidator.isInRange(SteppedPriceAssembler.FIELD_NAME_LIMIT, limit, minValue, maxValue);
        }

    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails savePriceModelForCustomer(VOServiceDetails service, VOPriceModel priceModel,
            VOOrganization customer) throws OrganizationAuthoritiesException, ObjectNotFoundException,
            OperationNotPermittedException, CurrencyException, ValidationException, ServiceStateException,
            PriceModelException, ServiceOperationException, ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notNull("priceModel", priceModel);
        ArgumentValidator.notNull("customer", customer);

        Organization targetCustomer = dm.getReference(Organization.class, customer.getKey());

        PlatformUser currentUser = dm.getCurrentUser();
        validateCustomersRole(targetCustomer, currentUser);
        PermissionCheck.supplierOfCustomer(currentUser.getOrganization(), targetCustomer, logger, sessionCtx);

        validateIfCustomerServiceExists(service, targetCustomer);
        validateCurrencyUniqunessOfMigrationPath(priceModel, service);

        Product product = prepareProductWithPriceModel(service, priceModel, targetCustomer,
                ServiceType.CUSTOMER_TEMPLATE, null);

        dm.flush();
        dm.refresh(product);

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        VOServiceDetails result = getServiceDetails(product, facade);

        return result;
    }

    void validateCustomersRole(Organization targetCustomer, PlatformUser currentUser)
            throws OperationNotPermittedException {
        // ensure that the customer is a) authorized as customer and b)
        // registered for the calling
        if (!targetCustomer.hasRole(OrganizationRoleType.CUSTOMER)) {
            OperationNotPermittedException onp = new OperationNotPermittedException(
                    "Creation of customer specific price model failed");
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                    LogMessageIdentifier.WARN_CREATE_PRICE_MODEL_FOR_ORGANIZATION_FAILED_NOT_AUTHORIZED,
                    currentUser.getUserId(), Long.toString(currentUser.getOrganization().getKey()));
            throw onp;
        }
    }

    private void validateIfCustomerServiceExists(VOServiceDetails service, Organization targetCustomer)
            throws ObjectNotFoundException, ServiceOperationException, ServiceStateException {
        // ensure that in case the given product is a not a copy itself, no
        // other copy of it does exist for the current customer. If there
        // already is one, throw an exception. The caller should use the
        // customer copy as argument
        Product referencedProduct = dm.getReference(Product.class, service.getKey());
        if (referencedProduct.getTemplate() == null) {
            // check if there is a copy for this customer
            Query query = dm.createNamedQuery("Product.getCopyForCustomer");
            query.setParameter("template", referencedProduct);
            query.setParameter("customer", targetCustomer);
            List<Product> resultList = ParameterizedTypes.list(query.getResultList(), Product.class);
            if (resultList.size() > 0) {
                ServiceOperationException sof = new ServiceOperationException(Reason.CUSTOMER_COPY_ALREADY_EXISTS);
                logger.logWarn(Log4jLogger.SYSTEM_LOG, sof,
                        LogMessageIdentifier.WARN_EX_SERVICE_OPERATION_EXCEPTION_CUSTOMER_COPY_ALREADY_EXISTS);
                throw sof;
            }
        }

        ProductValidator.validateActiveOrInactiveOrSuspended(referencedProduct.getProductId(),
                referencedProduct.getStatus());
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails savePriceModelForSubscription(VOServiceDetails service, VOPriceModel priceModel)
            throws OrganizationAuthoritiesException, ObjectNotFoundException, OperationNotPermittedException,
            CurrencyException, ValidationException, ConcurrentModificationException, SubscriptionStateException,
            PaymentInformationException, PriceModelException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notNull("priceModel", priceModel);

        PlatformUser currentUser = dm.getCurrentUser();

        Product product = dm.getReference(Product.class, service.getKey());
        validateExternalServiceMustBeFree(priceModel, product.getTechnicalProduct().getAccessType());

        // ensure the subscription belongs to the given product
        Subscription sub = validateSubscription(service, currentUser, product);

        validatePriceModel(priceModel, product, sub);

        // Free trial period value cannot be changed on saving the price model
        // for subscription
        validateFreePeriod(priceModel, product);

        validateOneTimeFee(priceModel, product);

        validateCurrencyUniqunessOfMigrationPath(priceModel, service);
        // as every subscription already has a separate product copy, don't
        // create a new one!! just retrieve the reference to the old
        product = null;
        try {
            product = prepareProductWithPriceModel(service, priceModel, sub.getOrganization(),
                    ServiceType.SUBSCRIPTION, sub);
        } catch (ServiceStateException e) {
            // this must not happen
            throw new SaaSSystemException(e);
        }

        dm.flush();
        dm.refresh(product);

        LocalizerFacade facade = new LocalizerFacade(localizer, currentUser.getLocale());
        VOServiceDetails result = getServiceDetails(product, facade);

        return result;
    }

    /**
     * test for not rewriting one-time fee for subscription price model for
     * Price Model for subscription we can not change one-time fee, new value
     * has to be always 0
     */
    private void validateOneTimeFee(VOPriceModel priceModel, Product product)
            throws OperationNotPermittedException {
        BigDecimal oneTimeFeeNew = priceModel.getOneTimeFee();
        BigDecimal oneTimeFeeOld = product.getPriceModel().getOneTimeFee();
        if (oneTimeFeeNew.compareTo(BigDecimal.ZERO) != 0) {
            if (oneTimeFeeNew.compareTo(oneTimeFeeOld) != 0) {
                OperationNotPermittedException onp = new OperationNotPermittedException(
                        "One-time fee can not be changed for a subscription-specific price model.");
                logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                        LogMessageIdentifier.WARN_UNCHANGEABLE_SUBSCRIPTION_ONE_TIME_FEE);
                throw onp;
            }
        } else {
            priceModel.setOneTimeFee(oneTimeFeeOld);
        }
    }

    private void validateFreePeriod(VOPriceModel priceModel, Product product)
            throws OperationNotPermittedException {
        int freePeriodNew = priceModel.getFreePeriod();
        int freePeriodOld = product.getPriceModel().getFreePeriod();
        if (freePeriodNew != freePeriodOld) {
            OperationNotPermittedException onp = new OperationNotPermittedException(
                    "Free trial period can not be changed for a subscription-specific price model.");

            logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                    LogMessageIdentifier.WARN_SAVE_PRICE_MODEL_FOR_SUBSCRIPTION_FAILED_FREEPERIOD_UNMODIFIABLE);

            throw onp;
        }
    }

    private void validatePriceModel(VOPriceModel priceModel, Product product, Subscription sub)
            throws PaymentInformationException, PriceModelException {
        validatePriceModelType(product.getPriceModel(), priceModel);
        validateCurrency(product.getPriceModel(), priceModel);
        validateTimeUnit(product.getPriceModel(), priceModel);

        if (priceModel.isChargeable()) {

            if (sub.getPaymentInfo() == null || sub.getBillingContact() == null) {
                PaymentInformationException pie = new PaymentInformationException(String.format(
                        "Defining the price model for the subscription %s is not possible, as no payment information has been specified.",
                        sub));
                logger.logWarn(Log4jLogger.SYSTEM_LOG, pie,
                        LogMessageIdentifier.WARN_SAVE_PRICE_MODEL_FAILED_NO_PAYMENT_INFO, sub.toString());
                sessionCtx.setRollbackOnly();
                throw pie;
            }

        }

        // Free of charge value cannot be changed on saving the price model for
        // subscription
        boolean chargeableValueNew = priceModel.isChargeable();
        boolean chargeableValueOld = product.getPriceModel().isChargeable();
        if (chargeableValueNew != chargeableValueOld) {
            PriceModelException pme = new PriceModelException(PriceModelException.Reason.UNMODIFIABLE_CHARGEABLE);
            logger.logWarn(Log4jLogger.SYSTEM_LOG, pme,
                    LogMessageIdentifier.WARN_UNCHANGEABLE_SUBSCRIPTION_CHARGING_CONDITIONS);
            sessionCtx.setRollbackOnly();
            throw pme;
        }
    }

    void validatePriceModelType(PriceModel existingPriceModel, VOPriceModel newPriceModel)
            throws PriceModelException {
        if (!existingPriceModel.getType().equals(newPriceModel.getType())) {
            PriceModelException pme = new PriceModelException(PriceModelException.Reason.UNMODIFIABLE_TYPE);
            logger.logWarn(Log4jLogger.SYSTEM_LOG, pme,
                    LogMessageIdentifier.WARN_PRICE_MODEL_TYPE_UNMODIFIABLE_FOR_SUBSCRIPTION);
            throw pme;
        }
    }

    void validateCurrency(PriceModel existingPriceModel, VOPriceModel newPriceModel) throws PriceModelException {
        if (existingPriceModel.isChargeable()) {
            boolean isCurrencyChanged = !(existingPriceModel.getCurrency().getCurrencyISOCode()
                    .equals(newPriceModel.getCurrencyISOCode()));
            if (isCurrencyChanged) {
                PriceModelException pme = new PriceModelException(PriceModelException.Reason.UNMODIFIABLE_CURRENCY);
                logger.logWarn(Log4jLogger.SYSTEM_LOG, pme,
                        LogMessageIdentifier.WARN_CURRENCY_UNMODIFIABLE_FOR_SUBSCRIPTION);
                throw pme;
            }
        }
    }

    void validateTimeUnit(PriceModel existingPriceModel, VOPriceModel newPriceModel) throws PriceModelException {
        if (existingPriceModel.isChargeable()) {
            boolean isTimeUnitChanged = !(existingPriceModel.getPeriod().equals(newPriceModel.getPeriod()));
            if (isTimeUnitChanged) {
                PriceModelException pme = new PriceModelException(PriceModelException.Reason.UNMODIFIABLE_TIMEUNIT);
                logger.logWarn(Log4jLogger.SYSTEM_LOG, pme,
                        LogMessageIdentifier.WARN_TIME_UNIT_UNMODIFIABLE_FOR_SUBSCRIPTION);
                throw pme;
            }
        }
    }

    private void validateExternalServiceMustBeFree(VOPriceModel priceModel, ServiceAccessType sat)
            throws ValidationException {

        if (priceModel.isChargeable() && sat == ServiceAccessType.EXTERNAL) {
            throw new ValidationException(ReasonEnum.EXTERNAL_SERVICE_MUST_BE_FREE_OF_CHARGE, null, null);

        }
    }

    @Override
    public VOSubscriptionDetails validateSubscription(VOService service)
            throws OperationNotPermittedException, SubscriptionStateException, ObjectNotFoundException {
        PlatformUser currentUser = dm.getCurrentUser();
        Product product = dm.getReference(Product.class, service.getKey());
        Subscription subscription = validateSubscription(service, currentUser, product);
        VOSubscriptionDetails voSubscriptionDetails = subscriptionService
                .getSubscriptionDetailsWithoutOwnerCheck(subscription.getKey());
        return voSubscriptionDetails;
    }

    private Subscription validateSubscription(VOService service, PlatformUser currentUser, Product product)
            throws OperationNotPermittedException, SubscriptionStateException {
        Subscription sub = product.getOwningSubscription();
        if (sub == null) {
            OperationNotPermittedException onp = new OperationNotPermittedException(
                    "Creation of subscription specific price model failed");
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                    LogMessageIdentifier.WARN_CREATE_PRICE_MODEL_FAILED_NOT_SUBSCRIPTION_DEFINED,
                    currentUser.getUserId(), Long.toString(product.getKey()), Long.toString(service.getKey()));
            throw onp;
        }

        if (sub.getStatus() == SubscriptionStatus.EXPIRED || sub.getStatus() == SubscriptionStatus.INVALID
                || sub.getStatus() == SubscriptionStatus.DEACTIVATED) {

            Object[] params = new Object[] { sub.getStatus().name() };
            SubscriptionStateException sse = new SubscriptionStateException(
                    SubscriptionStateException.Reason.SUBSCRIPTION_STATE_CHANGED, null, params);
            logger.logWarn(Log4jLogger.SYSTEM_LOG, sse,
                    LogMessageIdentifier.WARN_SAVE_PRICE_MODEL_FAILED_SUBSCRIPTION_STATE_INVALID);
            throw sse;
        }
        return sub;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public List<VOService> getCompatibleServices(VOService service)
            throws OrganizationAuthoritiesException, ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);

        List<VOService> result = new ArrayList<>();

        // verify that the caller is a supplier
        Organization org = dm.getCurrentUser().getOrganization();

        // retrieve the domain object for the product
        Product prod = dm.getReference(Product.class, service.getKey());

        // ensure that the product belongs to the calling supplier
        PermissionCheck.owns(prod, org, logger, sessionCtx);

        // determine the compatible products
        List<Product> compatibleProducts = prod.getCompatibleProductsList();

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        for (Product compatibleProduct : compatibleProducts) {
            // only consider not deleted and not obsolete target products
            if (compatibleProduct.getStatus() != ServiceStatus.DELETED
                    && compatibleProduct.getStatus() != ServiceStatus.OBSOLETE) {
                // check currency compatibility
                if (isCompatibleCurrency(prod.getPriceModel(), compatibleProduct.getPriceModel())) {
                    result.add(ProductAssembler.toVOProduct(compatibleProduct, facade));
                }
            }
        }

        return result;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public void setCompatibleServices(VOService service, List<VOService> compatibleServices)
            throws OrganizationAuthoritiesException, ObjectNotFoundException, OperationNotPermittedException,
            ServiceCompatibilityException, ServiceStateException, ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notNull("compatibleServices", compatibleServices);

        // 1. ensure that the caller is a supplier
        Organization org = dm.getCurrentUser().getOrganization();

        // 2. find the domain objects for the reference product and the
        // compatible products. For each of them ensure that they are owned by
        // the calling supplier. Check relation to the owning technical product
        // as well
        Product referenceProduct = dm.getReference(Product.class, service.getKey());
        BaseAssembler.verifyVersionAndKey(referenceProduct, service);
        ProductValidator.validateInactiveOrSuspended(ProductAssembler.getProductId(referenceProduct),
                referenceProduct.getStatus());
        PermissionCheck.owns(referenceProduct, org, logger, sessionCtx);
        List<Product> compProducts = new ArrayList<>();
        for (VOService voProd : compatibleServices) {
            Product tempProd = dm.getReference(Product.class, voProd.getKey());
            BaseAssembler.verifyVersionAndKey(tempProd, voProd);
            PermissionCheck.owns(tempProd, org, logger, sessionCtx);

            validateTechnicalProductCompatibility(referenceProduct, tempProd);
            validateCurrencyCompatibility(referenceProduct, tempProd);
            validateMarketplaceCompatibility(referenceProduct, tempProd);
            compProducts.add(tempProd);
        }

        // 3. internal check: ensure that the products are no copies! if they
        // are, throw an exception
        boolean isCopy = false;
        long productKey = 0;
        if (referenceProduct.getTemplate() != null) {
            isCopy = true;
            productKey = referenceProduct.getKey();
        }
        for (Product compatibleProduct : compProducts) {
            if (compatibleProduct.getTemplate() != null) {
                isCopy = true;
                productKey = compatibleProduct.getKey();
            }
        }
        if (isCopy) {
            SaaSSystemException sse = new SaaSSystemException(
                    "Defining product compatibility failed, as product '" + productKey + "' is not a template");
            logger.logError(Log4jLogger.SYSTEM_LOG, sse,
                    LogMessageIdentifier.ERROR_INVALID_ARGUMENT_AS_PRODUCT_NOT_TEMPLATE);
            throw sse;
        }

        // 4. Create the compatibility information
        // first determine all products which are currently marked as compatible
        // to the reference product
        Set<Long> currentCompatibleProductKeys = new HashSet<>();
        Set<Long> obsoleteCompatibleProductKeys = new HashSet<>();
        for (Product currentlyCompatibleProduct : referenceProduct.getCompatibleProductsList()) {
            currentCompatibleProductKeys.add(Long.valueOf(currentlyCompatibleProduct.getKey()));
            obsoleteCompatibleProductKeys.add(Long.valueOf(currentlyCompatibleProduct.getKey()));
        }

        // now register references, determine and omit duplicates, add only new
        // entries
        List<ProductReference> newReferences = new ArrayList<>();
        for (Product prod : compProducts) {
            if (!currentCompatibleProductKeys.contains(Long.valueOf(prod.getKey()))) {
                ProductReference ref = new ProductReference(referenceProduct, prod);
                newReferences.add(ref);
                currentCompatibleProductKeys.add(Long.valueOf(prod.getKey()));
            }
            obsoleteCompatibleProductKeys.remove(Long.valueOf(prod.getKey()));
        }

        // finally clean up those products which are not marked as compatible
        // anymore, persist the new ones
        List<ProductReference> objToBeRemoved = new ArrayList<>();
        for (ProductReference currentlyCompatibleProductRef : referenceProduct.getAllCompatibleProducts()) {
            if (obsoleteCompatibleProductKeys
                    .contains(Long.valueOf(currentlyCompatibleProductRef.getTargetProduct().getKey()))) {
                dm.remove(currentlyCompatibleProductRef);
                objToBeRemoved.add(currentlyCompatibleProductRef);
            }
        }
        referenceProduct.getAllCompatibleProducts().removeAll(objToBeRemoved);
        for (ProductReference newProductRef : newReferences) {
            try {
                dm.persist(newProductRef);
                referenceProduct.getAllCompatibleProducts().add(newProductRef);
            } catch (NonUniqueBusinessKeyException e) {
                // it's a new creation, there is no business key, so this
                // scenario should never occur. If it does, something is
                // completely wrong, so abort by throwing a system exception
                SaaSSystemException sse = new SaaSSystemException("Persisting the product reference failed", e);
                logger.logError(Log4jLogger.SYSTEM_LOG, sse,
                        LogMessageIdentifier.ERROR_DEFINE_COMPATIBLE_PRODUCT_FAILED);
                throw sse;
            }
        }

        logServiceUpDownGradeOptions(referenceProduct, newReferences, DEFINEIPDOWNGRADE_ON);
        logServiceUpDownGradeOptions(referenceProduct, objToBeRemoved, DEFINEIPDOWNGRADE_OFF);

    }

    void logServiceUpDownGradeOptions(Product referencrProdut, List<ProductReference> targetProducts,
            String upDowngrade) {
        for (ProductReference productReference : targetProducts) {
            serviceAudit.defineUpDownGradeOptions(dm, referencrProdut, productReference.getTargetProduct(),
                    upDowngrade);
        }
    }

    /**
     * Checks that the target product is published on the same marketplace as
     * the source product.
     * 
     * @param referenceProduct
     *            the source product
     * @param compatibleProduct
     *            the target product
     * @throws ServiceCompatibilityException
     */
    private void validateMarketplaceCompatibility(Product referenceProduct, Product compatibleProduct)
            throws ServiceCompatibilityException {
        Set<Marketplace> mps = new HashSet<>();
        Product s = referenceProduct.getTemplateOrSelf();
        Product t = compatibleProduct.getTemplateOrSelf();
        for (CatalogEntry ce : s.getCatalogEntries()) {
            if (ce.getMarketplace() != null) {
                mps.add(ce.getMarketplace());
            }
        }
        for (CatalogEntry ce : t.getCatalogEntries()) {
            if (ce.getMarketplace() != null && mps.contains(ce.getMarketplace())) {
                return;
            }
        }
        ServiceCompatibilityException ipc = new ServiceCompatibilityException(
                "Definition of product compatibility failed, they are not published on the same marketplace",
                ServiceCompatibilityException.Reason.MARKETPLACE);
        logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, ipc,
                LogMessageIdentifier.WARN_DEFINE_COMPATIBILITY_FOR_PRODUCTS_FAILED_NOT_SAME_MARKETPLACE,
                dm.getCurrentUser().getUserId(), Long.toString(s.getKey()), Long.toString(t.getKey()));
        throw ipc;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public void deleteService(VOService service)
            throws OrganizationAuthoritiesException, ObjectNotFoundException, OperationNotPermittedException,
            ServiceOperationException, ServiceStateException, ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);

        // 1. Determine the callers organization, load the product and ensure
        // that the callers organization owns the product
        Organization supplier = dm.getCurrentUser().getOrganization();
        Product productToDelete = dm.getReference(Product.class, service.getKey());
        BaseAssembler.verifyVersionAndKey(productToDelete, service);
        deleteProduct(supplier, productToDelete);
        serviceAudit.deleteService(dm, productToDelete);

    }

    @Override
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void deleteProduct(Organization supplier, Product product)
            throws OperationNotPermittedException, ServiceOperationException, ServiceStateException {

        PermissionCheck.owns(product, supplier, logger, sessionCtx);

        List<CatalogEntry> catalogEntries = product.getCatalogEntries();

        Marketplace marketplace = null;
        if (product.getCatalogEntries().size() > 0) {
            marketplace = catalogEntries.get(0).getMarketplace();
        }

        // 2. if the product is subscription specific, throw an exception
        if (product.getOwningSubscription() != null) {
            ServiceOperationException sof = new ServiceOperationException(Reason.DELETION_FAILED_USED_BY_SUB);
            logger.logWarn(Log4jLogger.SYSTEM_LOG, sof,
                    LogMessageIdentifier.WARN_PRODUCT_DELETION_FAILED_STILL_USED_BY_SUBSCRIPTION,
                    Long.toString(product.getKey()), Long.toString(product.getOwningSubscription().getKey()));
            throw sof;
        }

        // 3. if it's a customer specific copy, delete it
        else if (product.getTemplate() != null && product.getTargetCustomer() != null) {
            ProductValidator.validateInactiveOrSuspended(ProductAssembler.getProductId(product),
                    product.getStatus());
            deletePriceModelForCustomer(product);
        }

        // 4. if the product is not a copy, set its status to deleted and delete
        // all customer specific copies that are not used by any subscription
        else {
            ProductValidator.validateInactiveOrSuspended(ProductAssembler.getProductId(product),
                    product.getStatus());
            // check if there are active customer specific services
            long count = countNonSubscriptionCopiesInState(product, EnumSet.of(ServiceStatus.ACTIVE));
            if (count > 0) {
                String expected = ServiceStatus.INACTIVE.name() + ", " + ServiceStatus.SUSPENDED.name();
                throw new ServiceStateException(ServiceStatus.ACTIVE, expected,
                        ProductAssembler.getProductId(product));
            }

            // validate that there are no resale permissions for the MS
            validateNotExistingResalePermissions(product);

            product.setStatus(ServiceStatus.DELETED);
            // rename to allow reuse of the id
            product.setProductId(product.getProductId() + "#" + String.valueOf(System.currentTimeMillis()));

            // now delete all it's customer specific copies
            List<Product> customerSpecificProductCopies = getCustomerSpecificProductCopies(product);
            for (Product copy : customerSpecificProductCopies) {
                dm.remove(copy);
            }
        }

        if (marketplace != null) {
            landingpageService.removeProductFromLandingpage(marketplace, product);
        }

    }

    void deletePriceModelForCustomer(Product product) {
        priceModelAudit.deletePriceModel(dm, product.getPriceModel());
        dm.remove(product);
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public boolean statusAllowsDeletion(VOService service)
            throws OperationNotPermittedException, ObjectNotFoundException, ConcurrentModificationException {
        ArgumentValidator.notNull("service", service);
        Product productToDelete;

        productToDelete = dm.getReference(Product.class, service.getKey());
        Organization supplier = dm.getCurrentUser().getOrganization();
        PermissionCheck.owns(productToDelete, supplier, logger, sessionCtx);
        BaseAssembler.verifyVersionAndKey(productToDelete, service);
        Set<ServiceStatus> invalidSates = EnumSet.of(ServiceStatus.ACTIVE, ServiceStatus.DELETED,
                ServiceStatus.OBSOLETE);
        boolean rc = !invalidSates.contains(productToDelete.getStatus());
        if (rc) {
            long count = countNonSubscriptionCopiesInState(productToDelete, invalidSates);
            rc = count == 0;
        }
        return rc;
    }

    private long countNonSubscriptionCopiesInState(Product template, Set<ServiceStatus> states) {
        Query query = dm.createNamedQuery("Product.countCustomerCopiesForTemplateInState");
        query.setParameter("template", template);
        query.setParameter("status", states);
        Long count = (Long) query.getSingleResult();
        return count.longValue();
    }

    /**
     * Check if there are resale permissions for the given product template
     * 
     * @param template
     *            The product template
     */
    void validateNotExistingResalePermissions(Product template) throws ServiceOperationException {

        Query query = dm.createNamedQuery("Product.getPartnerCopiesForTemplateNotInState");
        query.setParameter("template", template);
        query.setParameter("statusToIgnore", ServiceStatus.DELETED);
        @SuppressWarnings("unchecked")
        List<Product> result = query.getResultList();

        if (result.size() > 0) {
            ServiceOperationException sof = new ServiceOperationException(
                    Reason.DELETION_FAILED_EXISTING_RESALE_PERMISSION);
            logger.logError(Log4jLogger.SYSTEM_LOG, sof,
                    LogMessageIdentifier.ERROR_SERVICE_DELETION_FAILED_EXISTING_RESALE_PERMISSION,
                    Long.toString(template.getKey()));
            throw sof;
        }

    }

    /**
     * Returns all customer specific product copies, that are not used in any
     * subscription.
     * 
     * @param template
     *            The product to retrieve the copies for.
     * @return The list of customer specific products.
     */
    private List<Product> getCustomerSpecificProductCopies(Product template) {

        Query query = dm.createNamedQuery("Product.getCustomerCopies");
        query.setParameter("template", template);
        List<Product> result = ParameterizedTypes.list(query.getResultList(), Product.class);

        return result;
    }

    /**
     * Returns the platform events defined in the system. No platform events are
     * available if the specified technical service is defined for the
     * <code>DIRECT</code> or <code>USER</code> access type.
     * 
     * @param tp
     *            The technical product to retrieve the events for.
     * 
     * @return The platform events.
     */
    List<Event> getPlatformEvents(TechnicalProduct tp) {

        List<Event> result = new ArrayList<>();
        Query query = dm.createNamedQuery("Event.getAllPlatformEvents");
        query.setParameter("eventType", EventType.PLATFORM_EVENT);
        result = ParameterizedTypes.list(query.getResultList(), Event.class);
        if (tp.getAccessType() == ServiceAccessType.DIRECT || tp.getAccessType() == ServiceAccessType.USER) {
            List<Event> copy = new ArrayList<>(result);
            for (Event ed : copy) {
                if (EventType.PLATFORM_EVENT == ed.getEventType()) {
                    result.remove(ed);
                }
            }
        }

        return result;
    }

    /**
     * Reads the platform parameters and removes
     * <ul>
     * <li>the user related ones for technical products of type
     * <code>DIRECT</code> access</li>
     * <li>the maximum number of concurrent user per subscription for technical
     * products of type <code>USER</code> access</li>
     * </ul>
     * 
     * @param tp
     *            the technical product to get the platform parameters for
     * @return the list of platform parameters for the technical product
     */
    private List<ParameterDefinition> getPlatformParameterDefinitions(TechnicalProduct tp) {

        Query query = dm.createNamedQuery("ParameterDefinition.getAllPlatformParameterDefinitions");
        query.setParameter("parameterType", ParameterType.PLATFORM_PARAMETER);
        List<ParameterDefinition> result = ParameterizedTypes.list(query.getResultList(),
                ParameterDefinition.class);
        if (tp.getAccessType() == ServiceAccessType.DIRECT || tp.getAccessType() == ServiceAccessType.USER) {
            List<ParameterDefinition> copy = new ArrayList<>(result);
            for (ParameterDefinition pd : copy) {
                if (PlatformParameterIdentifiers.CONCURRENT_USER.equals(pd.getParameterId())
                        || (tp.getAccessType() == ServiceAccessType.DIRECT
                                && PlatformParameterIdentifiers.NAMED_USER.equals(pd.getParameterId()))) {
                    result.remove(pd);
                }
            }
        }

        return result;
    }

    /**
     * Reads the product result list from the given query's single result. If no
     * result is found, <code>null</code> is returned. If the result is not
     * unique, a system exception will be thrown.
     * 
     * @param cust
     *            The customer the product is requested for.
     * @param query
     *            The query object.
     * @return The product returned by the query.
     */
    private Product getProductFromQueryResult(Organization cust, Query query) {
        Product customerProduct = null;
        try {
            customerProduct = (Product) query.getSingleResult();
        } catch (NoResultException e) {
            // product was not found, so return null
            return null;
        } catch (NonUniqueResultException e) {
            SaaSSystemException sse = new SaaSSystemException(
                    "Product retrieval failed due to invalid result, found duplicate entry.");
            logger.logError(Log4jLogger.SYSTEM_LOG, sse,
                    LogMessageIdentifier.ERROR_RETRIEVAL_CUSTOMER_OR_SUBSCRIPTION_FAILED_RESULT_NOT_UNIQUE,
                    Long.toString(cust.getKey()));
            throw sse;
        }
        return customerProduct;
    }

    @Override
    public VOServiceLocalization getServiceLocalization(VOService service)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);
        return spsLocalizer.getServiceLocalization(dm.getReference(Product.class, service.getKey()));
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER" })
    public void saveServiceLocalization(VOService service, VOServiceLocalization localization)
            throws ObjectNotFoundException, OperationNotPermittedException, ValidationException,
            ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notNull("localization", localization);

        spsLocalizer.saveServiceLocalization(service.getKey(), localization);
    }

    @Override
    public VOPriceModelLocalization getPriceModelLocalization(VOPriceModel priceModel)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("priceModel", priceModel);

        if (!dm.getCurrentUser().getOrganization().getGrantedRoleTypes().contains(OrganizationRoleType.SUPPLIER)) {
            throw new OperationNotPermittedException("only suppliers are allowed to call this method.");
        }
        PriceModel pm = dm.getReference(PriceModel.class, priceModel.getKey());
        if (pm.getProduct() == null) {
            throw new OperationNotPermittedException("price model has no product assigned.");
        }
        return spsLocalizer.getPriceModelLocalization(pm.getProduct().getKey());
    }

    @Override
    public List<VOLocalizedText> getPriceModelLicenseTemplateLocalization(VOServiceDetails service)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);

        if (!spsLocalizer.checkIsAllowedForLocalizingService(service.getKey())) {
            throw new OperationNotPermittedException(
                    "No rights for getting price model localizations for license.");
        }

        List<VOLocalizedText> templates = localizer.getLocalizedValues(service.getTechnicalService().getKey(),
                LocalizedObjectTypes.PRODUCT_LICENSE_DESC);

        return templates;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER" })
    public void savePriceModelLocalization(VOPriceModel priceModel, VOPriceModelLocalization localization)
            throws ObjectNotFoundException, OperationNotPermittedException, ConcurrentModificationException {

        ArgumentValidator.notNull("priceModel", priceModel);
        ArgumentValidator.notNull("localization", localization);

        spsLocalizer.savePriceModelLocalizationForSupplier(priceModel.getKey(), priceModel.isChargeable(),
                localization);
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public List<VOService> getServicesForCustomer(VOOrganization customer)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("customer", customer);
        PlatformUser user = dm.getCurrentUser();
        Organization cust = dm.getReference(Organization.class, customer.getKey());

        List<Product> list = getCustomerSpecificProducts(cust, user.getOrganization());

        LocalizerFacade facade = new LocalizerFacade(localizer, user.getLocale());
        List<VOService> voList = new ArrayList<>();
        for (Product product : list) {
            voList.add(ProductAssembler.toVOProduct(product, facade));
        }

        return voList;
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public List<Product> getCustomerSpecificProducts(Organization cust, Organization seller)
            throws OperationNotPermittedException {

        PermissionCheck.supplierOfCustomer(seller, cust, logger, sessionCtx);
        Query query = dm.createNamedQuery("Product.getCustomerSpecificProducts");
        query.setParameter("vendorKey", Long.valueOf(seller.getKey()));
        query.setParameter("customer", cust);
        List<Product> list = ParameterizedTypes.list(query.getResultList(), Product.class);

        return list;
    }

    @Override
    public List<String> getSupportedCurrencies() {

        Query query = dm.createNamedQuery("SupportedCurrency.getAll");
        List<String> curlist = new ArrayList<>();
        for (SupportedCurrency curr : ParameterizedTypes.iterable(query.getResultList(), SupportedCurrency.class)) {
            curlist.add(curr.getCurrencyISOCode());
        }

        return curlist;

    }

    @Override
    public VOImageResource loadImage(Long serviceKey) {

        ArgumentValidator.notNull("serviceKey", serviceKey);

        VOImageResource vo = null;

        Product product = dm.find(Product.class, serviceKey);

        if (product != null) {
            ImageResource imageResource = irm.read(product.getKey(), ImageType.SERVICE_IMAGE);
            while (imageResource == null && product.getTemplate() != null) {
                product = product.getTemplate();
                imageResource = irm.read(product.getKey(), ImageType.SERVICE_IMAGE);
            }
            if (imageResource != null) {
                vo = new VOImageResource();
                vo.setBuffer(imageResource.getBuffer());
                vo.setContentType(imageResource.getContentType());
                vo.setImageType(ImageType.SERVICE_IMAGE);
            }
        }

        return vo;
    }

    @Override
    public VOImageResource loadImageForSupplier(String serviceId, String supplierId)
            throws ObjectNotFoundException {

        ArgumentValidator.notNull("serviceId", serviceId);
        ArgumentValidator.notNull("supplierId", supplierId);

        VOImageResource vo = null;

        Product product = new Product();
        product.setProductId(serviceId);
        Organization supplier = new Organization();
        supplier.setOrganizationId(supplierId);
        // looking for supplier by id
        supplier = (Organization) dm.getReferenceByBusinessKey(supplier);
        product.setVendor(supplier);
        product = (Product) dm.find(product);

        if (product != null) {
            ImageResource imageResource = irm.read(product.getKey(), ImageType.SERVICE_IMAGE);
            if (imageResource != null) {
                vo = new VOImageResource();
                vo.setBuffer(imageResource.getBuffer());
                vo.setContentType(imageResource.getContentType());
                vo.setImageType(ImageType.SERVICE_IMAGE);
            }
        }

        return vo;
    }

    /**
     * Save or delete the images resource in/from the database
     * 
     * @param productKey
     *            the product key
     * @param voImageResource
     *            the image resource to store/delete
     */
    private void processImage(long productKey, VOImageResource voImageResource) throws ValidationException {

        if (voImageResource == null) {

            return;
        }

        if (voImageResource.getImageType() == null
                || voImageResource.getImageType().getOwnerType() != ImageOwnerType.SERVICE) {
            SaaSSystemException se = new SaaSSystemException("Only images belonging to a product can be saved.");
            logger.logError(Log4jLogger.SYSTEM_LOG, se, LogMessageIdentifier.ERROR_IMAGES_NOT_BELONG_TO_PRODUCT);
            throw se;
        }

        boolean isImageDeleted = voImageResource.getBuffer() == null;
        if (isImageDeleted) {
            irm.delete(productKey, voImageResource.getImageType());
        } else {

            try {
                ImageValidator.validate(voImageResource.getBuffer(), voImageResource.getContentType(), 80, 80, 80,
                        80);
            } catch (ValidationException e) {
                sessionCtx.setRollbackOnly();
                throw e;
            }

            ImageResource imageResource = new ImageResource();
            imageResource.setObjectKey(productKey);
            imageResource.setContentType(voImageResource.getContentType());
            imageResource.setBuffer(voImageResource.getBuffer());
            imageResource.setImageType(voImageResource.getImageType());
            irm.save(imageResource);
        }

    }

    /**
     * Returns true if an image is defined for the given product.
     */
    public boolean isImageDefined(Product product) {
        boolean flag = irm.read(product.getKey(), ImageType.SERVICE_IMAGE) != null;
        return flag;
    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public byte[] exportTechnicalServices(List<VOTechnicalService> technicalServices)
            throws OrganizationAuthoritiesException, ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("technicalServices", technicalServices);
        Organization provider = dm.getCurrentUser().getOrganization();
        List<TechnicalProduct> toExport = new ArrayList<>();
        for (VOTechnicalService product : technicalServices) {
            TechnicalProduct techProd = findTechnicalProductAndCheckOwner(provider, product);
            toExport.add(techProd);
        }
        TechnicalProductXmlConverter converter = new TechnicalProductXmlConverter();
        byte[] xml = converter.technicalProductToXml(toExport, localizer, dm);
        return xml;
    }

    private void setPricedParametersForPriceModel(long voPriceModelKey, PriceModel priceModel,
            List<PricedParameter> parametersToSet, List<VOPricedParameter> selectedParameters,
            boolean priceModelCreatedInTransaction, Organization targetCustomer)
            throws ValidationException, ConcurrentModificationException, OperationNotPermittedException {

        if (parametersToSet.isEmpty()) {
            // no parameter is specified, so remove all existing one
            for (PricedParameter pricedParameter : priceModel.getSelectedParameters()) {
                dm.remove(pricedParameter);
            }
            priceModel.setSelectedParameters(parametersToSet);
        } else {
            List<PricedParameter> parametersToBeStored = new ArrayList<>();
            Map<String, PricedParameter> curParameters = new HashMap<>();
            for (PricedParameter pp : priceModel.getSelectedParameters()) {
                if (pp.getParameter().getParameterDefinition().getValueType() == ParameterValueType.ENUMERATION
                        && pp.getPricedOptionList().isEmpty()) {
                    // remove the enumeration parameters that have no options -
                    // should never happen
                    dm.remove(pp);
                } else {
                    // fill the map
                    curParameters.put(pp.getParameter().getParameterDefinition().getParameterId(), pp);
                }
            }
            for (PricedParameter parameterToSet : parametersToSet) {

                String parameterId = parameterToSet.getParameter().getParameterDefinition().getParameterId();
                PricedParameter pricedParameter = curParameters.get(parameterId);
                if (pricedParameter != null) {
                    pricedParameter.setPricePerSubscription(parameterToSet.getPricePerSubscription());
                    pricedParameter.setPricePerUser(parameterToSet.getPricePerUser());

                    ParameterOptionAssembler.updatePriceOptions(pricedParameter, parameterToSet);

                    curParameters.remove(pricedParameter.getParameter().getParameterDefinition().getParameterId());
                    parametersToBeStored.add(pricedParameter);
                } else {
                    pricedParameter = parameterToSet;
                    parametersToBeStored.add(parameterToSet);
                }

                // handle priced product roles for the current parameter
                VOPricedParameter voPP = getPricedParamForParamId(selectedParameters, parameterId);
                setRoleSpecificPrices(voPriceModelKey, null, pricedParameter, null,
                        voPP.getRoleSpecificUserPrices(), priceModelCreatedInTransaction, targetCustomer, null,
                        null);
            }
            Collection<PricedParameter> obsoletePricedParameters = curParameters.values();
            for (PricedParameter obsoleteParameter : obsoletePricedParameters) {
                dm.remove(obsoleteParameter);
            }
            priceModel.setSelectedParameters(parametersToBeStored);
        }

    }

    /**
     * Iterates over the parameters and returns the one with the given
     * identifier.
     * 
     * @param selectedParameters
     *            The list of vo priced parameters to parse.
     * @param parameterId
     *            The parameter identifier to look for.
     * @return The priced parameter with the given identifier.
     */
    private VOPricedParameter getPricedParamForParamId(List<VOPricedParameter> selectedParameters,
            String parameterId) {
        for (VOPricedParameter voPricedParameter : selectedParameters) {
            if (voPricedParameter.getVoParameterDef().getParameterId().equals(parameterId)) {
                return voPricedParameter;
            }
        }
        // no priced param found, so the data validation did not work correctly.
        // Throw an exception
        SaaSSystemException sse = new SaaSSystemException(
                "Priced parameter input list does not contain a parameter for identifier '" + parameterId + "'");
        logger.logError(Log4jLogger.SYSTEM_LOG, sse,
                LogMessageIdentifier.ERROR_PRICED_PARAMETER_LIST_NOT_CONTAIN_PARAMETER_FOR_ID, parameterId);
        throw sse;
    }

    /**
     * Checks if the given parameters to be set for the price model are
     * contained in the parameter list for the product. If they are, a list of
     * consolidated priced parameters is returned. Otherwise an
     * OperationNotPermitted exception will be thrown.
     * 
     * @param voPricedParameters
     *            The parameters to be set for the price model.
     * @param productParams
     *            The current parameter list of the considered product.
     * @param priceModel
     *            The price model the priced parameters should be added to.
     * @param targetCustomer
     *            The targetCustomer of the price model.
     * @param subscription
     *            The subscription of the price model.
     * @param priceModelCreatedInTransaction
     *            Indicates whether the price model was created in this
     *            transaction or not.
     * @param isTemplateExistsForCustomer
     *            Indicates whether price model for service exists as a template
     *            for price model for customer
     * @return A list of priced parameters added to the price model.
     * @throws ValidationException
     * @throws ConcurrentModificationException
     *             Thrown in case the passed value object's version does not
     *             match the domain object.
     * @throws OperationNotPermittedException
     *             Thrown in case the passed value object key does not belong to
     *             a domain object.
     */
    private List<PricedParameter> convertAndValidateParameters(long voPriceModelKey,
            List<VOPricedParameter> voPricedParameters, List<Parameter> productParams, PriceModel priceModel,
            boolean priceModelCreatedInTransaction, Organization targetCustomer, Subscription subscription,
            boolean isTemplateExistsForCustomer)
            throws OperationNotPermittedException, ValidationException, ConcurrentModificationException {

        Map<Long, PricedParameter> pricedParameterMap = new HashMap<>();
        Map<Parameter, PricedParameter> paramToPricedParam = new HashMap<>();
        for (PricedParameter pricedParameter : priceModel.getSelectedParameters()) {
            pricedParameterMap.put(Long.valueOf(pricedParameter.getKey()), pricedParameter);
            paramToPricedParam.put(pricedParameter.getParameter(), pricedParameter);
        }
        Map<Long, Parameter> parameters = new LinkedHashMap<>();
        for (Parameter parameter : productParams) {
            parameters.put(Long.valueOf(parameter.getParameterDefinition().getKey()), parameter);
        }
        List<PricedParameter> result = new ArrayList<>();
        for (VOPricedParameter voPricedParameter : voPricedParameters) {
            PricedParameterChecks.validateParamDefSet(voPricedParameter);
            long paramDefKey = voPricedParameter.getVoParameterDef().getKey();
            Parameter parameter = parameters.remove(Long.valueOf(paramDefKey));
            Product product = priceModel.getProduct();
            if (parameter == null) {
                OperationNotPermittedException onp = new OperationNotPermittedException("Specified parameter '"
                        + voPricedParameter.getKey() + "' for parameter definition'" + paramDefKey
                        + "' is not defined for the current product '" + product.getKey() + "'.");
                logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                        LogMessageIdentifier.WARN_PARAMETER_FOR_PRICE_MODEL_INVALID);
                throw onp;
            }
            // only the configured parameter needs to be saved
            if (!parameter.isConfigurable()) {
                OperationNotPermittedException onp = new OperationNotPermittedException(
                        "Priced Parameter lined up to be saved, is not marked as configurable");
                logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                        LogMessageIdentifier.WARN_NOT_CONFIGURABLE_PARAMETER_PASSED_TO_PRICE_MODEL,
                        Long.toString(parameter.getKey()), Long.toString(product.getKey()));
                throw onp;
            }

            // priced parameters can only be based on non-string parameters. So
            // validate the remaining params
            PricedParameterChecks.isValidBaseParam(parameter, voPricedParameter);

            PricedParameter pricedParameter = null;
            if (pricedParameterMap.containsKey(Long.valueOf(voPricedParameter.getKey()))) {
                pricedParameter = pricedParameterMap.remove(Long.valueOf(voPricedParameter.getKey()));
                pricedParameter = handleParameterUpdate(voPricedParameter, pricedParameter,
                        priceModelCreatedInTransaction);
                pricedParameterMap.remove(Long.valueOf(voPricedParameter.getKey()));
            } else {
                PricedParameter existingPricedParam = paramToPricedParam.remove(parameter);
                validateDomainObjectKey(voPricedParameter, existingPricedParam, priceModelCreatedInTransaction);
                pricedParameter = createPricedParameter(voPricedParameter, parameter, priceModel);
            }

            validateAndSetRolePricesForParam(voPriceModelKey, priceModel, voPricedParameter, pricedParameter,
                    priceModelCreatedInTransaction, targetCustomer, subscription, isTemplateExistsForCustomer);
            List<SteppedPrice> steppedPrices = convertAndValidateSteppedPrices(voPriceModelKey,
                    voPricedParameter.getSteppedPrices(), priceModel, null, pricedParameter,
                    priceModelCreatedInTransaction);
            if (!steppedPrices.isEmpty()
                    && BigDecimal.ZERO.compareTo(pricedParameter.getPricePerSubscription()) != 0) {
                ValidationException ve = new ValidationException(
                        ValidationException.ReasonEnum.STEPPED_PARAMETER_PRICING, "pricePerSubscription",
                        new Object[] { pricedParameter.getParameter().getParameterDefinition().getParameterId() });
                logger.logWarn(Log4jLogger.SYSTEM_LOG, ve,
                        LogMessageIdentifier.WARN_STEPPED_PRICING_MIXED_WITH_BASEPRICE,
                        priceModel.getProduct().getProductId(), "priced parameter",
                        String.valueOf(pricedParameter.getKey()));
                throw ve;
            }
            result.add(pricedParameter);
        }
        removePricedParameters(voPriceModelKey, pricedParameterMap.values(), priceModel);

        return result;
    }

    PricedParameter createPricedParameter(VOPricedParameter voPricedParameter, Parameter parameter,
            PriceModel priceModel) throws ValidationException {

        PricedParameter pricedParameter = ParameterAssembler.toPricedParameter(voPricedParameter);
        pricedParameter.setParameter(parameter);
        pricedParameter.setPriceModel(priceModel);

        priceModelAudit.editParameterSubscriptionPrice(dm, pricedParameter, DEFAULT_PRICE_VALUE);
        priceModelAudit.editParameterUserPrice(dm, pricedParameter, DEFAULT_PRICE_VALUE);

        for (PricedOption pricedOption : pricedParameter.getPricedOptionList()) {
            priceModelAudit.editParameterOptionSubscriptionPrice(dm, pricedOption, DEFAULT_PRICE_VALUE);
            priceModelAudit.editParameterOptionUserPrice(dm, pricedOption, DEFAULT_PRICE_VALUE);
        }

        return pricedParameter;
    }

    void removePricedParameters(long voPriceModelKey, Collection<PricedParameter> pricedParameters,
            PriceModel priceModel) {
        for (PricedParameter pricedParameter : pricedParameters) {
            priceModel.getSelectedParameters().remove(pricedParameter);

            priceModelAudit.removeParameterSubscriptionPrice(dm, voPriceModelKey, pricedParameter);

            dm.remove(pricedParameter);
        }
    }

    /**
     * Updates a domain object parameter and all depending priced options
     * according to the passed value object.
     * 
     * @param voPricedParameter
     *            The value object representation of the priced parameter.
     * @param pricedParameter
     *            The priced parameter domain object.
     * @return The updated priced parameter.
     * @throws ValidationException
     * @throws ConcurrentModificationException
     * @throws OperationNotPermittedException
     *             When a key of an priced parameter is set but unknown and the
     *             priceModelCreatedInTransaction flag is not set.
     */
    PricedParameter handleParameterUpdate(VOPricedParameter voPricedParameter, PricedParameter pricedParameter,
            boolean priceModelCreatedInTransaction)
            throws ValidationException, ConcurrentModificationException, OperationNotPermittedException {

        updatePricedParameter(voPricedParameter, pricedParameter);

        // helper map for checking if the priced options belong to the priced
        // parameter
        Map<Long, ParameterOption> keyToParamOption = createParameterOptionMap(pricedParameter);

        List<VOPricedOption> voPricedOptions = voPricedParameter.getPricedOptions();
        List<PricedOption> pricedOptions = pricedParameter.getPricedOptionList();
        List<PricedOption> resultList = new ArrayList<>();
        Map<Long, PricedOption> storedOptionsMap = new HashMap<>();
        for (PricedOption pricedOption : pricedOptions) {
            storedOptionsMap.put(Long.valueOf(pricedOption.getKey()), pricedOption);
        }

        for (VOPricedOption voPricedOption : voPricedOptions) {
            ParameterOption paramOption = keyToParamOption
                    .remove(Long.valueOf(voPricedOption.getParameterOptionKey()));
            if (paramOption == null) {
                OperationNotPermittedException onp = new OperationNotPermittedException(
                        "No ParameterOption found for PricedOption value object.");
                logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                        LogMessageIdentifier.WARN_PRICEDOPTION_NO_OPTION_DEFINED);
                throw onp;
            }
            // new option
            PricedOption pricedOption = null;
            if (!storedOptionsMap.containsKey(Long.valueOf(voPricedOption.getKey()))) {
                if (!priceModelCreatedInTransaction && voPricedOption.getKey() != 0) {
                    OperationNotPermittedException onp = new OperationNotPermittedException(
                            "Priced option value object with invalid key.");
                    logger.logWarn(Log4jLogger.SYSTEM_LOG, onp,
                            LogMessageIdentifier.WARN_PRICED_OPTION_WITH_INVALID_KEY);
                    throw onp;
                }
                pricedOption = createPricedOption(voPricedOption, pricedParameter);
            } else {
                pricedOption = storedOptionsMap.remove(Long.valueOf(voPricedOption.getKey()));
                updatePricedOption(voPricedOption, pricedOption);

            }
            resultList.add(pricedOption);
        }

        // set new list to delete references before entities removed
        pricedParameter.setPricedOptionList(resultList);
        removePricedOptions(storedOptionsMap.values());

        return pricedParameter;
    }

    void updatePricedParameter(VOPricedParameter voPricedParameter, PricedParameter pricedParameter)
            throws ValidationException, ConcurrentModificationException {

        BigDecimal oldSubPrice = pricedParameter.getPricePerSubscription();
        BigDecimal oldUserPrice = pricedParameter.getPricePerUser();
        pricedParameter = ParameterAssembler.updatePricedParameter(voPricedParameter, pricedParameter);

        priceModelAudit.editParameterSubscriptionPrice(dm, pricedParameter, oldSubPrice);
        priceModelAudit.editParameterUserPrice(dm, pricedParameter, oldUserPrice);
    }

    PricedOption createPricedOption(VOPricedOption voPricedOption, PricedParameter pricedParameter) {
        PricedOption pricedOption = PricedOptionAssembler.toPricedOption(voPricedOption, pricedParameter);

        priceModelAudit.editParameterOptionSubscriptionPrice(dm, pricedOption, DEFAULT_PRICE_VALUE);
        priceModelAudit.editParameterOptionUserPrice(dm, pricedOption, DEFAULT_PRICE_VALUE);
        return pricedOption;
    }

    void updatePricedOption(VOPricedOption voPricedOption, PricedOption pricedOption)
            throws ConcurrentModificationException, ValidationException {

        BigDecimal oldPOSubPrice = pricedOption.getPricePerSubscription();
        BigDecimal oldPOUserPrice = pricedOption.getPricePerUser();
        PricedOptionAssembler.updatePricedOption(pricedOption, voPricedOption);

        priceModelAudit.editParameterOptionSubscriptionPrice(dm, pricedOption, oldPOSubPrice);
        priceModelAudit.editParameterOptionUserPrice(dm, pricedOption, oldPOUserPrice);
    }

    void removePricedOptions(Collection<PricedOption> pricedOptions) {
        for (PricedOption pricedOption : pricedOptions) {
            dm.remove(pricedOption);
        }
    }

    Map<Long, ParameterOption> createParameterOptionMap(PricedParameter pricedParameter) {
        Map<Long, ParameterOption> keyToParamOption = new HashMap<>();
        for (ParameterOption parameterOption : pricedParameter.getParameter().getParameterDefinition()
                .getOptionList()) {
            keyToParamOption.put(Long.valueOf(parameterOption.getKey()), parameterOption);
        }
        return keyToParamOption;
    }

    /**
     * Validates and sets the priced product role settings for the parameter and
     * parameter options.
     * 
     * @param priceModel
     *            The price model that should be modified.
     * @param voPP
     *            The value object representation of the priced parameter.
     * @param pp
     *            The domain object representation of the priced parameter.
     * @param targetCustomer
     *            The targetCustomer of the price model.
     * @param subscription
     *            The subscription of the price model.
     * @param priceModelCreatedInTransaction
     *            Indicates whether the price model was created in this
     *            transaction or not.
     * @param isTemplateExistsForCustomer
     *            Indicates whether price model for service exists as a template
     *            for price model for customer
     * @throws ValidationException
     * @throws OperationNotPermittedException
     * @throws ConcurrentModificationException
     *             Thrown in case the priced role value object version does not
     *             match the domain object's one.
     */
    void validateAndSetRolePricesForParam(long voPriceModelKey, PriceModel priceModel, VOPricedParameter voPP,
            PricedParameter pp, boolean priceModelCreatedInTransaction, Organization targetCustomer,
            Subscription subscription, boolean isTemplateExistsForCustomer)
            throws ValidationException, OperationNotPermittedException, ConcurrentModificationException {

        Map<Long, List<PricedProductRole>> oldPricedOptionMap = new HashMap<>();
        if (isTemplateExistsForCustomer) {
            oldPricedOptionMap = prepareOldPricedOptionMap(priceModel);
        }
        List<VOPricedOption> pricedOptions = voPP.getPricedOptions();
        for (VOPricedOption pricedOption : pricedOptions) {
            validatePricedProductRoles(pricedOption.getRoleSpecificUserPrices(), priceModel.getProduct());

            // find corresponding domain object representation for the
            // priced option and save the priced product role information to
            // it
            for (PricedOption po : pp.getPricedOptionList()) {
                if (po.getParameterOptionKey() == pricedOption.getParameterOptionKey()) {
                    if (isTemplateExistsForCustomer) {
                        setRoleSpecificPrices(voPriceModelKey, null, null, po,
                                pricedOption.getRoleSpecificUserPrices(), priceModelCreatedInTransaction,
                                targetCustomer, subscription,
                                oldPricedOptionMap.get(Long.valueOf(po.getParameterOptionKey())));

                    } else {
                        setRoleSpecificPrices(voPriceModelKey, null, null, po,
                                pricedOption.getRoleSpecificUserPrices(), priceModelCreatedInTransaction,
                                targetCustomer, subscription, null);
                    }

                }
            }
        }
        validatePricedProductRoles(voPP.getRoleSpecificUserPrices(), priceModel.getProduct());
        setRoleSpecificPrices(voPriceModelKey, null, pp, null, voPP.getRoleSpecificUserPrices(),
                priceModelCreatedInTransaction, targetCustomer, subscription, null);
    }

    private Map<Long, List<PricedProductRole>> prepareOldPricedOptionMap(PriceModel priceModel) {
        Map<Long, List<PricedProductRole>> oldPricedOptionMap = new HashMap<>();
        List<PricedParameter> pricedParameters = priceModel.getSelectedParameters();
        if (pricedParameters != null) {
            for (PricedParameter pricedParameter : pricedParameters) {
                List<PricedOption> pricedOptions = pricedParameter.getPricedOptionList();
                if (pricedOptions != null) {
                    for (PricedOption pricedOption : pricedOptions) {
                        oldPricedOptionMap.put(Long.valueOf(pricedOption.getParameterOptionKey()),
                                pricedOption.getRoleSpecificUserPrices());
                    }
                }
            }
        }
        return oldPricedOptionMap;
    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public VOTechnicalService createTechnicalService(VOTechnicalService technicalService)
            throws OrganizationAuthoritiesException, ValidationException, NonUniqueBusinessKeyException {

        ArgumentValidator.notNull("technicalService", technicalService);

        Organization org = dm.getCurrentUser().getOrganization();

        String billingId = technicalService.getBillingIdentifier();

        if (billingId == null || billingId.trim().length() == 0) {
            billingId = billingAdapterLocalBean.getDefaultBillingIdentifier();
            technicalService.setBillingIdentifier(billingId);
        }

        TechnicalProduct domObj = TechnicalProductAssembler.toTechnicalProduct(technicalService);
        domObj.setOrganization(org);
        dm.persist(domObj);
        dm.flush();

        String locale = dm.getCurrentUser().getLocale();

        String license = technicalService.getLicense();
        if (license == null) {
            license = "";
        }
        localizer.storeLocalizedResource(locale, domObj.getKey(), LocalizedObjectTypes.PRODUCT_LICENSE_DESC,
                license);
        String description = technicalService.getTechnicalServiceDescription();
        if (description == null) {
            description = "";
        }
        localizer.storeLocalizedResource(locale, domObj.getKey(), LocalizedObjectTypes.TEC_PRODUCT_TECHNICAL_DESC,
                description);
        String accessInfo = technicalService.getAccessInfo();
        if (accessInfo == null) {
            accessInfo = "";
        }
        ServiceAccessType accessType = technicalService.getAccessType();
        if (accessType == ServiceAccessType.DIRECT || accessType == ServiceAccessType.USER) {
            localizer.storeLocalizedResource("en", domObj.getKey(),
                    LocalizedObjectTypes.TEC_PRODUCT_LOGIN_ACCESS_DESC, accessInfo);
        } else {
            localizer.storeLocalizedResource(locale, domObj.getKey(),
                    LocalizedObjectTypes.TEC_PRODUCT_LOGIN_ACCESS_DESC, accessInfo);
        }

        // Store defined localized tags of this service
        tagService.updateTags(domObj, locale, TagAssembler.toTags(technicalService.getTags(), locale));

        // if the organization is supplier and technology provider, it must be
        // able to use its own products, so register it as supplier for itself.
        if (org.hasRole(OrganizationRoleType.SUPPLIER)) {
            try {
                ms.addMarketingPermission(org, domObj.getKey(), Collections.singletonList(org.getOrganizationId()));
            } catch (ObjectNotFoundException e) {
                // should not happen here
                logger.logWarn(Log4jLogger.SYSTEM_LOG, LogMessageIdentifier.WARN_MARKETING_PERMISSION_NOT_ADDED,
                        String.valueOf(domObj.getKey()), org.getOrganizationId());
            } catch (AddMarketingPermissionException e) {
                // should not happen here
                logger.logWarn(Log4jLogger.SYSTEM_LOG, LogMessageIdentifier.WARN_MARKETING_PERMISSION_NOT_ADDED,
                        String.valueOf(domObj.getKey()), org.getOrganizationId());
            }
        }

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        List<ParameterDefinition> paramDefs = getPlatformParameterDefinitions(domObj);
        List<Event> platformEvents = getPlatformEvents(domObj);
        VOTechnicalService result = TechnicalProductAssembler.toVOTechnicalProduct(domObj, paramDefs,
                platformEvents, facade, false);

        return result;
    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public void saveTechnicalServiceLocalization(VOTechnicalService technicalService)
            throws ObjectNotFoundException, OperationNotPermittedException, UpdateConstraintException,
            ValidationException {

        ArgumentValidator.notNull("technicalService", technicalService);

        Organization provider = dm.getCurrentUser().getOrganization();
        TechnicalProduct techProd = findTechnicalProductAndCheckOwner(provider, technicalService);
        String locale = dm.getCurrentUser().getLocale();
        checkLicenseConstrainsAndStore(techProd, locale, technicalService.getLicense());
        String description = technicalService.getTechnicalServiceDescription();
        if (description != null) {
            localizer.storeLocalizedResource(locale, technicalService.getKey(),
                    LocalizedObjectTypes.TEC_PRODUCT_TECHNICAL_DESC, description);
        }
        String accessInfo = technicalService.getAccessInfo();
        if (accessInfo != null) {
            createAccessInfoForDefaultLocale(locale, "en", technicalService, accessInfo);
            localizer.storeLocalizedResource(locale, technicalService.getKey(),
                    LocalizedObjectTypes.TEC_PRODUCT_LOGIN_ACCESS_DESC, accessInfo);
        }
        List<VORoleDefinition> roles = technicalService.getRoleDefinitions();
        for (VORoleDefinition role : roles) {
            RoleDefinition roleDef = new RoleDefinition();
            roleDef.setRoleId(role.getRoleId());
            roleDef.setTechnicalProduct(techProd);
            RoleDefinition r = (RoleDefinition) dm.getReferenceByBusinessKey(roleDef);
            String name = role.getName();
            if (name != null) {
                BLValidator.isName("role definition name", name, false);
                localizer.storeLocalizedResource(locale, r.getKey(), LocalizedObjectTypes.ROLE_DEF_NAME, name);
            }
            String desc = role.getDescription();
            if (desc != null) {
                localizer.storeLocalizedResource(locale, r.getKey(), LocalizedObjectTypes.ROLE_DEF_DESC, desc);
            }
        }
        List<VOTechnicalServiceOperation> ops = technicalService.getTechnicalServiceOperations();
        for (VOTechnicalServiceOperation op : ops) {
            TechnicalProductOperation operation = new TechnicalProductOperation();
            operation.setOperationId(op.getOperationId());
            operation.setTechnicalProduct(techProd);
            TechnicalProductOperation tpo = (TechnicalProductOperation) dm.getReferenceByBusinessKey(operation);
            String name = op.getOperationName();
            if (name != null) {
                BLValidator.isName("technical service operation name", name, false);
                localizer.storeLocalizedResource(locale, tpo.getKey(),
                        LocalizedObjectTypes.TECHNICAL_PRODUCT_OPERATION_NAME, name);
            }
            String desc = op.getOperationDescription();
            if (desc != null) {
                localizer.storeLocalizedResource(locale, tpo.getKey(),
                        LocalizedObjectTypes.TECHNICAL_PRODUCT_OPERATION_DESCRIPTION, desc);
            }
            List<VOServiceOperationParameter> params = op.getOperationParameters();
            for (VOServiceOperationParameter p : params) {
                String parameterName = p.getParameterName();
                if (parameterName != null) {
                    OperationParameter operationParameter = new OperationParameter();
                    operationParameter.setId(p.getParameterId());
                    operationParameter.setTechnicalProductOperation(tpo);
                    OperationParameter param = (OperationParameter) dm
                            .getReferenceByBusinessKey(operationParameter);
                    BLValidator.isName("technical service operation parameter name", parameterName, false);
                    localizer.storeLocalizedResource(locale, param.getKey(),
                            LocalizedObjectTypes.TECHNICAL_PRODUCT_OPERATION_PARAMETER_NAME, parameterName);
                }
            }
        }
        List<VOEventDefinition> events = technicalService.getEventDefinitions();
        for (VOEventDefinition event : events) {
            String eventDescription = event.getEventDescription();
            if (eventDescription != null) {
                if (event.getEventType() != EventType.PLATFORM_EVENT) {
                    Event eventObj = new Event();
                    eventObj.setEventIdentifier(event.getEventId());
                    eventObj.setEventType(event.getEventType());
                    eventObj.setTechnicalProduct(techProd);
                    Event e = (Event) dm.getReferenceByBusinessKey(eventObj);
                    // only service events can be modified
                    localizer.storeLocalizedResource(locale, e.getKey(), LocalizedObjectTypes.EVENT_DESC,
                            eventDescription);
                }
            }
        }
        List<VOParameterDefinition> parameters = technicalService.getParameterDefinitions();
        for (VOParameterDefinition parameter : parameters) {
            String parameterDescription = parameter.getDescription();
            if (parameter.getParameterType() != ParameterType.PLATFORM_PARAMETER) {
                ParameterDefinition parameterDef = new ParameterDefinition();
                parameterDef.setParameterId(parameter.getParameterId());
                parameterDef.setParameterType(parameter.getParameterType());
                parameterDef.setTechnicalProduct(techProd);
                ParameterDefinition p = (ParameterDefinition) dm.getReferenceByBusinessKey(parameterDef);

                // only service parameters can be modified
                if (parameterDescription != null) {
                    localizer.storeLocalizedResource(locale, p.getKey(), LocalizedObjectTypes.PARAMETER_DEF_DESC,
                            parameterDescription);

                }
                List<VOParameterOption> options = parameter.getParameterOptions();
                for (VOParameterOption option : options) {
                    String optionDescription = option.getOptionDescription();
                    if (optionDescription != null) {
                        ParameterOption parameterOption = new ParameterOption();
                        parameterOption.setOptionId(option.getOptionId());
                        parameterOption.setParameterDefinition(p);
                        ParameterOption o = (ParameterOption) dm.getReferenceByBusinessKey(parameterOption);
                        localizer.storeLocalizedResource(locale, o.getKey(),
                                LocalizedObjectTypes.OPTION_PARAMETER_DEF_DESC, optionDescription);
                    }
                }
            }
        }
        List<Tag> tags = TagAssembler.toTags(technicalService.getTags(), locale);
        tagService.updateTags(techProd, locale, tags);

    }

    private void createAccessInfoForDefaultLocale(String userLocale, String defaultLocale,
            VOTechnicalService technicalService, String accessInfo) {
        ServiceAccessType accessType = technicalService.getAccessType();
        if (!userLocale.equals(defaultLocale)
                && (accessType == ServiceAccessType.DIRECT || accessType == ServiceAccessType.USER)) {
            String accessInfoDefaultLocale = localizer.getLocalizedTextFromDatabase(defaultLocale,
                    technicalService.getKey(), LocalizedObjectTypes.TEC_PRODUCT_LOGIN_ACCESS_DESC);
            if (accessInfoDefaultLocale == null || accessInfoDefaultLocale.trim().length() <= 0) {
                localizer.storeLocalizedResource(defaultLocale, technicalService.getKey(),
                        LocalizedObjectTypes.TEC_PRODUCT_LOGIN_ACCESS_DESC, accessInfo);
            }
        }
    }

    /**
     * Checks if the new license of the technical product can be changed. Saving
     * is done when the technical product doesn't have a license in the provided
     * locale or if no marketing products exist based on the technical product.
     * In case the new license isn't set or equals the existing one, it isn't
     * saved.
     * 
     * @param tp
     *            the technical product value object to get the new license from
     * @param locale
     *            the locale that is used for saving the license
     * @param license
     *            The license for the technical product.
     */
    private void checkLicenseConstrainsAndStore(TechnicalProduct tp, String locale, String license) {
        if (license == null) {
            return;
        }
        List<VOLocalizedText> localizedValues = localizer.getLocalizedValues(tp.getKey(),
                LocalizedObjectTypes.PRODUCT_LICENSE_DESC);

        String storedLicense = null;
        for (VOLocalizedText text : localizedValues) {
            if (text.getLocale().equals(locale)) {
                storedLicense = text.getText();
            }
        }

        ProductLicenseValidator.validate(tp, storedLicense, license);
        // save in DB only if they are different
        if (!Strings.areStringsEqual(storedLicense, license)) {
            localizer.storeLocalizedResource(locale, tp.getKey(), LocalizedObjectTypes.PRODUCT_LICENSE_DESC,
                    license);
        }
    }

    /**
     * Tries to find the technical product represented by the given value object
     * and check if the provided organization is the owner of it.
     * 
     * @param provider
     *            the technology provider
     * @param voTechnicalProduct
     *            the value object to find the domain object for
     * @return the {@link TechnicalProduct}
     * @throws ObjectNotFoundException
     *             in case the technical product wasn't found
     * @throws OperationNotPermittedException
     *             in case the provided organization is not owner of the
     *             technical product
     */
    private TechnicalProduct findTechnicalProductAndCheckOwner(Organization provider,
            VOTechnicalService voTechnicalProduct) throws ObjectNotFoundException, OperationNotPermittedException {
        TechnicalProduct techProd = dm.getReference(TechnicalProduct.class, voTechnicalProduct.getKey());
        PermissionCheck.owns(techProd, provider, logger, sessionCtx);
        return techProd;
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public List<VOCustomerService> getAllCustomerSpecificServices() throws OrganizationAuthoritiesException {
        Organization organization = dm.getCurrentUser().getOrganization();
        Query query = dm.createNamedQuery("Product.getCustomerProductsForVendor");
        query.setParameter("vendorKey", Long.valueOf(organization.getKey()));
        List<Product> products = ParameterizedTypes.list(query.getResultList(), Product.class);
        List<VOCustomerService> result = new ArrayList<>();
        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        Set<ServiceStatus> states = EnumSet.of(ServiceStatus.ACTIVE, ServiceStatus.INACTIVE,
                ServiceStatus.SUSPENDED);
        for (Product product : products) {
            if (states.contains(product.getStatus())) {
                result.add(ProductAssembler.toVOCustomerProduct(product, facade));
            }
        }
        return result;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public VOServiceDetails copyService(VOService service, String serviceId) throws ObjectNotFoundException,
            OrganizationAuthoritiesException, OperationNotPermittedException, ServiceStateException,
            ConcurrentModificationException, NonUniqueBusinessKeyException, ValidationException {

        ArgumentValidator.notNull("service", service);
        Organization org = dm.getCurrentUser().getOrganization();
        BLValidator.isId("serviceId", serviceId, true);
        Product product = dm.getReference(Product.class, service.getKey());
        PermissionCheck.owns(product, org, logger, sessionCtx);
        if (product.getOwningSubscription() != null || product.getTargetCustomer() != null) {
            OperationNotPermittedException onp = new OperationNotPermittedException("Copying the service failed");
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, onp,
                    LogMessageIdentifier.WARN_COPY_SERVICE_FAILED_NOT_GLOBAL_TEMPLATE, Long.toString(org.getKey()),
                    Long.toString(product.getKey()));
            throw onp;
        }
        ServiceStatus status = product.getStatus();
        if (status != ServiceStatus.ACTIVE && status != ServiceStatus.INACTIVE) {
            ServiceStateException sse = new ServiceStateException(status,
                    ServiceStatus.ACTIVE.name() + ", " + ServiceStatus.INACTIVE.name(),
                    ProductAssembler.getProductId(product));
            logger.logWarn(Log4jLogger.SYSTEM_LOG, sse, LogMessageIdentifier.WARN_COPY_SERVICE_FAILED_INVALID_STATE,
                    Long.toString(org.getKey()), Long.toString(product.getKey()));
            throw sse;
        }
        BaseAssembler.verifyVersionAndKey(product, service);
        // check uniqueness of new service id
        try {
            validateChangedId(serviceId, org);
        } catch (NonUniqueBusinessKeyException e) {
            sessionCtx.setRollbackOnly();
            throw e;
        }

        Product copy = product.copyTemplate(serviceId);
        dm.persist(copy);
        // get the marketplace and the categories for the product to copy
        Marketplace mp = null;
        List<Category> cats = new ArrayList<>();
        if (!product.getCatalogEntries().isEmpty()) {
            CatalogEntry ce = product.getCatalogEntries().get(0);
            mp = ce.getMarketplace();
            for (CategoryToCatalogEntry c : ce.getCategoryToCatalogEntry()) {
                cats.add(c.getCategory());
            }
        }

        // create the catalog entry for the copy with the same marketplace and
        // categories
        CatalogEntry catalogEntry = QueryBasedObjectFactory.createCatalogEntry(copy, mp);
        for (Category cat : cats) {
            catalogEntry.addCategory(cat);
        }
        if (product.getCatalogEntries().size() > 0) {
            catalogEntry.setVisibleInCatalog(product.getCatalogEntries().get(0).isVisibleInCatalog());
            catalogEntry.setAnonymousVisible(product.getCatalogEntries().get(0).isAnonymousVisible());
        }

        if (!product.getCatalogEntries().isEmpty()) {
            copyOperatorPriceModel(catalogEntry, product.getCatalogEntries().get(0).getOperatorPriceModel());
        }

        dm.persist(catalogEntry);
        dm.flush();

        // copy the enabled default payment types of the supplier
        copyDefaultPaymentEnablement(copy, org);

        // copy all the localized stuff
        List<VOLocalizedText> locDescs = localizer.getLocalizedValues(product.getKey(),
                LocalizedObjectTypes.PRODUCT_MARKETING_DESC);
        localizer.setLocalizedValues(copy.getKey(), LocalizedObjectTypes.PRODUCT_MARKETING_DESC, locDescs);
        List<VOLocalizedText> locShortDescs = localizer.getLocalizedValues(product.getKey(),
                LocalizedObjectTypes.PRODUCT_SHORT_DESCRIPTION);
        localizer.setLocalizedValues(copy.getKey(), LocalizedObjectTypes.PRODUCT_SHORT_DESCRIPTION, locShortDescs);
        List<VOLocalizedText> locNames = localizer.getLocalizedValues(product.getKey(),
                LocalizedObjectTypes.PRODUCT_MARKETING_NAME);
        localizer.setLocalizedValues(copy.getKey(), LocalizedObjectTypes.PRODUCT_MARKETING_NAME, locNames);
        List<VOLocalizedText> locCustomTabNames = localizer.getLocalizedValues(product.getKey(),
                LocalizedObjectTypes.PRODUCT_CUSTOM_TAB_NAME);
        localizer.setLocalizedValues(copy.getKey(), LocalizedObjectTypes.PRODUCT_CUSTOM_TAB_NAME,
                locCustomTabNames);

        PriceModel pm = product.getPriceModel();
        if (pm != null) {
            PriceModel pmCopy = copy.getPriceModel();
            List<VOLocalizedText> locPmDescs = localizer.getLocalizedValues(pm.getKey(),
                    LocalizedObjectTypes.PRICEMODEL_DESCRIPTION);
            localizer.setLocalizedValues(pmCopy.getKey(), LocalizedObjectTypes.PRICEMODEL_DESCRIPTION, locPmDescs);
            // copy licenses
            copyLicenseInformation(pm, pmCopy);
        }

        // copy the image if existing
        ImageResource imageResource = irm.read(product.getKey(), ImageType.SERVICE_IMAGE);
        if (imageResource != null) {
            irm.save(imageResource.copy(copy.getKey()));
        }

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        VOServiceDetails createdProduct = getServiceDetails(copy, facade);

        serviceAudit.copyService(dm, copy, product.getProductId(), service.getNameToDisplay());
        return createdProduct;
    }

    /**
     * Copy license information from price model to its copy.
     * 
     * @param source
     *            Original price model.
     * @param destination
     *            Copy price model.
     */
    private void copyLicenseInformation(PriceModel source, PriceModel destination) {
        // get local descriptions for technical service and
        // save for marketable service for all languages
        List<VOLocalizedText> licenseDescriptions = localizer.getLocalizedValues(source.getKey(),
                LocalizedObjectTypes.PRICEMODEL_LICENSE);

        if (licenseDescriptions != null) {
            for (VOLocalizedText localizedText : licenseDescriptions) {
                String license = localizedText.getText();
                String locale = localizedText.getLocale();
                localizer.storeLocalizedResource(locale, destination.getKey(),
                        LocalizedObjectTypes.PRICEMODEL_LICENSE, license);
            }
        }
    }

    @Override
    @RolesAllowed({ "SERVICE_MANAGER", "RESELLER_MANAGER", "BROKER_MANAGER" })
    public List<VOService> setActivationStates(List<VOServiceActivation> activations)
            throws ObjectNotFoundException, ServiceStateException, OrganizationAuthoritiesException,
            OperationNotPermittedException, ServiceOperationException, TechnicalServiceNotAliveException,
            ServiceNotPublishedException, OperationPendingException, ConcurrentModificationException {

        ArgumentValidator.notNull("activations", activations);
        List<VOService> resultList = new ArrayList<>();

        ServiceVisibilityCheck visChecker = new ServiceVisibilityCheck(dm);
        for (VOServiceActivation voActivation : activations) {
            VOService result = setActivationState(voActivation, visChecker);
            if (result != null) {
                resultList.add(result);
            }
            dm.flush();
        }

        try {
            visChecker.validate();
        } catch (ServiceOperationException e) {
            sessionCtx.setRollbackOnly();
            throw e;
        }

        return resultList;
    }

    private VOService setActivationState(VOServiceActivation serviceActivation, ServiceVisibilityCheck visChecker)
            throws ObjectNotFoundException, ServiceStateException, OrganizationAuthoritiesException,
            OperationNotPermittedException, ServiceOperationException, TechnicalServiceNotAliveException,
            ServiceNotPublishedException, OperationPendingException, ConcurrentModificationException {
        VOService service = serviceActivation.getService();
        boolean active = serviceActivation.isActive();
        List<VOCatalogEntry> catalogEntries = serviceActivation.getCatalogEntries();
        return setActivationState(service, active, catalogEntries, visChecker);
    }

    /**
     * Activates or deactivates the given service and updates the visibility
     * flag for all given catalog entries. If a trigger function is defined, the
     * request will be queued.
     */
    private VOService setActivationState(VOService service, boolean activate, List<VOCatalogEntry> entries,
            ServiceVisibilityCheck visChecker)
            throws ServiceStateException, ObjectNotFoundException, OrganizationAuthoritiesException,
            OperationNotPermittedException, ServiceOperationException, TechnicalServiceNotAliveException,
            ServiceNotPublishedException, OperationPendingException, ConcurrentModificationException {

        ArgumentValidator.notNull("service", service);
        PlatformUser currentUser = dm.getCurrentUser();
        Product prod = dm.getReference(Product.class, service.getKey());
        if (activate) {
            validateForProductStatusChange(prod, service, ServiceStatus.ACTIVE, ServiceStatus.INACTIVE,
                    currentUser);
            validateForProductActivation(service);
        } else {
            validateForProductStatusChange(prod, service, ServiceStatus.INACTIVE, ServiceStatus.ACTIVE,
                    currentUser);
        }

        TriggerProcessValidator triggerProcessValidator = new TriggerProcessValidator(dm);
        // If there is an existing pending operation throw an exception.
        if (triggerProcessValidator.isActivateOrDeactivateServicePending(service)) {
            OperationPendingException ope = new OperationPendingException(String.format(
                    "Operation cannot be performed. There is already another pending request to activate or deactivate the service with ID '%s'",
                    String.valueOf(service.getServiceId())),
                    (activate) ? OperationPendingException.ReasonEnum.ACTIVATE_SERVICE
                            : OperationPendingException.ReasonEnum.DEACTIVATE_SERVICE,
                    new Object[] { String.valueOf(service.getServiceId()) });

            logger.logWarn(Log4jLogger.SYSTEM_LOG, ope,
                    (activate) ? LogMessageIdentifier.WARN_ACTIVATE_SERVICE_FAILED_DUE_TO_TRIGGER_CONFLICT
                            : LogMessageIdentifier.WARN_DEACTIVATE_SERVICE_FAILED_DUE_TO_TRIGGER_CONFLICT,
                    String.valueOf(service.getKey()));

            throw ope;
        }

        TriggerMessage message = new TriggerMessage(
                activate ? TriggerType.ACTIVATE_SERVICE : TriggerType.DEACTIVATE_SERVICE);
        List<TriggerProcessMessageData> list = triggerQS.sendSuspendingMessages(Collections.singletonList(message));
        TriggerProcess tProc = list.get(0).getTrigger();
        tProc.addTriggerProcessParameter(TriggerProcessParameterName.OBJECT_ID, service.getServiceId());
        tProc.addTriggerProcessParameter(TriggerProcessParameterName.PRODUCT, service);
        if (entries != null) {
            tProc.addTriggerProcessParameter(TriggerProcessParameterName.CATALOG_ENTRIES, entries);
        }
        tProc.setUser(currentUser);

        VOService voProduct = null;
        TriggerDefinition triggerDefinition = tProc.getTriggerDefinition();
        if (triggerDefinition == null) {
            // if processing is not suspended, call finishing method
            try {
                if (activate) {
                    activateServiceInt(tProc);
                } else {
                    deactivateServiceInt(tProc);
                }

                dm.flush();
                if (entries != null && !entries.isEmpty()) {
                    VOCatalogEntry catalogEntry = entries.get(0);
                    if (catalogEntry.getMarketplace() != null) {
                        serviceAudit.activeOrDeactiveService(dm, prod,
                                catalogEntry.getMarketplace().getMarketplaceId(),
                                catalogEntry.getMarketplace().getName(), activate,
                                catalogEntry.isVisibleInCatalog());
                    }
                }

                voProduct = ProductAssembler.toVOProduct(prod,
                        new LocalizerFacade(localizer, currentUser.getLocale()));
            } catch (TechnicalServiceNotAliveException | ServiceStateException | ObjectNotFoundException
                    | OrganizationAuthoritiesException | ServiceOperationException
                    | ConcurrentModificationException e) {
                sessionCtx.setRollbackOnly();
                throw e;
            }
        } else if (triggerDefinition.isSuspendProcess()) {
            // Register this REQUEST for final visibility constraint validation
            if (entries == null) {
                List<CatalogEntry> entry = dm.getReference(Product.class, service.getKey()).getCatalogEntries();
                if (!entry.isEmpty()) {
                    final CatalogEntry ce = entry.get(0);
                    entries = new ArrayList<>();
                    VOCatalogEntry vo = new VOCatalogEntry();
                    vo.setService(service);
                    vo.setVisibleInCatalog(ce.isVisibleInCatalog());
                    vo.setMarketplace(MarketplaceAssembler.toVOMarketplace(ce.getMarketplace(),
                            new LocalizerFacade(localizer, dm.getCurrentUser().getLocale())));
                    entries.add(vo);
                }
            }

            // If the operation defined by the trigger definition is suspended,
            // then set the trigger process identifiers.
            tProc.setTriggerProcessIdentifiers(
                    TriggerProcessIdentifiers.createDeactivateService(dm, triggerDefinition.getType(), service));
            dm.merge(tProc);
        }

        // Register this REQUEST for final visibility constraint validation
        visChecker.add(prod, entries, activate);

        return voProduct;
    }

    /**
     * Update visibility of catalog entries for given service.
     * 
     * @throws ConcurrentModificationException
     *             when the passed entry has a different marketplace set as the
     *             persisted one
     */
    private void updateCatalogEntryVisibility(Product product, List<VOCatalogEntry> entries)
            throws ConcurrentModificationException {

        HashMap<String, CatalogEntry> entryMap;

        // Get all existing catalog entries for this service
        Query query = dm.createNamedQuery("CatalogEntry.findByService");
        query.setParameter("service", product);
        List<CatalogEntry> tempList = ParameterizedTypes.list(query.getResultList(), CatalogEntry.class);
        entryMap = new HashMap<>();
        for (CatalogEntry entry : tempList) {
            if (entry.getMarketplace() != null) {
                entryMap.put(entry.getMarketplace().getMarketplaceId(), entry);
            } else {
                entryMap.put(null, entry);
            }
        }

        // Now process all given entries
        for (VOCatalogEntry voEntry : entries) {
            final VOMarketplace mp = voEntry.getMarketplace();

            if (mp != null) {
                CatalogEntry domEntry = entryMap.get(mp.getMarketplaceId());
                if (domEntry == null) {
                    // No catalog entry for the specified marketplace found
                    ConcurrentModificationException cme = new ConcurrentModificationException(voEntry);
                    logger.logError(Log4jLogger.SYSTEM_LOG, cme,
                            LogMessageIdentifier.WARN_MARKETPLACE_MISMATCH_ON_SETTING_VISIBILITY,
                            String.valueOf(product.getKey()));
                    throw cme;
                }

                // Update visibility as specified
                domEntry.setVisibleInCatalog(voEntry.isVisibleInCatalog());
            }
        }

    }

    @Override
    public VOOrganization getServiceSeller(long serviceKey, String locale) throws ObjectNotFoundException {
        ArgumentValidator.notNull("locale", locale);

        Product product = dm.getReference(Product.class, serviceKey);
        if (product.getType() == ServiceType.PARTNER_TEMPLATE) {
            product = product.getTemplate();
        }
        return OrganizationAssembler.toVOOrganization(product.getVendor(), false,
                new LocalizerFacade(localizer, locale));
    }

    @Override
    public VOOrganization getServiceSellerFallback(long serviceKey, String locale) throws ObjectNotFoundException {
        VOOrganization org = getServiceSeller(serviceKey, locale);
        if ((org.getDescription() == null || org.getDescription().isEmpty()) && !locale.equals("en")) {
            Product product = dm.getReference(Product.class, serviceKey);
            LocalizerFacade localizerEn = new LocalizerFacade(localizer, "en");
            String description = localizerEn.getText(product.getVendor().getKey(),
                    LocalizedObjectTypes.ORGANIZATION_DESCRIPTION);
            org.setDescription(description);
        }
        return org;
    }

    public VOOrganization getPartnerForService(long serviceKey, String locale) throws ObjectNotFoundException {
        ArgumentValidator.notNull("locale", locale);

        Product product = dm.getReference(Product.class, serviceKey);
        return OrganizationAssembler.toVOOrganization(product.getVendor(), false,
                new LocalizerFacade(localizer, locale));
    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public List<String> getInstanceIdsForSellers(List<String> organizationIds) {

        ArgumentValidator.notNull("organizationIds", organizationIds);

        List<String> result = new ArrayList<>();
        Organization providerOrg = dm.getCurrentUser().getOrganization();

        if (organizationIds.size() > 0) {
            Query query = dm.createNamedQuery("Subscription.instanceIdsForSuppliers");
            query.setParameter("providerKey", Long.valueOf(providerOrg.getKey()));
            query.setParameter("supplierIds", organizationIds);
            query.setParameter("status", EnumSet.of(SubscriptionStatus.ACTIVE, SubscriptionStatus.SUSPENDED));
            List<String> instanceIds = ParameterizedTypes.list(query.getResultList(), String.class);
            if (instanceIds != null) {
                result.addAll(instanceIds);
            }
        }

        return result;
    }

    @Override
    @RolesAllowed("MARKETPLACE_OWNER")
    public VOService suspendService(VOService service, String reason)
            throws ObjectNotFoundException, OperationNotPermittedException, ServiceStateException {

        ArgumentValidator.notNull("service", service);
        ArgumentValidator.notEmptyString("reason", reason);
        PlatformUser currentUser = dm.getCurrentUser();
        Product prod = dm.getReference(Product.class, service.getKey());
        Marketplace mp = validatePermissionForSuspendAndResume(currentUser, prod);

        // If the product is a PARTNER_TEMPLATE then get itself
        Product tempOrSelf;
        if (prod.getType() == ServiceType.PARTNER_TEMPLATE) {
            tempOrSelf = prod;
        }
        // Otherwise get the template or self
        else {
            tempOrSelf = prod.getTemplateOrSelf();
        }

        if (tempOrSelf.getStatus() != ServiceStatus.ACTIVE) {
            ServiceStateException e = new ServiceStateException(ServiceStatus.ACTIVE, tempOrSelf.getStatus());
            logger.logWarn(Log4jLogger.SYSTEM_LOG, e, LogMessageIdentifier.WARN_SUSPEND_SERVICE_INVALID_STATE,
                    tempOrSelf.getStatus().name());
            throw e;
        }

        tempOrSelf.setStatus(ServiceStatus.SUSPENDED);
        List<Product> list = getCustomerSpecificProductCopies(tempOrSelf);
        for (Product product : list) {
            if (product.getStatus() == ServiceStatus.ACTIVE) {
                product.setStatus(ServiceStatus.SUSPENDED);
            }
        }
        List<PlatformUser> users = tempOrSelf.getVendor().getPlatformUsers();
        for (PlatformUser user : users) {
            if (user.isOrganizationAdmin() || user.hasRole(UserRoleType.SERVICE_MANAGER)) {
                try {
                    commService.sendMail(user, EmailType.SERVICE_SUSPENDED,
                            new Object[] { tempOrSelf.getProductId(), reason, currentUser.getEmail() }, mp);
                } catch (MailOperationException e) {
                    logger.logError(Log4jLogger.SYSTEM_LOG, e,
                            LogMessageIdentifier.ERROR_SEND_SERVICE_SUSPENDED_MAIL_FAILED);
                }
            }
        }
        dm.flush();
        VOService voProduct = ProductAssembler.toVOProduct(prod,
                new LocalizerFacade(localizer, dm.getCurrentUser().getLocale()));

        return voProduct;
    }

    @Override
    @RolesAllowed("MARKETPLACE_OWNER")
    public VOService resumeService(VOService service)
            throws ObjectNotFoundException, OperationNotPermittedException, ServiceStateException {

        ArgumentValidator.notNull("service", service);
        Product prod = dm.getReference(Product.class, service.getKey());
        validatePermissionForSuspendAndResume(dm.getCurrentUser(), prod);

        // If the product is a PARTNER_TEMPLATE then get itself
        Product tempOrSelf;
        if (prod.getType() == ServiceType.PARTNER_TEMPLATE) {
            tempOrSelf = prod;
        }
        // Otherwise get the template or self
        else {
            tempOrSelf = prod.getTemplateOrSelf();
        }

        if (tempOrSelf.getStatus() != ServiceStatus.SUSPENDED) {
            ServiceStateException e = new ServiceStateException(ServiceStatus.SUSPENDED, tempOrSelf.getStatus());
            logger.logWarn(Log4jLogger.SYSTEM_LOG, e, LogMessageIdentifier.WARN_RESUME_SERVICE_INVALID_STATE,
                    tempOrSelf.getStatus().name());
            throw e;
        }
        tempOrSelf.setStatus(ServiceStatus.ACTIVE);
        List<Product> list = getCustomerSpecificProductCopies(tempOrSelf);
        for (Product product : list) {
            if (product.getStatus() == ServiceStatus.SUSPENDED) {
                product.setStatus(ServiceStatus.ACTIVE);
            }
        }
        dm.flush();
        VOService voProduct = ProductAssembler.toVOProduct(prod,
                new LocalizerFacade(localizer, dm.getCurrentUser().getLocale()));

        return voProduct;
    }

    /**
     * Check that the provided service is published to a marketplace owned by
     * the provided user and that it does not belong to a subscription.
     * 
     * @param currentUser
     *            the calling user
     * @param prod
     *            the service to validate
     * @return the marketplace the service is published to
     * @throws OperationNotPermittedException
     *             in case the service is not published to a marketplace owned
     *             by the caller or the service belongs to a subscription
     */
    private Marketplace validatePermissionForSuspendAndResume(PlatformUser currentUser, Product prod)
            throws OperationNotPermittedException {
        if (prod.getOwningSubscription() != null) {
            String message = "Service '%s' is related to a subscription.";
            OperationNotPermittedException e = new OperationNotPermittedException(
                    String.format(message, Long.valueOf(prod.getKey())));
            logger.logWarn(Log4jLogger.SYSTEM_LOG, e,
                    LogMessageIdentifier.WARN_VALIDATE_PERMISSION_FOR_SUSPEND_AND_RESUME, message);
            throw e;
        }

        Product tempOrSelf;
        if (prod.getType() == ServiceType.PARTNER_TEMPLATE) {
            tempOrSelf = prod;
        } else {
            tempOrSelf = prod.getTemplateOrSelf();
        }
        List<CatalogEntry> ces = tempOrSelf.getCatalogEntries();
        // the catalog entry may have no marketplace set
        // this happens when the MP got deleted while the service was not active
        if (ces == null || ces.isEmpty() || ces.get(0).getMarketplace() == null) {
            String message = "Service '%s' is not published to a marketplace.";
            OperationNotPermittedException e = new OperationNotPermittedException(
                    String.format(message, Long.valueOf(prod.getKey())));
            logger.logWarn(Log4jLogger.SYSTEM_LOG, e,
                    LogMessageIdentifier.WARN_VALIDATE_PERMISSION_FOR_SUSPEND_AND_RESUME, message);
            throw e;
        }
        // currently a service can only be published to one marketplace
        Marketplace mp = ces.get(0).getMarketplace();
        PermissionCheck.owns(mp, currentUser.getOrganization(), logger, sessionCtx);
        return mp;
    }

    /**
     * compares the technical product key
     */
    private void validateTechnicalProductCompatibility(Product referenceProduct, Product compatibleProd)
            throws ServiceCompatibilityException {
        if (compatibleProd.getTechnicalProduct().getKey() != referenceProduct.getTechnicalProduct().getKey()) {
            ServiceCompatibilityException ipc = new ServiceCompatibilityException(
                    "Definition of product compatibility failed, related technical products do not match",
                    ServiceCompatibilityException.Reason.TECH_SERVICE);
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, ipc,
                    LogMessageIdentifier.WARN_DEFINE_COMPATIBILITY_FOR_PRODUCTS_FAILED_NOT_SAME_BASE,
                    dm.getCurrentUser().getUserId(), Long.toString(compatibleProd.getKey()),
                    Long.toString(referenceProduct.getKey()));
            throw ipc;
        }
    }

    /**
     * Checks weather all currencies of the dependent price models are equal, or
     * free of charge.
     */
    private void validateCurrencyCompatibility(Product referenceProduct, Product compatibleProduct)
            throws ServiceCompatibilityException {
        PriceModel referencePriceModel = referenceProduct.getPriceModel();
        PriceModel comaptiblePriceModel = compatibleProduct.getPriceModel();
        if (!isCompatibleCurrency(referencePriceModel, comaptiblePriceModel)) {
            ServiceCompatibilityException ipc = new ServiceCompatibilityException(
                    "Definition of product compatibility failed,the price models have different currencies",
                    ServiceCompatibilityException.Reason.CURRENCY);
            logger.logWarn(Log4jLogger.SYSTEM_LOG | Log4jLogger.AUDIT_LOG, ipc,
                    LogMessageIdentifier.WARN_DEFINE_COMPATIBILITY_FOR_PRODUCTS_FAILED_NOT_SAME_CURRENCY,
                    dm.getCurrentUser().getUserId(), Long.toString(referencePriceModel.getKey()),
                    Long.toString(comaptiblePriceModel.getKey()));
            throw ipc;
        }
    }

    /**
     * Returns true if one or both of the price models are free of charge, or
     * the currencies are of the same type.
     */
    private boolean isCompatibleCurrency(PriceModel referencePriceModel, PriceModel compatiblePriceModel) {
        if (referencePriceModel != null && compatiblePriceModel != null && referencePriceModel.isChargeable()
                && compatiblePriceModel.isChargeable()) {
            if (!referencePriceModel.getCurrency().equals(compatiblePriceModel.getCurrency())) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if one or both of the price models are free of charge, or
     * the currencies are of the same type.
     */
    private boolean isCompatibleCurrency(PriceModel referencePriceModel, VOPriceModel compatiblePriceModel) {
        if (referencePriceModel != null && compatiblePriceModel != null && referencePriceModel.isChargeable()
                && compatiblePriceModel.isChargeable()) {
            if (!referencePriceModel.getCurrency().getCurrencyISOCode()
                    .equals(compatiblePriceModel.getCurrencyISOCode())) {
                return false;
            }
        }
        return true;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public List<VOCompatibleService> getPotentialCompatibleServices(VOService service)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);
        Product p = dm.getReference(Product.class, service.getKey());
        Organization supplier = dm.getCurrentUser().getOrganization();
        PermissionCheck.owns(p, supplier, logger, null);

        p = p.getTemplateOrSelf();

        // get the marketplaces the service is published on
        Set<Marketplace> mps = new HashSet<>();
        for (CatalogEntry ce : p.getCatalogEntries()) {
            Marketplace mp = ce.getMarketplace();
            if (mp != null) {
                mps.add(mp);
            }
        }
        List<VOCompatibleService> result = new ArrayList<>();
        if (mps.isEmpty()) {
            // Bug 9850: is no marketplace is set, return an empty list
            return result;
        }

        // get all services based on the same TP, published on the same MPs
        Query q = dm.createNamedQuery("Product.getPotentialCompatibleForProduct");
        q.setParameter("marketplaces", mps);
        q.setParameter("vendorKey", Long.valueOf(supplier.getKey()));
        q.setParameter("tp", p.getTechnicalProduct());
        q.setParameter("status", EnumSet.of(ServiceStatus.DELETED, ServiceStatus.OBSOLETE));
        List<Product> products = ParameterizedTypes.list(q.getResultList(), Product.class);
        products.remove(p);

        // get the currently configured targets
        Set<Long> targetKeys = new HashSet<>();
        q = dm.createNamedQuery("ProductReference.getTargetKeysForProduct");
        q.setParameter("product", p);
        targetKeys.addAll(ParameterizedTypes.list(q.getResultList(), Long.class));

        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        ProductAssembler.prefetchData(products, facade, PerformanceHint.ONLY_FIELDS_FOR_LISTINGS);

        for (Product prod : products) {
            if (isCompatibleCurrency(p.getPriceModel(), prod.getPriceModel())) {
                VOCompatibleService s = ProductAssembler.toVOCompatibleService(prod, targetKeys, facade);
                result.add(s);
            }
        }

        return result;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public boolean isPartOfUpgradePath(VOService service)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);

        Organization org = dm.getCurrentUser().getOrganization();

        // retrieve the domain object for the product
        Product prod = dm.getReference(Product.class, service.getKey());

        // ensure that the product belongs to the calling supplier
        PermissionCheck.owns(prod, org, logger, sessionCtx);

        boolean result = isPartOfUpgradePath(prod.getKey());

        return result;
    }

    @Override
    public boolean isPartOfUpgradePath(long serviceKey) {
        Query query = dm.createNamedQuery("Product.countAllReferences");
        query.setParameter("productKey", Long.valueOf(serviceKey));
        query.setParameter("status", EnumSet.of(ServiceStatus.DELETED, ServiceStatus.OBSOLETE));
        Long count = (Long) query.getSingleResult();
        return count.longValue() > 0;
    }

    @Override
    public List<Product> getCustomerSpecificCopyProducts(Product template) throws OperationNotPermittedException {
        Organization supplier = dm.getCurrentUser().getOrganization();
        PermissionCheck.owns(template, supplier, logger, sessionCtx);

        Set<ServiceType> types = EnumSet.of(ServiceType.CUSTOMER_TEMPLATE);

        Query query = dm.createNamedQuery("Product.getCustomerSpecificCopiesForTemplate");
        query.setParameter("template", template);
        query.setParameter("serviceType", types);
        List<Product> list = ParameterizedTypes.list(query.getResultList(), Product.class);

        return list;
    }

    @Override
    public List<VOCustomerService> getServiceCustomerTemplates(VOService service)
            throws ObjectNotFoundException, OperationNotPermittedException {

        ArgumentValidator.notNull("service", service);

        Product storedService = dm.getReference(Product.class, service.getKey());
        List<Product> customerProducts = getCustomerSpecificCopyProducts(storedService);

        List<VOCustomerService> result = new ArrayList<>();
        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        Set<ServiceStatus> states = EnumSet.of(ServiceStatus.ACTIVE, ServiceStatus.INACTIVE,
                ServiceStatus.SUSPENDED);

        for (Product product : customerProducts) {
            if (states.contains(product.getStatus())) {
                result.add(ProductAssembler.toVOCustomerProduct(product, facade));
            }
        }

        return result;
    }

    @Override
    @RolesAllowed("SERVICE_MANAGER")
    public void deleteService(Long key)
            throws ObjectNotFoundException, OrganizationAuthoritiesException, OperationNotPermittedException,
            ServiceOperationException, ServiceStateException, ConcurrentModificationException {
        VOService vo = new VOService();
        vo.setKey(key.longValue());
        vo = getServiceDetails(vo);
        deleteService(vo);
    }

    @Override
    @RolesAllowed("TECHNOLOGY_MANAGER")
    public void deleteTechnicalService(Long key) throws ObjectNotFoundException, OperationNotPermittedException,
            DeletionConstraintException, OrganizationAuthoritiesException, ConcurrentModificationException {
        Organization provider = dm.getCurrentUser().getOrganization();
        VOTechnicalService vo = new VOTechnicalService();
        vo.setKey(key.longValue());
        TechnicalProduct tProd = findTechnicalProductAndCheckOwner(provider, vo);
        LocalizerFacade facade = new LocalizerFacade(localizer, dm.getCurrentUser().getLocale());
        List<ParameterDefinition> paramDefs = getPlatformParameterDefinitions(tProd);
        List<Event> platformEvents = getPlatformEvents(tProd);
        vo = TechnicalProductAssembler.toVOTechnicalProduct(tProd, paramDefs, platformEvents, facade, false);
        deleteTechnicalService(vo);
    }
}