petascope.wcst.transaction.executeTransaction.java Source code

Java tutorial

Introduction

Here is the source code for petascope.wcst.transaction.executeTransaction.java

Source

/*
 * This file is part of rasdaman community.
 *
 * Rasdaman community is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rasdaman community 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with rasdaman community.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003 - 2010 Peter Baumann / rasdaman GmbH.
 *
 * For more information please see <http://www.rasdaman.org>
 * or contact Peter Baumann via <baumann@rasdaman.com>.
 */
package petascope.wcst.transaction;

import java.sql.SQLException;
import net.opengis.ows.v_1_0_0.BoundingBoxType;
import net.opengis.wcs.ows.v_1_1_0.InterpolationMethodType;
import net.opengis.wcs.v_1_1_0.CoverageDescriptionType;
import net.opengis.wcs.v_1_1_0.CoverageDescriptions;
import net.opengis.wcs.v_1_1_0.CoverageSummaryType;
import net.opengis.wcs.v_1_1_0.FieldType;
import net.opengis.wcs.v_1_1_0.RangeType;
import org.apache.commons.io.IOUtils;
import petascope.exceptions.PetascopeException;
import petascope.exceptions.RasdamanException;
import petascope.exceptions.WCPSException;
import petascope.wcps.server.core.SDU;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import javax.imageio.ImageIO;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import net.opengis.wcs.ows.v_1_1_0.InterpolationMethods;
import org.odmg.ODMGException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import petascope.core.DbMetadataSource;
import petascope.core.Metadata;
import petascope.exceptions.ExceptionCode;
import petascope.ConfigManager;
import petascope.wcps.server.core.CellDomainElement;
import petascope.wcps.server.core.DomainElement;
import petascope.wcps.server.core.InterpolationMethod;
import petascope.wcps.server.core.RangeElement;
import petascope.exceptions.WCSTException;
import petascope.util.AxisTypes;
import wcst.transaction.schema.CodeType;
import wcst.transaction.schema.CoverageType;
import wcst.transaction.schema.KeywordsType;
import wcst.transaction.schema.LanguageStringType;
import wcst.transaction.schema.ManifestType;
import wcst.transaction.schema.ReferenceType;
import wcst.transaction.schema.TransactionResponseType;
import wcst.transaction.schema.TransactionType;
import petascope.wcst.transaction.tools.RasdamanUtils;
import petascope.util.CrsUtil;

/**
 * This class takes a WCS-T Transaction XML request and executes the request,
 * building the corresponding XML respose. Use this class for synchronous processing.
 *
 * @author Andrei Aiordachioaie
 */
public class executeTransaction {

    private static Logger log = LoggerFactory.getLogger(executeTransaction.class);
    //   private static boolean printLog = true;
    private boolean finished;
    private TransactionType input;
    protected TransactionResponseType output;
    private RasdamanUtils rasUtils;
    private String requestId;
    private DbMetadataSource metaDb;
    private HashSet<String> newCoverages;

    /**
     * Default constructor. Initialize internal variables.
     * @param tr Transaction object, a WCS-T request
     * @param metadataDbPath Path to the "dbparams.properties" file
     */
    public executeTransaction(TransactionType tr, DbMetadataSource source) throws WCSTException, RasdamanException {
        input = tr;
        output = new TransactionResponseType();
        finished = false;
        metaDb = source;
        newCoverages = new HashSet<String>();

        String server = ConfigManager.RASDAMAN_URL;
        String db = ConfigManager.RASDAMAN_DATABASE;

        rasUtils = new RasdamanUtils(server, db);
        rasUtils.init();

        // In case no-one will ever call this method and we need it
        generateRequestId();
    }

    /**
     * Generate a new Request ID string, and return it. If the transaction request
     * does not include a request ID, the last generated string will be used.
     * @return String Request ID
     */
    public String generateRequestId() {
        requestId = String.valueOf(UUID.randomUUID());
        return requestId;
    }

    /**
     * Main method of this class: Computes the response to the TransactionResponse
     * request given to the constructor. If needed, it also calls <b>process()</b>
     * @return a TransactionResponse object.
     * @throws WCSTException
     */
    public TransactionResponseType get() throws WCSTException, WCPSException, PetascopeException {
        try {
            if (finished == false) {
                metaDb.ensureConnection();
                process();
            }
        } catch (SQLException e) {
            throw new WCSTException(ExceptionCode.InternalSqlError,
                    "Could not ensure connection to database is valid", e);
        }
        if (finished == false) {
            throw new WCSTException(ExceptionCode.NoApplicableCode,
                    "Could not execute the Transaction request! " + "Please see the other errors...");
        }

        return output;
    }

