/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.rest.catalog; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.geoserver.data.test.SystemTestData; import org.geoserver.rest.RestBaseController; import org.geotools.util.logging.Logging; import org.junit.Test; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.web.servlet.DispatcherServlet; public class RestConcurrencyTest extends CatalogRESTTestSupport { static volatile Exception exception; volatile DispatcherServlet dispatcher; @Override protected void onSetUp(SystemTestData testData) throws Exception { // super.onSetUp(testData); exception = null; // Uncomment this and ... KA-BOOM!!!! // GeoServerConfigurationLock locker = (GeoServerConfigurationLock) applicationContext // .getBean("configurationLock"); // locker.setEnabled(false); } protected void addPropertyDataStores(int typeCount) throws Exception { ByteArrayOutputStream zbytes = new ByteArrayOutputStream(); ZipOutputStream zout = new ZipOutputStream(zbytes); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); for (int i = 0; i < typeCount; i++) { String name = "pds" + i; BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(bytes)); writer.write("_=name:String,pointProperty:Point\n"); writer.write(name + ".0='zero'|POINT(0 0)\n"); writer.write(name + ".1='one'|POINT(1 1)\n"); writer.flush(); zout.putNextEntry(new ZipEntry(name + ".properties")); zout.write(bytes.toByteArray()); bytes.reset(); } zout.flush(); zout.close(); put(RestBaseController.ROOT_PATH + "/workspaces/gs/datastores/pds/file.properties?configure=none", zbytes.toByteArray(), "application/zip"); } @Override protected DispatcherServlet getDispatcher() throws Exception { if(dispatcher == null) { synchronized (this) { if(dispatcher == null) { dispatcher = super.getDispatcher(); } } } return dispatcher; } @Test public void testFeatureTypeConcurrency() throws Exception { int typeCount = 5; addPropertyDataStores(typeCount); ExecutorService es = Executors.newCachedThreadPool(); try { List<Future<Integer>> futures = new ArrayList<>(); for (int i = 0; i < typeCount; i++) { futures.add(es.submit(new AddRemoveFeatureTypeWorker("gs", "pds", "pds" + i, 5))); } for (Future<Integer> future : futures) { future.get(); } } finally { es.shutdownNow(); } } class AddRemoveFeatureTypeWorker implements Callable<Integer> { final Logger LOGGER = Logging.getLogger(RestConcurrencyTest.class); String typeName; String workspace; String store; int loops; public AddRemoveFeatureTypeWorker(String workspace, String store, String typeName, int loops) { this.typeName = typeName; this.workspace = workspace; this.store = store; this.loops = loops; } @Override public Integer call() throws Exception { try { callInternal(); } catch (Exception e) { exception = e; throw e; } return loops; } private void callInternal() throws Exception { login(); String threadId = Thread.currentThread().getId() + " "; for (int i = 0; i < loops && exception == null; i++) { // add the type name String base = RestBaseController.ROOT_PATH + "/workspaces/" + workspace + "/datastores/" + store + "/featuretypes/"; String xml = "<featureType>" + "<name>" + typeName + "</name>" + "<nativeName>" + typeName + "</nativeName>" + "<srs>EPSG:4326</srs>" + "<nativeCRS>EPSG:4326</nativeCRS>" + "<nativeBoundingBox>" + "<minx>0.0</minx>" + "<maxx>1.0</maxx>" + "<miny>0.0</miny>" + "<maxy>1.0</maxy>" + "<crs>EPSG:4326</crs>" + "</nativeBoundingBox>" + "<store>" + store + "</store>" + "</featureType>"; LOGGER.info(threadId + "Adding " + typeName); MockHttpServletResponse response = postAsServletResponse(base, xml, "text/xml"); assertEquals(201, response.getStatus()); assertNotNull(response.getHeader("Location")); assertTrue(response.getHeader("Location").endsWith(base + typeName)); // check it's there LOGGER.info(threadId + "Checking " + typeName); String resourcePath = RestBaseController.ROOT_PATH + "/layers/" + workspace + ":" + typeName; response = getAsServletResponse(resourcePath + ".xml"); assertEquals(200, response.getStatus()); // reload LOGGER.info(threadId + "Reloading catalog"); assertEquals(200, postAsServletResponse(RestBaseController.ROOT_PATH + "/reload", "").getStatus()); // remove it LOGGER.info(threadId + "Removing layer"); String deletePath = resourcePath + "?recurse=true"; assertEquals(200, deleteAsServletResponse(deletePath).getStatus()); } } } }