diff --git a/src/main/java/it/geosolutions/geoserver/rest/GeoServerRESTPublisher.java b/src/main/java/it/geosolutions/geoserver/rest/GeoServerRESTPublisher.java index c68c5330..d77ac22d 100644 --- a/src/main/java/it/geosolutions/geoserver/rest/GeoServerRESTPublisher.java +++ b/src/main/java/it/geosolutions/geoserver/rest/GeoServerRESTPublisher.java @@ -3206,4 +3206,110 @@ public void postImport(int i) throws Exception { importerManager.postImport(i); } + /** + * Defines multiple modes of recalculating bounding boxes. + * + * @author Carl Schroedl - cschroedl@usgs.gov + */ + public enum BBoxRecalculationMode { + /** + * Recalculate the native bounding box, but do not recalculate the + * lat/long bounding box. + */ + NATIVE_BBOX("nativebbox"), + + /** + * Recalculate both the native bounding box and the lat/long bounding + * box. + */ + NATIVE_AND_LAT_LON_BBOX("nativebbox,latlonbbox"), + + /** + * Do not calculate any fields, regardless of the projection, projection + * policy, etc. This might be useful to avoid slow recalculation when + * operating against large datasets. + */ + NONE(""), + ; + + private final String paramValue; + + /** + * Associates the enum value with a URL query string parameter value + * @param paramValue + */ + BBoxRecalculationMode(String paramValue){ + this.paramValue = paramValue; + } + + /** + * Get the URL param value + * @return The query string parameter value + */ + public String getParamValue(){ + return paramValue; + } + } + + /** + * + * Recalculate the bounding box for a feature type or a coverage + * + * @param type + * @param xmlElementName - either featureType or coverage + * @param workspace + * @param storeName + * @param layerName + * @param calculationMode + * @param enabled + * @return true if recalculation succeeded, false otherwise. + */ + private boolean recalculateBBox(StoreType type, GSResourceEncoder renc, String workspace, String storeName, String layerName, BBoxRecalculationMode calculationMode){ + + String baseUrl = restURL + "/rest/workspaces/" + workspace + "/" + + type.getType().toLowerCase() +"s/" + storeName + "/" + + type.getTypeName().toLowerCase() + "/" + + layerName + "." + Format.XML.toString(); + + String sUrl = baseUrl + "?recalculate=" + calculationMode.getParamValue(); + LOGGER.debug("Constructed the following url for bounding box recalculation: " + sUrl); + + renc.recursivelyRemoveEmptyChildren(); + String body = renc.toString(); + String sendResult = HTTPUtils.putXml(sUrl, body, gsuser, gspass); + boolean success = sendResult != null; + return success; + } + + /** + * Recalculate a bounding box for a feature type + * @param workspace + * @param storeName + * @param layerName + * @param calculationMode + * @param enabled + * @return true if successful, false otherwise + */ + public boolean recalculateFeatureTypeBBox(String workspace, String storeName, String layerName, BBoxRecalculationMode calculationMode, boolean enabled){ + GSFeatureTypeEncoder fenc = new GSFeatureTypeEncoder(); + fenc.setName(layerName); + fenc.setEnabled(enabled); + return recalculateBBox(StoreType.DATASTORES, fenc, workspace, storeName, layerName, calculationMode); + } + + /** + * Recalculate a bounding box for a coverage + * @param workspace + * @param storeName + * @param layerName + * @param calculationMode + * @param enabled + * @return true if successful, false otherwise + */ + public boolean recalculateCoverageBBox(String workspace, String storeName, String layerName, BBoxRecalculationMode calculationMode, boolean enabled){ + GSCoverageEncoder cenc = new GSCoverageEncoder(); + cenc.setName(layerName); + cenc.setEnabled(enabled); + return recalculateBBox(StoreType.COVERAGESTORES, cenc, workspace, storeName, layerName, calculationMode); + } } diff --git a/src/main/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElement.java b/src/main/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElement.java index 63c6d91b..4e138761 100644 --- a/src/main/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElement.java +++ b/src/main/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElement.java @@ -26,6 +26,8 @@ package it.geosolutions.geoserver.rest.encoder.utils; +import java.util.ArrayList; +import java.util.List; import org.jdom.Content; import org.jdom.Element; import org.jdom.Text; @@ -122,6 +124,39 @@ public boolean remove(final String key){ return false; } + + /** + * Recursively removes all child nodes that have no contents. + */ + public void recursivelyRemoveEmptyChildren(){ + //must make a copy to avoid ConcurrentModificationException + List children = new ArrayList(this.root.getContent()); + + for(Content child : children){ + if(child instanceof Element){ + recursivelyRemoveEmptyChildren(this.root, (Element)child); + } + } + } + private void recursivelyRemoveEmptyChildren(Element grandparent, Element parent){ + //must make a copy to avoid ConcurrentModificationException + List childrenPreRemoval; + childrenPreRemoval = new ArrayList(parent.getContent()); + + //base case: the parent has no children + for(Content child : childrenPreRemoval){ + //recursive case: the parent has children + if(child instanceof Element){ + recursivelyRemoveEmptyChildren(parent, (Element)child); + } + } + + //if the parent node contains no children, remove it from the parent + List childrenPostRemoval = parent.getContent(); + if(childrenPostRemoval.isEmpty()){ + grandparent.removeContent(parent); + } + } /** * @return an xml String */ diff --git a/src/test/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElementTest.java b/src/test/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElementTest.java new file mode 100644 index 00000000..0c1e87d4 --- /dev/null +++ b/src/test/java/it/geosolutions/geoserver/rest/encoder/utils/XmlElementTest.java @@ -0,0 +1,134 @@ +/* + * GeoServer-Manager - Simple Manager Library for GeoServer + * + * Copyright (C) 2007,2011 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package it.geosolutions.geoserver.rest.encoder.utils; + +import java.io.IOException; +import java.io.StringReader; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Tests XmlElements + * @author Carl Schroedl - cschroedl@usgs.gov + */ +public class XmlElementTest { + + /** + * Creates an XmlElement from a String + * @param xmlString + * @return the specified String as an XmlElement + */ + private XmlElement makeElement(String xmlString){ + Document doc; + SAXBuilder builder = new SAXBuilder(); + try { + doc = builder.build(new StringReader(xmlString)); + } catch (JDOMException ex) { + throw new RuntimeException(ex); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + Element root = doc.getRootElement(); + + return new XmlElement(root); + } + + /** + * Asserts that the serializations of two XmlElements are the same. + * @param message + * @param expected + * @param actual + */ + private void assertEqualXml(String message, XmlElement expected, XmlElement actual){ + XMLOutputter out = new XMLOutputter(); + String expectedElementString = out.outputString(expected.getRoot()); + String actualElementString = out.outputString(actual.getRoot()); + assertEquals(message, expectedElementString, actualElementString); + } + + @Test + public void testRecursiveRemovalOnChildlessParent(){ + XmlElement root = makeElement(""); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("no child elements expected", makeElement(""), root); + } + + @Test + public void testRecursiveRemovalOfOneChild(){ + XmlElement root = makeElement(""); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("no child elements expected", makeElement(""), root); + } + + @Test + public void testRecursiveRemovalOfOneChildWithOneKeeper(){ + XmlElement root = makeElement("keep"); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("one child element expected", makeElement("keep"), root); + } + + @Test + public void testRecursiveRemovalOfOneParentAndOneChild() { + XmlElement root = makeElement(""); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("no child elements expected", makeElement(""), root); + } + + @Test + public void testRecursiveRemovalOfOneParentAndManyChildren() { + XmlElement root = makeElement(""); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("no child elements expected", makeElement(""), root); + } + + @Test + public void testRecursiveRemovalOfManyParentsWithOneChild() { + XmlElement root = makeElement(""); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("no child elements expected", makeElement(""), root); + } + + @Test + public void testRecursiveRemovalOfManyParentsAndManyChildren() { + XmlElement root = makeElement(""); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("no child elements expected", makeElement(""), root); + } + + @Test + public void testRecursiveRemovalOfManyParentsAndManyChildrenWithSomeKeepers() { + XmlElement root = makeElement("keepkeepkeepkeep"); + root.recursivelyRemoveEmptyChildren(); + assertEqualXml("only non-empty child elements should remain", makeElement("keepkeepkeepkeep"), root); + } + + +} \ No newline at end of file diff --git a/src/test/java/it/geosolutions/geoserver/rest/publisher/GeoserverRESTRecalculateTest.java b/src/test/java/it/geosolutions/geoserver/rest/publisher/GeoserverRESTRecalculateTest.java new file mode 100644 index 00000000..d7461c6b --- /dev/null +++ b/src/test/java/it/geosolutions/geoserver/rest/publisher/GeoserverRESTRecalculateTest.java @@ -0,0 +1,108 @@ +/* + * GeoServer-Manager - Simple Manager Library for GeoServer + * + * Copyright (C) 2007,2011 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package it.geosolutions.geoserver.rest.publisher; + +import it.geosolutions.geoserver.rest.GeoServerRESTPublisher; +import it.geosolutions.geoserver.rest.GeoserverRESTTest; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.junit.After; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +/** + * Test case for recalculating the bounding box for features and coverages on + * GeoServer. We need a running GeoServer to properly run the tests. + * If such GeoServer instance cannot be contacted, tests will be skipped. + * + * @author Carl Schroedl - cschroedl@usgs.gov + */ +public class GeoserverRESTRecalculateTest extends GeoserverRESTTest { + + private final static Logger LOGGER = LoggerFactory.getLogger(GeoserverRESTRecalculateTest.class); + + @Before + @Override + public void before(){ + super.before(); + + if (!enabled()) + return; + + deleteAll(); + assertTrue(publisher.createWorkspace(DEFAULT_WS)); + } + + @After + public void cleanUp(){ + if (!enabled()) + return; + + deleteAll(); + } + + @Test + public void testRecalculateFeatureTypeBBox() throws FileNotFoundException, IOException { + if (!enabled()) + return; + + String storeName = "resttestshp"; + String layerName = "cities"; + + File zipFile = new ClassPathResource("testdata/resttestshp.zip").getFile(); + assertTrue(publisher.publishShp(DEFAULT_WS, storeName, layerName, zipFile)); + + for(GeoServerRESTPublisher.BBoxRecalculationMode recalcMode : GeoServerRESTPublisher.BBoxRecalculationMode.values()){ + boolean recalculated = publisher.recalculateFeatureTypeBBox(DEFAULT_WS,storeName, layerName, recalcMode, true); + assertTrue("recalculateBBox failed with recalculation mode '" +recalcMode.toString() + "'.", recalculated); + } + } + + @Test + public void testRecalculateCoverageBBox() throws FileNotFoundException, IOException { + if (!enabled()) + return; + + String storeName = "testRESTStoreArcGrid"; + String layerName = "resttestdem"; + + File arcgrid = new ClassPathResource("testdata/resttestdem.asc").getFile(); + + assertTrue(publisher.publishArcGrid(DEFAULT_WS, storeName, layerName, arcgrid)); + + for(GeoServerRESTPublisher.BBoxRecalculationMode recalcMode : GeoServerRESTPublisher.BBoxRecalculationMode.values()){ + boolean recalculated = publisher.recalculateCoverageBBox(DEFAULT_WS,storeName, layerName, recalcMode, true); + assertTrue("recalculateBBox failed with recalculation mode '" +recalcMode.toString() + "'.", recalculated); + } + } +}