    /**
     * Computes the response to the Transaction request given to the constructor.
     */
    public void process() throws WCSTException, WCPSException, PetascopeException {
        if (!input.getService().equalsIgnoreCase("WCS")) {
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Service. Explanation: Service must be \"WCS\" !");
        }
        if (!input.getVersion().equalsIgnoreCase("1.1")) {
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Service. Explanation: Service Version must be \"1.1\" !");
        }

        // Set the output request ID
        String reqID = input.getRequestId();
        if (reqID == null) {
            reqID = "Request_" + requestId;
            generateRequestId();
        }
        output.setRequestId(reqID);

        // All actions succeed or fail as one group.
        try {
            ManifestType covs = input.getInputCoverages();
            List l = covs.getReferenceGroup();

            for (int i = 0; i < l.size(); i++) {
                // This object is the XML element "InputCoverages"
                Object obj = ((JAXBElement) l.get(i)).getValue();
                CoverageType cov = (CoverageType) obj;

                // Each action adds something to the output XML document
                processInputCoverageNode(cov);
            }

            finished = true;

            /*  Commit rasdaman changes */
            try {
                log.debug("Commit rasdaman changes ...");
                rasUtils.commitAndClose();
                log.debug("Rasdaman coverages saved successfully !");
            } catch (ODMGException e) {
                throw new WCSTException(ExceptionCode.InternalComponentError, "Could not commit Rasdaman changes !",
                        e);
            }

            /* Commit metadata changes */
            try {
                log.debug("Commit metadata changes ...");
                metaDb.commitAndClose();
                log.debug("Metadata has been saved !");
            } catch (SQLException e) {
                throw new WCSTException(ExceptionCode.InternalSqlError, "Could not commit metadata changes", e);
            }
        } catch (WCSTException e) {
            // One action failed, therefore all actions have failed

            /* Abort metadata changes */
            finished = false;
            try {
                log.debug("Rolling back metadata database changes ...");
                metaDb.abortAndClose();
                log.debug("Metadata rollback completed!");
            } catch (SQLException ex) {
                log.error("Could not rollback metadata changes.");
                e.appendExceptionText(" Could not rollback metadata changes!");
            }

            /* Abort rasdaman changes */
            try {
                log.debug("Aborting rasdaman changes ...");
                rasUtils.abortAndClose();
                log.debug("Rasdaman changes aborted !");
            } catch (ODMGException ex) {
                log.error("Could not abort rasdaman changes.");
                e.appendExceptionText(" Could not rollback rasdaman changes!");
            }

            throw e;
        }
    }

    /**
     * Delete a coverage from the Rasdaman server.
     * @param identifier Name of coverage
     * @throws Exception
     */
    private void deleteCoverageFromRasdaman(String identifier) throws Exception {
        try {
            log.trace("Deleting coverage from Rasdaman ...");
            rasUtils.deleteCollection(identifier);
            log.trace("Rasdaman Collection '" + identifier + "' is now deleted !");
        } catch (ODMGException e) {
            log.error("Failed to delete rasdaman collection " + identifier);
            throw new WCSTException(ExceptionCode.InternalComponentError,
                    "Failed to delete collection from Rasdaman !", e);
        }
    }

    /**
     * Insert pixel data for a coverage into RasDaMan DB system
     *
     * @param identifier Identifier of the coverage
     * @param href The location of the pixels for the new image
     */
    private void insertImageIntoRasdaman(String identifier, BufferedImage img)
            throws WCSTException, RasdamanException {
        log.trace("Inserting image into Rasdaman raster server...");
        try {
            rasUtils.insertGrayImageAsArray(identifier, img);
            log.debug("Inserted image into Rasdaman !");
        } catch (ODMGException e) {
            log.error("Could not insert image into Rasdaman !");
            throw new WCSTException(ExceptionCode.InternalComponentError, "Could not insert image into Rasdaman.",
                    e);
        }
    }

    /** Load a BufferedImage from a ReferenceType object.
     *
     * @param pixels Reference object
     * @return available image
     * @throws WCSTException
     */
    private BufferedImage loadPixelsReference(ReferenceType pixels) throws WCSTException {
        URL url = null;
        BufferedImage img = null;

        try {
            url = new URL(pixels.getHref());
        } catch (MalformedURLException e) {
            log.error("URL " + url.toString() + " is not valid.");
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Reference pixels. " + "Explanation: URL " + url.toString() + " is not valid.");
        }

        try {
            img = ImageIO.read(url);
            if (img == null) {
                throw new IOException("Empty stream while reading image.");
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new WCSTException(ExceptionCode.IOConnectionError,
                    "Reference Pixels. Explanation: " + "Could not read image from URL '" + url, e);
        }

        return img;
    }

    /**
     * Load a Coverage Description XML object from a Reference
     *
     * @param identifier Name of coverage
     * @param desc Reference to a CoverageDescriptions xml
     * @return coverage description
     * @throws WCSTException
     */
    private CoverageDescriptionType loadDescriptionReference(String identifier, ReferenceType desc)
            throws WCSTException, WCSTException {
        URL url = null;
        String xmlString = null;
        CoverageDescriptions descs = null;
        CoverageDescriptionType desc0 = null;

        // Load the URL
        try {
            url = new URL(desc.getHref());
        } catch (MalformedURLException e) {
            log.error("URL " + url.toString() + " is not valid !");
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Reference pixels. Explanation: " + "URL " + url.toString() + " is not valid.");
        }

        // Read the contents of the URL
        try {
            URLConnection conn = url.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            xmlString = IOUtils.toString(in);
        } catch (IOException ex) {
            throw new WCSTException(ExceptionCode.IOConnectionError,
                    "Description Reference. Explanation: error loading the " + "coverage description from URL "
                            + url.toString(),
                    ex);
        }

        // Unmarshall the XML string into a Java Object
        try {
            JAXBContext jaxbCtx = JAXBContext.newInstance(CoverageDescriptions.class.getPackage().getName());
            Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
            Object obj = unmarshaller.unmarshal(new StringReader(xmlString));

            if (obj instanceof JAXBElement) {
                descs = (CoverageDescriptions) ((JAXBElement) obj).getValue();
            } else if (obj instanceof CoverageDescriptions) {
                descs = (CoverageDescriptions) obj;
            } else {
                log.error("Coverage description metadata is not a valid xml document.");
                throw new WCSTException(ExceptionCode.XmlNotValid,
                        "Coverage " + "description metadata is not a valid xml document.");
            }

        } catch (javax.xml.bind.JAXBException ex) {
            throw new WCSTException(ExceptionCode.XmlStructuresError,
                    "Could not marshall/unmarshall XML structures.", ex);
        }

        // Filter by coverage name
        desc0 = null;
        Iterator<CoverageDescriptionType> i = descs.getCoverageDescription().iterator();

        while (i.hasNext()) {
            CoverageDescriptionType d = i.next();

            if (d.getIdentifier().equals(identifier)) {
                desc0 = d;
                break;
            }
        }

        if (desc0 == null) {
            throw new WCSTException(ExceptionCode.InvalidRequest,
                    "Could not find a CoverageDescription for coverage: " + identifier);
        }

        return desc0;
    }

    private CoverageSummaryType loadSummaryReference(ReferenceType pixels) throws WCSTException {
        URL url = null;
        String xmlString = null;
        CoverageSummaryType xml = null;

        try {
            url = new URL(pixels.getHref());
        } catch (MalformedURLException e) {
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Reference summary. Explanation: URL is not valid.", e);
        }

        // Read the contents of the URL
        try {
            URLConnection conn = url.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            xmlString = IOUtils.toString(in);
        } catch (IOException ex) {
            throw new WCSTException(ExceptionCode.IOConnectionError, "Summary Reference. Explanation: "
                    + "Error loading the " + "coverage summary from URL " + url.toString(), ex);
        }

        // Unmarshall the XML string into a Java Object
        try {
            JAXBContext jaxbCtx = JAXBContext.newInstance(CoverageSummaryType.class.getPackage().getName());
            Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
            Object obj = unmarshaller.unmarshal(new StringReader(xmlString));

            if (obj instanceof JAXBElement) {
                xml = (CoverageSummaryType) ((JAXBElement) obj).getValue();
            } else if (obj instanceof CoverageSummaryType) {
                xml = (CoverageSummaryType) obj;
            } else {
                throw new WCSTException(ExceptionCode.XmlNotValid,
                        "Coverage Summary metadata is not a valid xml document.");
            }
        } catch (javax.xml.bind.JAXBException ex) {
            throw new WCSTException(ExceptionCode.XmlStructuresError,
                    "Could not marshall/unmarshall XML structures.", ex);
        }

        return xml;
    }

    /**
     * Updates the coverage metadata: textual descriptions. The title and the abstract
     * could be specified in multiple languages, but we only store english.
     *
     * @param meta Metadata object to be modified
     * @param summary summary object, that contains title, abstract and coverage keywords
     * @return modified metadata object
     */
    private Metadata updateMetadataWithSummary(Metadata meta, CoverageSummaryType summary) throws WCSTException {
        log.debug("Updating metadata with values from Coverage Summary...");

        String title = null, abstr = null, keywords = null;

        title = summary.getTitle();
        abstr = summary.getAbstract();
        List<net.opengis.ows.v_1_0_0.KeywordsType> keywordL = summary.getKeywords();
        List<String> kList = new ArrayList<String>();
        Iterator i = keywordL.iterator();

        while (i.hasNext()) {
            KeywordsType keywordInMultipleLangs = (KeywordsType) i.next();
            String keyword = filterAcceptedLanguage(keywordInMultipleLangs.getKeyword());

            kList.add(keyword);
        }
        keywords = SDU.string2str(kList);

        meta.setTitle(title);
        meta.setKeywords(keywords);
        meta.setAbstract(abstr);

        return meta.clone();
    }

    /**
     * Retrieve only the string in the accepted language from a multiple-language list
     *
     * @param list List of strings in several languages
     * @return String in the accepted language, or null if none is found
     */
    private String filterAcceptedLanguage(List<LanguageStringType> list) {
        String result = null;
        Iterator i = list.iterator();

        while (i.hasNext()) {
            LanguageStringType a = (LanguageStringType) i.next();

            if (a.getLang().equals(ConfigManager.WCST_LANGUAGE)) {
                result = a.getValue();
            }
        }

        return result;
    }

    /**
     * Partially updates a Rasdaman coverage with pixels from pixHref. Info about
     * the bounding box is available from descHref.
     *
     * @param identifier ID of the coverage to be updates
     * @param pixHref URL for the pixels data
     * @param descHref URL for the metadata
     */
    private void insertSomePixelsIntoRasdaman(String identifier, String pixHref, String descHref) {
        // TODO: Implement !
        log.error("Partial update is not yet implemented");
        throw new UnsupportedOperationException("Partial Rasdaman update is not yet implemented.");
    }

    /**
     * Inserts default metadata values for the given coverage.
     *
     * @param identifier ID of the coverage
     * @param img The image, fetched from external reference
     * @throws WCSTException on error
     */
    private Metadata createNewCoverageMetadata(String identifier, BufferedImage img)
            throws WCPSException, PetascopeException {
        Metadata m = null;
        log.debug("Creating metadata with default values...");

        // TODO: When we accept multi-band images, update nullDefault
        String nullDefault = "0";

        // Cell domains
        BigInteger lowX = new BigInteger("0");
        BigInteger highX = new BigInteger(String.valueOf(img.getHeight() - 1));
        BigInteger lowY = new BigInteger("0");
        BigInteger highY = new BigInteger(String.valueOf(img.getWidth() - 1));
        CellDomainElement cellX = new CellDomainElement(lowX, highX, AxisTypes.X_AXIS);
        CellDomainElement cellY = new CellDomainElement(lowY, highY, AxisTypes.Y_AXIS);
        List<CellDomainElement> cellList = new ArrayList<CellDomainElement>(2);
        cellList.add(cellX);
        cellList.add(cellY);

        // Domains
        Set<String> crsSet = new HashSet<String>(1);
        crsSet.add(CrsUtil.IMAGE_CRS);
        String str1 = null, str2 = null;
        /* Since we currently do not use the Domain sizes, we can set them to 0 and 1 */
        DomainElement domX = new DomainElement(AxisTypes.X_AXIS, AxisTypes.X_AXIS, 0.0, 1.0, str1, str2, crsSet,
                metaDb.getAxisNames(), null);
        DomainElement domY = new DomainElement(AxisTypes.Y_AXIS, AxisTypes.Y_AXIS, 0.0, 1.0, str1, str2, crsSet,
                metaDb.getAxisNames(), null);
        List<DomainElement> domList = new ArrayList<DomainElement>(2);
        domList.add(domX);
        domList.add(domY);

        // Ranges
        /* TODO: When multiple-field images are supported, update ranges */
        RangeElement range = new RangeElement("intensity", ConfigManager.WCST_DEFAULT_DATATYPE, null);
        List<RangeElement> rList = new ArrayList<RangeElement>(1);
        rList.add(range);

        // Interpolation methods: only the default
        String interpMeth = ConfigManager.WCST_DEFAULT_INTERPOLATION;
        String nullRes = ConfigManager.WCST_DEFAULT_NULL_RESISTANCE;
        InterpolationMethod interp = new InterpolationMethod(interpMeth, nullRes);
        Set<InterpolationMethod> interpList = new HashSet<InterpolationMethod>(1);
        interpList.add(interp);

        // Null sets
        /* TODO: update for multi-band images */
        String nullVal = "0";
        Set<String> nullSet = new HashSet<String>(1);
        nullSet.add(nullVal);

        // Descriptions
        String abstr = null;
        String title = "Coverage " + identifier;
        String type = "GridCoverage"; // FIXME
        String keywords = null;

        m = new Metadata(cellList, rList, nullSet, nullDefault, interpList, interp, identifier, type, domList, null,
                title, abstr, keywords);

        log.debug("Done creating default metadata");
        return m;
    }

    /**
     * Processes one <Coverage> node from the input XML
     *
     * @param elem the JAXB node equivalent to the <Coverage> node
     */
    private void processInputCoverageNode(CoverageType elem)
            throws WCSTException, WCPSException, PetascopeException {
        if (elem.getAction() == null) {
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Action. Explanation: " + "Every <Coverage> node must contain an <Action> child node.");
        }

        String action = elem.getAction().getValue();
        String identifier = null;
        List references = null;

        if (elem.getIdentifier() == null) {
            throw new InvalidParameterException("Identifier");
        }

        identifier = elem.getIdentifier().getValue();
        references = elem.getAbstractReferenceBase();

        if (action.equalsIgnoreCase("Add")) {
            actionAddCoverage(identifier, references);
        } else if (action.equalsIgnoreCase("UpdateMetadata")) {
            actionUpdateMetadata(identifier, references);
        } else if (action.equalsIgnoreCase("Delete")) {
            actionDeleteCoverage(identifier, references);
        } else if (action.equalsIgnoreCase("UpdateAll")) {
            actionUpdateAll(identifier, references);
        } else if (action.equalsIgnoreCase("UpdateDataPart")) {
            throw new WCSTException(ExceptionCode.OperationNotSupported,
                    "\"UpdateDataPart\" is not supported yet.");
            /* TODO: UpdateDataPart is not yet functional. The Rasdaman server
             * returns with an unexpected internal error (code: 10000) when
             * a partial update query is sent. */
            //         actionUpdateDataPart(identifier, references);
        }
    }

    /**
     * Performs the action "UpdateAll", as part of the Transaction operation
     *
     * @param identifier Name of coverage to update
     * @param references List of references with data for update
     */
    private void actionUpdateAll(String identifier, List references) throws WCSTException, PetascopeException {
        log.trace("Executing action Update All ...");
        actionUpdateDataPart(identifier, references);
        actionUpdateMetadata(identifier, references);
        log.trace("Finished action Update All!");
    }

    /**
     * Updates the Metadata DB with the information contained in the CoverageDescriptions XML object
     *
     * @param identifier ID of the coverage
     * @param desc object that contains the coverage description.
     */
    private Metadata updateMetadataWithDescription(Metadata meta, CoverageDescriptionType desc)
            throws WCPSException, WCSTException {
        log.debug("Updating metadata with values from CoverageDescription...");

        /* (B) Table ps_descriptions: Update coverage title, abstract, keywords */
        String title = desc.getTitle();
        String abstr = desc.getAbstract();
        String keywords = desc.getKeywords().toString();

        meta.setAbstract(abstr);
        meta.setKeywords(keywords);
        meta.setTitle(title);

        /* (C) Table ps_range: Update field name, types, and interpolation methods */

        if (desc.getRange() != null) {
            Set<InterpolationMethod> interpSet = new HashSet<InterpolationMethod>();
            RangeType range = desc.getRange();
            List<FieldType> fields = range.getField();
            log.debug("Updating range information...");
            Iterator<FieldType> i = fields.iterator();
            ArrayList<RangeElement> rangeList = new ArrayList<RangeElement>();

            while (i.hasNext()) {
                FieldType field = i.next();

                String name = field.getIdentifier();
                String datatype = field.getDefinition().getDataType().getValue();
                RangeElement fieldRange = new RangeElement(name, datatype, null); // FIXME uom = null
                rangeList.add(fieldRange);

                InterpolationMethods methods = field.getInterpolationMethods();
                String interpType = methods.getDefaultMethod().getValue();
                String nullResist = methods.getDefaultMethod().getNullResistance();
                InterpolationMethod interp = new InterpolationMethod(interpType, nullResist);
                interpSet.add(interp);

                Iterator<InterpolationMethodType> it = methods.getOtherMethod().iterator();
                while (it.hasNext()) {
                    InterpolationMethodType imt = it.next();
                    String type = imt.getValue();
                    String resis = imt.getNullResistance();
                    interp = new InterpolationMethod(type, resis);
                    interpSet.add(interp);
                }
            }
            meta.setRange(rangeList);
            meta.setInterpolationSet(interpSet);
        }

        /* (D) Table ps_coverage: Update default interpolation method and null resistance */

        /*
         *  We store interpolation methods at coverage level, not field level.
         * So we only look at the interpolation method list of the first field,
         * and use it on the whole coverage
         */
        if (desc.isSetRange()) {
            log.debug("Updating default interpolation method...");
            InterpolationMethodType def1 = desc.getRange().getField().get(0).getInterpolationMethods()
                    .getDefaultMethod();
            String method = def1.getValue();
            String resist = def1.getNullResistance();

            InterpolationMethod meth = new InterpolationMethod(method, resist);
            meta.setDefaultInterpolation(meth);
        }

        /* (E) Table ps_celldomain: Update cell domain of the coverage. */
        /* NOTE: Only works for 2-D (x/y) or 3-D (x/y/t) coverages */

        if (desc.isSetDomain()) {
            log.debug("Updating spatial bounding box of coverage ...");
            List<JAXBElement<? extends BoundingBoxType>> list = desc.getDomain().getSpatialDomain()
                    .getBoundingBox();
            if (list.size() == 1) {
                BoundingBoxType bbox = (BoundingBoxType) list.get(0).getValue();
                if (bbox.getCrs() == null || bbox.getCrs().equals(CrsUtil.IMAGE_CRS)) {
                    meta = updateImageCrsBoundingBox(meta, bbox);
                } else {
                    throw new WCSTException(ExceptionCode.InvalidParameterValue,
                            "crs. Explanation: Unknown CRS " + bbox.getCrs());
                }
            } else {
                Iterator i = list.iterator();
                while (i.hasNext()) {
                    BoundingBoxType bbox = (BoundingBoxType) i.next();
                    if (bbox.getCrs().equals(CrsUtil.IMAGE_CRS)) {
                        meta = updateImageCrsBoundingBox(meta, bbox);
                    }
                    // TODO: Implement WGS84 update
                    //                    if (bbox.getCrs().equals(DomainElement.WGS84_CRS))
                    //                        updateWgs84CrsBoundingBox(meta, bbox);
                }
            }
        }

        if (desc.getDomain().isSetTemporalDomain()) {
            log.debug("Updating temporal bounding box of coverage ...");
            /*
            try
            {
            List<Object> list =
            desc.getDomain().getTemporalDomain().getTimePositionOrTimePeriod();
            if (list.size() == 1)
            {
            Object obj = list.get(0);
            if (obj instanceof TimePeriodType)
            {
            TimePeriodType period = (TimePeriodType) obj;
            TimePositionType start = period.getBeginPosition();
            TimePositionType end = period.getEndPosition();
            }
            }
            else
            {
            throw new WCSTException("InvalidParameterValue", "TemporalDomain",
            "Exactly one time-period should be present in the " +
            "Temporal Domain of coverage: " + meta.getCoverageName());
            }
            }
            catch (InvalidMetadataException e)
            {
            throw new WCSTException("InvalidParameterValue", "Unknown, please look at the root cause exception.", e);
            }
             */
            log.error("This server did not implement the parsing of 'TimePeriod' nodes.");
            throw new WCSTException(ExceptionCode.NodeParsingNotImplemented,
                    "This server did not implement the parsing of 'TimePeriod' nodes.");
        }

        /* (F) Table ps_crss: Update supported CRS */
        // TODO later ... we don't support CRSs as of yet

        return meta;
    }

    /**
     * Performs the action "UpdateDataPart", as part of the Transaction operation
     *
     * @param identifier
     * @param references
     */
    private void actionUpdateDataPart(String identifier, List references) throws WCSTException, PetascopeException {
        log.trace("Executing action UpdateDataPart ...");

        // Error checking
        // Only change the metadata for an existing coverage
        Metadata m = metaDb.read(identifier);

        // Obtain the references
        ReferenceType pixels, desc;

        pixels = getPixelsRef(references);
        desc = getDescriptionRef(references);

        // References check. We are updating a coverage values, mandatory are: pixels, description
        if (pixels == null) {
            throw new WCSTException(ExceptionCode.MissingParameterValue,
                    "Reference role='" + getUrnCode("pixels") + "'");
        }
        if (desc == null) {
            throw new WCSTException(ExceptionCode.MissingParameterValue,
                    "Reference role='" + getUrnCode("description") + "'");
        }

        // (2) Do the actual processing
        try {
            insertSomePixelsIntoRasdaman(identifier, pixels.getHref(), desc.getHref());
        } catch (Exception e) {
            throw new WCSTException(ExceptionCode.NoApplicableCode, e.getMessage(), e);
        }
    }

    /**
     * Performs the action "UpdateMetadata", as part of the Transaction operation
     *
     * @param identifier
     * @param references
     * @throws wcs.server.core.WCSTException
     */
    private void actionUpdateMetadata(String identifier, List references) throws WCSTException {
        log.trace("Executing action Update Metadata...");

        // Only change the metadata for an existing coverage
        Metadata m = null;
        try {
            m = metaDb.read(identifier);
        } catch (Exception e) {
            throw new WCSTException(ExceptionCode.InternalComponentError,
                    "Invalid metadata for coverage: " + identifier, e);
        }

        // Obtain the references
        ReferenceType descRef, summRef;

        descRef = getDescriptionRef(references);
        summRef = getSummaryRef(references);

        // References check. We are updating metadata, mandatory is only the description
        if (descRef == null) {
            throw new WCSTException(ExceptionCode.MissingParameterValue,
                    "Reference role='" + getUrnCode("description") + "'");
        }

        log.trace("Loading reference: coverage description ...");
        CoverageDescriptionType desc = loadDescriptionReference(identifier, descRef);

        CoverageSummaryType summ = null;

        if (summRef != null) {
            log.trace("Loading reference: coverage summary ...");
            summ = loadSummaryReference(summRef);
        }

        log.trace("Done loading references !");

        // (2) Do the actual processing
        try {
            Metadata oldMeta = m;
            Metadata newMeta = updateMetadataWithDescription(oldMeta, desc);
            if (summ != null) {
                Metadata tempMeta = newMeta;
                newMeta = updateMetadataWithSummary(newMeta, summ);
            }

            metaDb.updateCoverageMetadata(m, false);
        } catch (Exception e) {
            throw new WCSTException(ExceptionCode.NoApplicableCode, "Error while updating metadata.", e);
        }

        log.trace("Finished action Update Metadata !");
    }

    /**
     * Performs the action "Add", as part of the Transaction operation
     *
     * @param identifier
     * @param references
     * @throws wcs.server.core.WCSTException
     */
    private void actionAddCoverage(String identifier, List references)
            throws WCSTException, WCPSException, PetascopeException {
        log.trace("Executing action AddCoverage ...");

        // Obtain the references
        ReferenceType pixelsRef, descRef, summRef;

        pixelsRef = getPixelsRef(references);
        descRef = getDescriptionRef(references);
        summRef = getSummaryRef(references);

        // References check. We are adding a coverage, mandatory are: pixels, description
        if (pixelsRef == null) {
            throw new WCSTException(ExceptionCode.MissingParameterValue,
                    "Reference role='" + getUrnCode("pixels") + "'");
        }
        if (descRef == null) {
            throw new WCSTException(ExceptionCode.MissingParameterValue,
                    "Reference role='" + getUrnCode("description") + "'");
        }

        log.trace("Loading reference: coverage pixels ...");
        BufferedImage img = loadPixelsReference(pixelsRef);

        log.trace("Loading reference: coverage description ...");
        CoverageDescriptionType desc = loadDescriptionReference(identifier, descRef);

        CoverageSummaryType summ = null;

        if (summRef != null) {
            log.trace("Loading reference: coverage summary ...");
            summ = loadSummaryReference(summRef);
        }

        log.trace("Done loading references !");

        /**
         * (1) Check coverage name
         */
        boolean changeId = false;

        if (newCoverages.contains(identifier)) {
            throw new WCSTException(ExceptionCode.InvalidParameterValue,
                    "Identifier: You cannot use the same identifier twice.");
        }

        if (metaDb.existsCoverageName(identifier)) {
            changeId = true;
            log.warn("Changing coverage identifier since coverage '" + identifier + "' already exists !");
        }

        // Generate new coverage name ?
        while (changeId) {
            identifier = "coverage_" + Integer.toString((new Random()).nextInt());
            changeId = metaDb.existsCoverageName(identifier);
        }

        /**
         * (2) Do the actual processing. Stores the image in rasdaman.
         */
        try {
            /* Currently we only support one-band (gray-scale) images. */
            if (img.getColorModel().getNumComponents() != 1) {
                throw new WCSTException(ExceptionCode.MultiBandImagesNotSupported,
                        "This server currently only supports one-band images (grayscale). " + "This coverage has "
                                + img.getColorModel().getNumComponents() + " bands.");
            }
            insertImageIntoRasdaman(identifier, img);
        } catch (Exception e) {
            throw new WCSTException(ExceptionCode.InternalComponentError,
                    "Error while inserting image in rasdaman.", e);
        }

        /**
         * (3) Build the metadata object and store it in the db.
         */
        Metadata m = createNewCoverageMetadata(identifier, img);
        m = updateMetadataWithDescription(m, desc);
        /* Top level descriptions overwrite other metadata sources */
        if (summ != null) {
            m = updateMetadataWithSummary(m, summ);
        }

        metaDb.insertNewCoverageMetadata(m, false);

        /**
         * (4) Indicate success: Add this ID to the output XML document
         */
        CodeType id = new CodeType();
        id.setValue(identifier);
        output.getIdentifier().add(id);
        log.trace("Finished action Add !");
    }

    /**
     * Performs the action "Delete", as part of the Transaction operation
     *
     * @param identifier
     * @param references
     * @throws wcs.server.core.WCSTException
     */
    private void actionDeleteCoverage(String identifier, List references) throws WCSTException {
        log.trace("Executing action Delete Coverage ...");

        if (metaDb.existsCoverageName(identifier) == false) {
            throw new WCSTException(ExceptionCode.InvalidParameterValue, "Identifier");
        }

        // (2) Do the actual processing
        try {
            Metadata m = metaDb.read(identifier);
            deleteCoverageFromRasdaman(identifier);
            metaDb.delete(m, false);
        } catch (Exception e) {
            throw new WCSTException(ExceptionCode.NoApplicableCode, "Error while deleting coverage.", e);
        }

        // Indicate success: Add this ID to the output XML document
        CodeType id = new CodeType();

        id.setValue(identifier);
        output.getIdentifier().add(id);

        log.trace("Finished action Delete !");
    }

    /**
     * Retrieve the full URN code of a string
     *
     * @param key Internal representation of a URN code
     * @return the URN code
     */
    private String getUrnCode(String key) throws WCSTException {
        if (key.equalsIgnoreCase("pixels")) {
            return "urn:ogc:def:role:WCS:1.1:Pixels";
        }
        if (key.equalsIgnoreCase("description")) {
            return "urn:ogc:def:role:WCS:1.1:CoverageDescription";
        }
        if (key.equalsIgnoreCase("summary")) {
            return "urn:ogc:def:role:WCS:1.1:CoverageSummary";
        }
        if (key.equalsIgnoreCase("transform")) {
            return "urn:ogc:def:role:WCS:1.1:GeoreferencingTransformation";
        }
        if (key.equalsIgnoreCase("other")) {
            return "urn:ogc:def:role:WCS:1.1:OtherSource";
        }

        throw new WCSTException(ExceptionCode.InternalComponentError, "Unknown URN key '" + key + "'");
    }

    /**
     * Get the Pixels Reference
     *
     * @param references List of References
     * @return the Pixels Reference
     */
    private ReferenceType getPixelsRef(List references) throws WCSTException {
        ReferenceType result = searchReferenceList("pixels", references);

        return result;
    }

    /**
     * Get the Coverage Description Reference
     *
     * @param references List of References
     * @return the Coverage Description Reference
     */
    private ReferenceType getDescriptionRef(List references) throws WCSTException {
        ReferenceType result = searchReferenceList("description", references);

        return result;
    }

    /**
     * Get the Coverage Summary Reference
     *
     * @param references List of References
     * @return the Coverage Summary Reference
     */
    private ReferenceType getSummaryRef(List references) throws WCSTException {
        ReferenceType result = searchReferenceList("summary", references);

        return result;
    }

    /**
     * Get the Georeferencing Transform Reference
     *
     * @param references List of References
     * @return the Georeferencing Transform Reference
     */
    private ReferenceType getTransformRef(List references) throws WCSTException {
        ReferenceType result = searchReferenceList("transform", references);

        return result;
    }

    /**
     * Get the "Other" Reference
     *
     * @param references List of References
     * @return the "Other" Reference
     */
    private ReferenceType getOtherRef(List references) throws WCSTException {
        ReferenceType result = searchReferenceList("other", references);

        return result;
    }

    /**
     * Search a references list for a particular term
     * @param key internal name of reference
     * @param references List of references
     * @return a Reference object
     */
    private ReferenceType searchReferenceList(String key, List references) throws WCSTException {
        String urn = getUrnCode(key);
        Iterator i = references.iterator();

        while (i.hasNext()) {
            Object obj = i.next();
            JAXBElement jelem = (JAXBElement) obj;
            ReferenceType ref = (ReferenceType) jelem.getValue();
            String role = ref.getRole();

            if (role.equalsIgnoreCase(urn)) {
                return ref;
            }
        }

        return null;
    }

    private Metadata updateImageCrsBoundingBox(Metadata meta, BoundingBoxType bbox) throws WCPSException {
        List<Double> lower = bbox.getLowerCorner();
        List<Double> upper = bbox.getUpperCorner();

        if (lower.size() != 2) {
            throw new InvalidParameterException("LowerCorner. Explanation: Should contain only two numbers.");
        }
        if (upper.size() != 2) {
            throw new InvalidParameterException("UpperCorder. Explanation: Should contain only two numbers.");
        }
        long loX = lower.get(0).longValue();
        long loY = lower.get(1).longValue();
        long hiX = upper.get(0).longValue();
        long hiY = upper.get(1).longValue();

        CellDomainElement cellX = new CellDomainElement(BigInteger.valueOf(loX), BigInteger.valueOf(hiX),
                AxisTypes.X_AXIS);
        CellDomainElement cellY = new CellDomainElement(BigInteger.valueOf(loY), BigInteger.valueOf(hiY),
                AxisTypes.Y_AXIS);

        List<CellDomainElement> list = new ArrayList<CellDomainElement>();
        list.add(cellX);
        list.add(cellY);

        meta.setCellDomain(list);
        return meta.clone();
    }
}