All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.cq.commerce.common.VendorJcrPlacedOrder Maven / Gradle / Ivy

There is a newer version: 6.5.21
Show newest version
package com.adobe.cq.commerce.common;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Currency;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2013 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/

import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceSession;
import com.adobe.cq.commerce.api.PlacedOrder;
import com.adobe.cq.commerce.api.PriceInfo;
import com.adobe.cq.commerce.api.Product;
import com.adobe.cq.commerce.api.conf.CommerceBasePathsService;
import com.adobe.cq.commerce.api.promotion.PromotionInfo;
import com.adobe.cq.commerce.api.promotion.Voucher;
import com.adobe.cq.commerce.api.promotion.VoucherInfo;
import com.adobe.cq.commerce.impl.promotion.JcrVoucherImpl;
import com.day.cq.commons.jcr.JcrUtil;

/**
 * An editable implementation of a {@link PlacedOrder}, for use in order administration.
 *
 * NB: models vendor records in /var/commerce/orders/, not shopper records in ~/commerce/orders/.
 * For (read-only) access to shopper records, see {@link DefaultJcrPlacedOrder}.
 */
public class VendorJcrPlacedOrder implements PlacedOrder {
    protected static final Logger log = LoggerFactory.getLogger(VendorJcrPlacedOrder.class);

    protected Resource order;

    protected Map details;
    protected List prices;
    protected List entries;

    protected AbstractJcrCommerceService commerceService;
    protected Locale locale;

    /**
     * Instantiate a new, writable PlacedOrder record.
     *
     * @param commerceService The owning CommerceService
     * @param orderId An ID uniquely identifying the placed order.  Can either be the orderId property, or
     *                the path to the order node.  (The path is obviously quicker, but the orderId might
     *                be used to track the order in external systems, such as fulfillment systems.)
     * @param locale
     */
    public VendorJcrPlacedOrder(AbstractJcrCommerceService commerceService, String orderId, Locale locale) {
        this.commerceService = commerceService;
        this.locale = locale;
        this.order = getPlacedOrder(orderId);
    }

    @Override
    public String getOrderId() throws CommerceException {
        if (details == null) {
            lazyLoadOrderDetails();
        }
        return (String) details.get("orderId");
    }

    @Override
    public Map getOrder() throws CommerceException {
        if (details == null) {
            lazyLoadOrderDetails();
        }
        return details;
    }

    /**
     * Update a number of properties in the order record.  Currently this is a "raw" update;
     * there is no re-calculation involved.
     * @param delta The properties to update.  Properties not in this set will NOT be removed;
     *              to remove a property include it with a value of null.
     * @param autoSave If true, the current session will be saved after updating the order.
     * @throws CommerceException
     */
    public void updateOrder(Map delta, boolean autoSave) throws CommerceException {
        try {
            Node orderNode = order.adaptTo(Node.class);
            for (Map.Entry entry : delta.entrySet()) {
                String key = entry.getKey();
                // skip synthetic properties
                if ("orderPath".equals(key) || key.endsWith("Formatted")) {
                    continue;
                }
                setProperty(orderNode, key, entry.getValue());
            }

            if (autoSave) {
                orderNode.getSession().save();
            }
        } catch (RepositoryException e) {
            throw new CommerceException("Failed to persist the order properties", e);
        } finally {
            // caches will need re-loading...
            details = null;
            prices = null;
            entries = null;
        }
    }

    protected void setProperty(Node node, String propertyPath, Object value) throws RepositoryException {
        String propertyName = propertyPath;
        int lastSlash = propertyPath.lastIndexOf('/');
        if (lastSlash >= 0) {
            node = JcrUtil.createPath(node, propertyPath.substring(0, lastSlash), false, "nt:unstructured", "nt:unstructured",
                    node.getSession(), false);
            propertyName = propertyPath.substring(lastSlash + 1);
        }
        if (value instanceof Calendar) {
            node.setProperty(propertyName, (Calendar) value);
        } else if (value instanceof BigDecimal) {
            node.setProperty(propertyName, (BigDecimal) value);
        } else if (value instanceof String) {
            String stringValue = (String) value;
            if (stringValue.startsWith("[") && stringValue.endsWith("]")) {
                String[] stringValues = stringValue.substring(1, stringValue.length()-1).split(",");
                node.setProperty(propertyName, stringValues);
            } else {
                node.setProperty(propertyName, stringValue);
            }
        } else if (value instanceof String[]) {
            node.setProperty(propertyName, (String[]) value);
        }
    }

