diff options
Diffstat (limited to 'petascope/src/petascope/wcst/transaction/executeTransaction.java')
-rw-r--r-- | petascope/src/petascope/wcst/transaction/executeTransaction.java | 1152 |
1 files changed, 1152 insertions, 0 deletions
diff --git a/petascope/src/petascope/wcst/transaction/executeTransaction.java b/petascope/src/petascope/wcst/transaction/executeTransaction.java new file mode 100644 index 0000000..ed02780 --- /dev/null +++ b/petascope/src/petascope/wcst/transaction/executeTransaction.java @@ -0,0 +1,1152 @@ +/* + * This file is part of PetaScope. + * + * PetaScope 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 3 of + * the License, or (at your option) any later version. + * + * PetaScope 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 PetaScope. If not, see <http://www.gnu.org/licenses/>. + * + * For more information please see <http://www.PetaScope.org> + * or contact Peter Baumann via <baumann@rasdaman.com>. + * + * Copyright 2009 Jacobs University Bremen, Peter Baumann. + */ +package petascope.wcst.transaction; + +//~--- non-JDK imports -------------------------------------------------------- +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.wcps.server.core.SDU; + +//~--- JDK imports ------------------------------------------------------------ + +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.wcps.server.core.DbMetadataSource; +import petascope.wcps.server.core.Metadata; +import petascope.wcs.server.exceptions.WCSException; +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.exceptions.InvalidMetadataException; +import petascope.wcps.server.exceptions.InvalidWcpsRequestException; +import petascope.wcps.server.core.RangeElement; +import petascope.wcps.server.exceptions.ResourceException; +import petascope.wcs.server.exceptions.ActionNotSupportedException; +import petascope.wcs.server.exceptions.InputOutputException; +import petascope.wcs.server.exceptions.InternalComponentException; +import petascope.wcs.server.exceptions.InternalSqlException; +import petascope.wcs.server.exceptions.InvalidParameterValueException; +import petascope.wcs.server.exceptions.InvalidRequestException; +import petascope.wcs.server.exceptions.MissingParameterValueException; +import petascope.wcs.server.exceptions.MultiBandImagesNotSupportedException; +import petascope.wcs.server.exceptions.NoApplicableCodeException; +import petascope.wcs.server.exceptions.NodeParsingNotImplementedException; +import petascope.wcs.server.exceptions.XmlNotValidException; +import petascope.wcs.server.exceptions.XmlStructuresException; +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; + +/** + * 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 WCSException { + 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 WCSException + */ + public TransactionResponseType get() throws WCSException { + try { + if (finished == false) { + metaDb.ensureConnection(); + process(); + } + } catch (SQLException e) { + throw new InternalSqlException("Could not ensure connection to database is valid", e); + } + if (finished == false) { + throw new NoApplicableCodeException("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 WCSException { + if (!input.getService().equalsIgnoreCase("WCS")) { + throw new InvalidParameterValueException("Service. Explanation: Service must be \"WCS\" !"); + } + if (!input.getVersion().equalsIgnoreCase("1.1")) { + throw new InvalidParameterValueException("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 InternalComponentException("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 InternalSqlException("Could not commit metadata changes", e); + } + } catch (WCSException 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.appendErrorDetail(" 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.appendErrorDetail(" 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 InternalComponentException("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 WCSException { + 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 InternalComponentException("Could not insert image into Rasdaman.", e); + } + } + + /** Load a BufferedImage from a ReferenceType object. + * + * @param pixels Reference object + * @return available image + * @throws WCSException + */ + private BufferedImage loadPixelsReference(ReferenceType pixels) throws WCSException { + 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 InvalidParameterValueException("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 InputOutputException("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 WCSException + */ + private CoverageDescriptionType loadDescriptionReference(String identifier, + ReferenceType desc) throws WCSException { + 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 InvalidParameterValueException("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 InputOutputException("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 XmlNotValidException("Coverage " + + "description metadata is not a valid xml document."); + } + + } catch (javax.xml.bind.JAXBException ex) { + throw new XmlStructuresException("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 InvalidRequestException("Could not find a CoverageDescription for coverage: " + identifier); + } + + return desc0; + } + + private CoverageSummaryType loadSummaryReference(ReferenceType pixels) throws WCSException { + URL url = null; + String xmlString = null; + CoverageSummaryType xml = null; + + try { + url = new URL(pixels.getHref()); + } catch (MalformedURLException e) { + throw new InvalidParameterValueException("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 InputOutputException("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 XmlNotValidException("Coverage Summary metadata is not a valid xml document."); + } + } catch (javax.xml.bind.JAXBException ex) { + throw new XmlStructuresException("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 WCSException { + 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 WCSException on error + */ + private Metadata createNewCoverageMetadata(String identifier, BufferedImage img) + throws InvalidMetadataException { + 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); + CellDomainElement cellY = new CellDomainElement(lowY, highY); + List<CellDomainElement> cellList = new ArrayList<CellDomainElement>(2); + cellList.add(cellX); + cellList.add(cellY); + + // Domains + Set<String> crsSet = new HashSet<String>(1); + crsSet.add(DomainElement.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("x", "x", 0.0, 1.0, str1, str2, crsSet, metaDb.getAxisNames()); + DomainElement domY = new DomainElement("y", "y", 0.0, 1.0, str1, str2, crsSet, metaDb.getAxisNames()); + 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); + 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 keywords = null; + + + m = + new Metadata(cellList, rList, nullSet, nullDefault, interpList, + interp, identifier, 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 WCSException { + if (elem.getAction() == null) { + throw new InvalidParameterValueException("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 ActionNotSupportedException("Action \"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 WCSException { + 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 WCSException { + 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(); + try { + 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); + 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); + } catch (InvalidMetadataException e) { + throw new InternalComponentException("Invalid metadata.", e); + } + 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()) { + try { + 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); + } catch (InvalidMetadataException e) { + throw new InternalComponentException("Invalid metadata.", e); + } + } + + /* (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 ..."); + try { + 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(DomainElement.IMAGE_CRS)) { + meta = updateImageCrsBoundingBox(meta, bbox); + } else { + throw new InvalidParameterValueException("crs. Explanation: Unknown CRS " + bbox.getCrs()); + } + } else { + Iterator i = list.iterator(); + while (i.hasNext()) { + BoundingBoxType bbox = (BoundingBoxType) i.next(); + if (bbox.getCrs().equals(DomainElement.IMAGE_CRS)) { + meta = updateImageCrsBoundingBox(meta, bbox); + } + // TODO: Implement WGS84 update + // if (bbox.getCrs().equals(DomainElement.WGS84_CRS)) + // updateWgs84CrsBoundingBox(meta, bbox); + } + } + } catch (InvalidMetadataException e) { + throw new InternalComponentException("Invalid metadata.", e); + } + } + + 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 WCSException("InvalidParameterValue", "TemporalDomain", + "Exactly one time-period should be present in the " + + "Temporal Domain of coverage: " + meta.getCoverageName()); + } + } + catch (InvalidMetadataException e) + { + throw new WCSException("InvalidParameterValue", "Unknown, please look at the root cause exception.", e); + } + */ + LOG.error("This server did not implement the parsing of 'TimePeriod' nodes."); + throw new NodeParsingNotImplementedException("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 WCSException { + LOG.trace("Executing action UpdateDataPart ..."); + + // Error checking + // Only change the metadata for an existing coverage + Metadata m = null; + try { + m = metaDb.read(identifier); + } catch (ResourceException e) { + throw new InternalComponentException("Invalid metadata for coverage: " + identifier, e); + } catch (InvalidWcpsRequestException e) { + throw new InternalComponentException("Could not read metadata for coverage: " + identifier, e); + } + + // 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 MissingParameterValueException("Reference role='" + getUrnCode("pixels") + "'"); + } + if (desc == null) { + throw new MissingParameterValueException("Reference role='" + getUrnCode("description") + "'"); + } + + // (2) Do the actual processing + try { + insertSomePixelsIntoRasdaman(identifier, pixels.getHref(), desc.getHref()); + } catch (Exception e) { + throw new NoApplicableCodeException(e.getMessage(), e); + } + } + + /** + * Performs the action "UpdateMetadata", as part of the Transaction operation + * + * @param identifier + * @param references + * @throws wcs.server.core.WCSException + */ + private void actionUpdateMetadata(String identifier, List references) throws WCSException { + 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 InternalComponentException("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 MissingParameterValueException("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 NoApplicableCodeException("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.WCSException + */ + private void actionAddCoverage(String identifier, List references) throws WCSException { + 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 MissingParameterValueException("Reference role='" + getUrnCode("pixels") + "'"); + } + if (descRef == null) { + throw new MissingParameterValueException("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 InvalidParameterValueException("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 MultiBandImagesNotSupportedException( + "This server currently only supports one-band images (grayscale). " + + "This coverage has " + img.getColorModel().getNumComponents() + " bands."); + } + insertImageIntoRasdaman(identifier, img); + } catch (Exception e) { + throw new InternalComponentException("Error while inserting image in rasdaman.", e); + } + + /** + * (3) Build the metadata object and store it in the db. + */ + try { + 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); + } catch (InvalidMetadataException e) { + throw new InternalComponentException("Metadata to be inserted is invalid.", e); + } catch (ResourceException e) { + throw new InternalComponentException("Could not insert metadata into database.", e); + } + + /** + * (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.WCSException + */ + private void actionDeleteCoverage(String identifier, List references) throws WCSException { + LOG.trace("Executing action Delete Coverage ..."); + + if (metaDb.existsCoverageName(identifier) == false) { + throw new InvalidParameterValueException("Identifier"); + } + + // (2) Do the actual processing + try { + Metadata m = metaDb.read(identifier); + deleteCoverageFromRasdaman(identifier); + metaDb.delete(m, false); + } catch (Exception e) { + throw new NoApplicableCodeException("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 WCSException { + 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 InternalComponentException("Unknown URN key '" + key + "'"); + } + + /** + * Get the Pixels Reference + * + * @param references List of References + * @return the Pixels Reference + */ + private ReferenceType getPixelsRef(List references) throws WCSException { + 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 WCSException { + 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 WCSException { + 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 WCSException { + 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 WCSException { + 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 WCSException { + 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 WCSException, InvalidMetadataException { + 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)); + CellDomainElement cellY = new CellDomainElement(BigInteger.valueOf(loY), BigInteger.valueOf(hiY)); + + List<CellDomainElement> list = new ArrayList<CellDomainElement>(); + list.add(cellX); + list.add(cellY); + + meta.setCellDomain(list); + return meta.clone(); + } +} |