package org.geoserver.catalog.rest; 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.geotools.util.logging.Logging; import org.springframework.web.servlet.DispatcherServlet; import com.mockrunner.mock.web.MockHttpServletResponse; public class RestConcurrencyTest extends CatalogRESTTestSupport { static volatile Exception exception; volatile DispatcherServlet dispatcher; @Override protected void setUpInternal() throws Exception { super.setUpInternal(); 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("/rest/workspaces/gs/datastores/pds/file.properties?configure=none", zbytes.toByteArray(), "application/zip"); } @Override protected boolean useLegacyDataDirectory() { // if we don't do this the writes will be load and after the reload we won't get // the newly added layer return false; } @Override protected DispatcherServlet getDispatcher() throws Exception { if(dispatcher == null) { synchronized (this) { if(dispatcher == null) { dispatcher = super.getDispatcher(); } } } return dispatcher; } public void testFeatureTypeConcurrency() throws Exception { int typeCount = 5; addPropertyDataStores(typeCount); ExecutorService es = Executors.newCachedThreadPool(); try { List<Future<Integer>> futures = new ArrayList<Future<Integer>>(); 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 { String threadId = Thread.currentThread().getId() + " "; for (int i = 0; i < loops && exception == null; i++) { // add the type name String base = "/rest/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.getStatusCode()); assertNotNull(response.getHeader("Location")); assertTrue(response.getHeader("Location").endsWith(base + typeName)); // check it's there LOGGER.info(threadId + "Checking " + typeName); String resourcePath = "/rest/layers/" + workspace + ":" + typeName; response = getAsServletResponse(resourcePath + ".xml"); assertEquals(200, response.getStatusCode()); // reload LOGGER.info(threadId + "Reloading catalog"); assertEquals(200, postAsServletResponse("/rest/reload", "").getStatusCode()); // remove it LOGGER.info(threadId + "Removing layer"); String deletePath = resourcePath + "?recurse=true"; assertEquals(200, deleteAsServletResponse(deletePath).getStatusCode()); } } } }