    //
    // On-demand loads the details member variable from the order data.
    //
    private void lazyLoadOrderDetails() throws CommerceException {
        details = new HashMap();
        if (order != null) {
            final SimpleDateFormat dateFmt = new SimpleDateFormat("dd MMM, yyyy");

            details.put("orderPath", order.getPath());

            ValueMap orderProperties = order.getValueMap();
            for (Map.Entry entry : orderProperties.entrySet()) {
                String key = entry.getKey();
                if ("cartItems".equals(key)) {
                    // returned by getPlacedOrderEntries()
                } else {
                    Object property = entry.getValue();
                    if (property instanceof Calendar) {
                        // explode date into 'property' and 'propertyFormatted'
                        details.put(key, property);
                        details.put(key + "Formatted", dateFmt.format(((Calendar) property).getTime()));
                    } else {
                        details.put(key, property);
                    }
                }
            }

            Resource orderDetailsChild = order.getChild("order-details");
            if (orderDetailsChild != null) {
                ValueMap orderDetailProperties = orderDetailsChild.getValueMap();
                for (ValueMap.Entry detailProperty : orderDetailProperties.entrySet()) {
                    String key = detailProperty.getKey();
                    Object property = detailProperty.getValue();
                    if (property instanceof Calendar) {
                        // explode date into 'property' and 'propertyFormatted'
                        details.put("order-details/" + key, property);
                        details.put("order-details/" + key + "Formatted", dateFmt.format(((Calendar) property).getTime()));
                    } else {
                        details.put("order-details/" + key, property);
                    }
                }
            }
        }
    }

    @Override
    public List getCartPriceInfo(Predicate filter) throws CommerceException {
        if (prices == null) {
            lazyLoadPriceInfo();
        }

        List filteredPrices = new ArrayList();
        CollectionUtils.select(prices, filter, filteredPrices);
        return filteredPrices;
    }

    @Override
    public String getCartPrice(Predicate filter) throws CommerceException {
        if (prices == null) {
            lazyLoadPriceInfo();
        }

        PriceInfo price = (PriceInfo) CollectionUtils.find(prices, filter);
        if (price != null) {
            return price.getFormattedString();
        } else {
            return "";
        }
    }

    //
    // On-demand loads the prices member variable from the order data.
    //
    protected void lazyLoadPriceInfo() throws CommerceException {
        prices = new ArrayList();

        if (order != null) {
            ValueMap orderMap = order.getValueMap();
            String currencyCode = orderMap.get("currencyCode", String.class);
            if (currencyCode == null) {
                log.error("Missing currencyCode in order: " + order.getPath());
                log.error("Assuming 'USD'");
                currencyCode = "USD";
            }
            Currency currency = Currency.getInstance(currencyCode);

            //
            // Note: order is important; non-fully-specified price requests will get the first match.
            //

            PriceInfo price = new PriceInfo(orderMap.get("orderTotalPrice", BigDecimal.class), locale, currency);
            price.put(PriceFilter.PN_TYPES, new HashSet(Arrays.asList("orderTotalPrice")));
            prices.add(price);

            price = new PriceInfo(orderMap.get("orderTotalTax", BigDecimal.class), locale, currency);
            price.put(PriceFilter.PN_TYPES, new HashSet(Arrays.asList("orderTotalTax")));
            prices.add(price);

            price = new PriceInfo(orderMap.get("cartSubtotal", BigDecimal.class), locale, currency);
            price.put(PriceFilter.PN_TYPES, new HashSet(Arrays.asList("cartSubtotal")));
            prices.add(price);

            price = new PriceInfo(orderMap.get("orderShipping", BigDecimal.class), locale, currency);
            price.put(PriceFilter.PN_TYPES, new HashSet(Arrays.asList("orderShipping")));
            prices.add(price);
        }
    }

    @Override
    public List getCartEntries() throws CommerceException {
        if (entries == null) {
            lazyLoadCartEntries();
        }
        return entries;
    }

    //
    // On-demand loads the entries member variable form the order data.
    //
    protected void lazyLoadCartEntries() throws CommerceException {
        entries = new ArrayList();

        if (order != null) {
            String[] serializedEntries = order.getValueMap().get("cartItems", new String[0]);
            for (String serializedEntry : serializedEntries) {
                try {
                    CommerceSession.CartEntry entry = deserializeCartEntry(serializedEntry, entries.size());
                    entries.add(entry);
                } catch (Exception e) {     // NOSONAR (catch any errors thrown attempting to parse/decode entry)
                    log.error("Unable to load product from order: {}", serializedEntry);
                }
            }
        }
    }

