/**
 * PETALS - PETALS Services Platform. Copyright (c) 2010 EBM WebSourcing -
 * http://www.petalslink.com/
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version. This library is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 */

package org.ow2.petals.se.jsr181;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.activation.DataHandler;
import javax.jbi.messaging.MessagingException;

import org.ow2.petals.component.framework.api.message.Exchange;
import org.ow2.petals.component.framework.api.util.MtomUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * A patch for the CDK's MTOM Utilities.
 *
 * @author Vincent Zurczak - EBM WebSourcing
 */
public class Patch {

    /**
     * A bean that describes a mapping of attachments associated with a Petals
     * message.
     * <p>
     * An instance of this class aims at:
     * </p>
     * <ul>
     * <li>Associating a content ID with a data handler.</li>
     * <li>Indicating content IDs that could be mapped to a data handlers
     * (missing data-handler).</li>
     * <li>Indicating data handlers that are not referenced from the pay-load
     * (data handlers that may be ignored).</li>
     * </ul>
     */
    public static class MtomMapping {

        private final Map<String, DataHandler> contentIdToDataHandler;
        private final Set<String> orphanContentIds;
        private final List<DataHandler> orphanDataHandlers;

        /**
         * Constructor.
         */
        public MtomMapping() {
            this.contentIdToDataHandler = new HashMap<String, DataHandler>();
            this.orphanContentIds = new HashSet<String>();
            this.orphanDataHandlers = new ArrayList<DataHandler>();
        }

        /**
         * @return a map associating a content ID (as specified in the XOP's
         *         cid) and a data handler.
         *         <p>
         *         This map is never null, but can be empty.
         *         </p>
         */
        public Map<String, DataHandler> getContentIdToDataHandler() {
            return this.contentIdToDataHandler;
        }

        /**
         * @return the list of content IDs for which no data handler was found.
         *         <p>
         *         This list is never null, but can be empty.<br />
         *         Every missing data handler implies there is an error,
         *         something is missing in the pay-load.
         *         </p>
         */
        public Set<String> getOrphanContentIds() {
            return this.orphanContentIds;
        }

        /**
         * @return the list of data handlers that are not associated with a
         *         content ID.
         *         <p>
         *         This list is never null, but can be empty.<br />
         *         Every orphan data handler implies there is an error on the
         *         client side (SwA attachments are part of the pay-load). But
         *         this should not prevent the processing from being done. This
         *         method aims at describing the coherence of the pay-load with
         *         the attachments (and can be used for logging reasons).
         *         </p>
         */
        public List<DataHandler> getOrphanDataHandlers() {
            return this.orphanDataHandlers;
        }

        /**
         * @param key
         *            a content ID, as specified by the XOP's cid
         * @param value
         *            the associated data handler
         * @return the previous value that was associated with this content ID
         * @see java.util.Map#put(java.lang.Object, java.lang.Object)
         */
        DataHandler put(String key, DataHandler value) {
            return this.contentIdToDataHandler.put(key, value);
        }

        /**
         * @param o
         *            a content ID, as specified by the XOP's cid
         * @return true, if this content ID was not already stored
         * @see java.util.Set#add(java.lang.Object)
         */
        boolean add(String o) {
            return this.orphanContentIds.add(o);
        }

        /**
         * @param o
         *            a data handler which are associated with no content ID
         * @return true if the data handler was not already in the list
         * @see java.util.List#add(java.lang.Object)
         */
        boolean add(DataHandler o) {
            return this.orphanDataHandlers.add(o);
        }

        /**
         * @param c
         *            a collection of data handlers which are associated with no
         *            content ID
         * @return if this list changed as a result of the call
         * @see java.util.List#add(java.lang.Object)
         */
        boolean addAll(Collection<? extends DataHandler> c) {
            return this.orphanDataHandlers.addAll(c);
        }
    }

    /**
     * Builds a MTOM mapping from both an exchange and a root element.
     *
     * @param exchange
     *            the message exchange
     * @param element
     *            the element from which MTOM elements must be searched.
     *            <p>
     *            MTOM elements are elements associated with the name space
     *            <code>http://www.w3.org/2004/08/xop/include</code>.<br />
     *            If null, the root element of the pay-load is taken.<br />
     *            If you already have extracted the pay-load as a document, you
     *            can use <code>Document#getDocumentElement()</code>.
     *            </p>
     *
     * @return null if the element is null and could not be built, a
     *         {@link MtomMapping} instance otherwise
     * @throws MessagingException
     *             if something went wrong while acquiring the message content
     */
    public static MtomMapping getMtomMapping(Exchange exchange, Element element) throws MessagingException {

        MtomMapping result = null;

        // Need an element? Take the root of the pay-load.
        if (element == null) {
            Document doc = exchange.getInMessageContentAsDocument(true);
            if (doc != null && doc.getDocumentElement() != null)
                element = doc.getDocumentElement();
        }

        // Get the mapping
        if (element != null) {
            result = new MtomMapping();

            // Get all the content IDs from the pay-load
            Set<String> contentIds = MtomUtil.extractAttachmentsIdFromPayload(element);
            List<DataHandler> allTheDataHandlers = new ArrayList<DataHandler> ();
            for (String attName : exchange.getInMessageAttachmentNames()) {
                allTheDataHandlers.add(exchange.getInMessageAttachment(attName));
            }

            // For each content ID, find the associated data handler.
            // Keep a trace of orphan data handlers.
            for (String contentId : contentIds) {
                DataHandler dataHandler = null;
                for (String attName : exchange.getInMessageAttachmentNames()) {
                    if (MtomUtil.compare(contentId, attName)) {
                        dataHandler = exchange.getInMessageAttachment(attName);
                        break;
                    }
                }

                if (dataHandler != null) {
                    allTheDataHandlers.remove(dataHandler);
                    result.put(contentId, dataHandler);
                }
                else {
                    result.add(contentId);
                }
            }

            // Do not forget the orphan data handlers
            result.addAll(allTheDataHandlers);
        }

        return result;
    }
}