    protected CommerceSession.CartEntry deserializeCartEntry(String str, int index) throws CommerceException {
        Object[] entryData = commerceService.deserializeCartEntryData(str);
        Product product = (Product) entryData[0];
        int quantity = (Integer) entryData[1];
        DefaultJcrCartEntry entry = commerceService.newCartEntryImpl(index, product, quantity);
        if (entryData[2] == null) {
            return entry;
        }

        @SuppressWarnings("unchecked")
        Map properties = (Map) entryData[2];
        entry.updateProperties(properties);
        return entry;
    }

    @Override
    public List getPromotions() throws CommerceException {
        List infos = new ArrayList();

        if (order != null) {
            String[] records = order.getValueMap().get("promotions", new String[]{});
            for (String record : records) {
                try {
                    String[] fields = record.split(";", 3);
                    String path = "null".equals(fields[0]) ? null : fields[0];
                    Integer entryIndex = "null".equals(fields[1]) ? null : Integer.parseInt(fields[1]);
                    String message = "null".equals(fields[2]) ? null : fields[2];
                    infos.add(new PromotionInfo(path, "", PromotionInfo.PromotionStatus.FIRED, "", message, entryIndex));
                } catch (Exception e) {     // NOSONAR (catch any errors thrown attempting to parse/decode entry)
                    log.error("Unable to load promotion: " + record, e);
                }
            }
        }

        return infos;
    }

    @Override
    public List getVoucherInfos() throws CommerceException {
        List infos = new ArrayList();

        if (order != null) {
            String[] records = order.getValueMap().get("vouchers", new String[]{});
            for (String record : records) {
                String[] fields = record.split(";", 3);
                try {
                    if (fields.length == 1) {
                        // 5.6 only wrote out the path of JCR vouchers.  Since we have no way of knowing if the
                        // Voucher has materially changed since the order was placed (or even if the Voucher
                        // still exists, for that matter), just attempt to fetch the voucher code out of it.
                        String path = fields[0];
                        Voucher voucher = new JcrVoucherImpl(order.getResourceResolver().getResource(path));
                        infos.add(new VoucherInfo(voucher.getCode(), voucher.getPath(), "", "", true, ""));
                    } else {
                        // 6.0 serializes code;path;message
                        String code = "null".equals(fields[0]) ? null : fields[0];
                        String path = "null".equals(fields[1]) ? null : fields[1];
                        String message = "null".equals(fields[2]) ? null : fields[2];
                        infos.add(new VoucherInfo(code, path, "", "", true, message));
                    }
                } catch (Exception e) {     // NOSONAR (catch any errors thrown attempting to parse/decode entry)
                    log.error("Unable to load voucher from order: {}", record);
                }
            }
        }

        return infos;
    }

    /**
     * Return the resource for a placed order identified by path or primary key.  (A path is obviously
     * quicker, but a primary key might be used to track the order in external systems, such as fulfillment
     * systems.)
     */
    protected Resource getPlacedOrder(String orderId) {
        try {
            //
            // Quick way -- orderId is already a path:
            //
            if (orderId.startsWith("/")) {
                return commerceService.resolver.getResource(orderId);
            }

            //
            // Slow way -- orderId needs to be looked up in the user's home directory:
            //
            // example query: /jcr:root/var/commerce/orders//element(*)[@orderId='foo')]
            CommerceBasePathsService cbps = commerceService.resolver.adaptTo(CommerceBasePathsService.class);
            StringBuilder buffer = new StringBuilder();
            buffer.append("/jcr:root" + cbps.getOrdersBasePath() + "//element(*)[@orderId = '")
                    .append(Text.escapeIllegalXpathSearchChars(orderId).replaceAll("'", "''"))
                    .append("']");

            final QueryManager queryManager = commerceService.resolver.adaptTo(Session.class).getWorkspace().getQueryManager();
            final Query query = queryManager.createQuery(buffer.toString(), Query.XPATH);
            NodeIterator nodeIterator = query.execute().getNodes();
            if (nodeIterator.hasNext()) {
                return commerceService.resolver.getResource(nodeIterator.nextNode().getPath());
            }
        } catch (Exception e) {     // NOSONAR (fail-safe for when the query above contains errors)
            log.error("Error while searching for order history with orderId '" + orderId + "'", e);
        }
        return null;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy