/* (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.web.catalogstresstool; import java.io.Serializable; import java.text.MessageFormat; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.wicket.Session; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.validation.validator.RangeValidator; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.Predicates; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WMSLayerInfo; import org.geoserver.catalog.WMSStoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.impl.CatalogImpl; import org.geoserver.catalog.impl.CoverageInfoImpl; import org.geoserver.catalog.impl.CoverageStoreInfoImpl; import org.geoserver.catalog.impl.DataStoreInfoImpl; import org.geoserver.catalog.impl.FeatureTypeInfoImpl; import org.geoserver.catalog.impl.LayerInfoImpl; import org.geoserver.catalog.impl.NamespaceInfoImpl; import org.geoserver.catalog.impl.WMSLayerInfoImpl; import org.geoserver.catalog.impl.WMSStoreInfoImpl; import org.geoserver.catalog.impl.WorkspaceInfoImpl; import org.geoserver.catalog.util.CloseableIterator; import org.geoserver.ows.util.OwsUtils; import org.geoserver.security.SecureCatalogImpl; import org.geoserver.web.GeoServerApplication; import org.geoserver.web.GeoServerSecuredPage; import org.geoserver.web.ToolPage; import org.opengis.filter.Filter; import com.google.common.base.Function; import com.google.common.base.Stopwatch; import com.google.common.base.Strings; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; @SuppressWarnings({ "rawtypes", "unchecked" }) public class CatalogStressTester extends GeoServerSecuredPage { DropDownChoice<Tuple> workspace; DropDownChoice<Tuple> store; DropDownChoice<Tuple> resourceAndLayer; TextField<Integer> duplicateCount; TextField<String> sufix; Label progress; AjaxButton startLink; private CheckBox recursive; /** * DropDown choice model object becuase dbconfig freaks out if using the CatalogInfo objects * directly * */ private static final class Tuple implements Serializable, Comparable<Tuple> { private static final long serialVersionUID = 1L; final String id, name; public Tuple(String id, String name) { this.id = id; this.name = name; } @Override public int compareTo(Tuple o) { return name.compareTo(o.name); } } private static class TupleChoiceRenderer extends ChoiceRenderer<Tuple> { private static final long serialVersionUID = 1L; @Override public Object getDisplayValue(Tuple object) { return object.name; } @Override public String getIdValue(Tuple object, int index) { return object.id; } } public CatalogStressTester() { super(); setDefaultModel(new Model()); Form form = new Form("form", new Model()); add(form); IModel<List<Tuple>> wsModel = new LoadableDetachableModel<List<Tuple>>() { private static final long serialVersionUID = 1L; @Override protected List<Tuple> load() { Catalog catalog = GeoServerApplication.get().getCatalog(); Filter filter = Predicates.acceptAll(); CloseableIterator<WorkspaceInfo> list = catalog.list(WorkspaceInfo.class, filter, null, 4000, null); List<Tuple> workspaces; try { workspaces = Lists.newArrayList(Iterators.transform(list, new Function<WorkspaceInfo, Tuple>() { @Override public Tuple apply(WorkspaceInfo input) { return new Tuple(input.getId(), input.getName()); } })); } finally { list.close(); } Collections.sort(workspaces); return workspaces; } }; workspace = new DropDownChoice<Tuple>("workspace", new Model<Tuple>(), wsModel, new TupleChoiceRenderer()); workspace.setNullValid(true); workspace.setOutputMarkupId(true); workspace.setRequired(true); form.add(workspace); workspace.add(new OnChangeAjaxBehavior() { private static final long serialVersionUID = -5613056077847641106L; @Override protected void onUpdate(AjaxRequestTarget target) { target.add(store); target.add(resourceAndLayer); } }); IModel<List<Tuple>> storesModel = new LoadableDetachableModel<List<Tuple>>() { private static final long serialVersionUID = 1L; @Override protected List<Tuple> load() { Catalog catalog = GeoServerApplication.get().getCatalog(); Tuple ws = workspace.getModelObject(); if (ws == null) { return Lists.newArrayList(); } Filter filter = Predicates.equal("workspace.id", ws.id); int limit = 100; CloseableIterator<StoreInfo> iter = catalog.list(StoreInfo.class, filter, null, limit, null); List<Tuple> stores; try { stores = Lists.newArrayList(Iterators.transform(iter, new Function<StoreInfo, Tuple>() { @Override public Tuple apply(StoreInfo input) { return new Tuple(input.getId(), input.getName()); } })); } finally { iter.close(); } Collections.sort(stores); return stores; } }; store = new DropDownChoice<Tuple>("store", new Model<Tuple>(), storesModel, new TupleChoiceRenderer()); store.setNullValid(true); store.setOutputMarkupId(true); store.add(new OnChangeAjaxBehavior() { private static final long serialVersionUID = -5333344688588590014L; @Override protected void onUpdate(AjaxRequestTarget target) { target.add(resourceAndLayer); } }); form.add(store); IModel<List<Tuple>> resourcesModel = new LoadableDetachableModel<List<Tuple>>() { private static final long serialVersionUID = 1L; @Override protected List<Tuple> load() { Catalog catalog = getCatalog(); Tuple storeInfo = store.getModelObject(); if (storeInfo == null) { return Lists.newArrayList(); } Integer limit = 100; Filter filter = Predicates.equal("store.id", storeInfo.id); CloseableIterator<ResourceInfo> iter = catalog.list(ResourceInfo.class, filter, null, limit, null); List<Tuple> resources; try { resources = Lists.newArrayList(Iterators.transform(iter, new Function<ResourceInfo, Tuple>() { @Override public Tuple apply(ResourceInfo input) { return new Tuple(input.getId(), input.getName()); } })); } finally { iter.close(); } Collections.sort(resources); return resources; } }; resourceAndLayer = new DropDownChoice<Tuple>("resourceAndLayer", new Model<Tuple>(), resourcesModel, new TupleChoiceRenderer()); resourceAndLayer.setNullValid(true); resourceAndLayer.setOutputMarkupId(true); form.add(resourceAndLayer); recursive = new CheckBox("recursive", new Model<Boolean>(Boolean.FALSE)); form.add(recursive); duplicateCount = new TextField<Integer>("duplicateCount", new Model<Integer>(100), Integer.class); duplicateCount.setRequired(true); duplicateCount.add(new RangeValidator<Integer>(1, 100000)); form.add(duplicateCount); sufix = new TextField<String>("sufix", new Model<String>("-copy-")); sufix.setRequired(true); form.add(sufix); progress = new Label("progress", new Model<String>("0/0")); progress.setOutputMarkupId(true); form.add(progress); form.add(new AjaxButton("cancel") { private static final long serialVersionUID = 5767430648099432407L; protected void onSubmit(AjaxRequestTarget target, Form<?> form) { setResponsePage(ToolPage.class); } }); startLink = new AjaxButton("submit", form) { private static final long serialVersionUID = -4087484089208211355L; @Override protected void onSubmit(AjaxRequestTarget target, Form<?> form) { progress.setDefaultModelObject(""); startLink.setVisible(false); target.add(startLink); target.add(progress); try { startCopy(target, form); } catch (Exception e) { form.error(e.getMessage()); target.add(form); } finally { startLink.setVisible(true); target.add(startLink); target.add(progress); } } }; form.add(startLink); startLink.setOutputMarkupId(true); } private void startCopy(AjaxRequestTarget target, Form<?> form) { Session.get().getFeedbackMessages().clear(); target.add(getFeedbackPanel()); final boolean recursive = this.recursive.getModelObject(); final int numCopies = duplicateCount.getModelObject(); final String s = sufix.getModelObject(); LayerInfo layer = null; CatalogInfo original; { Tuple modelObject = resourceAndLayer.getModelObject(); if (modelObject != null) { original = getCatalog().getResource(modelObject.id, ResourceInfo.class); List<LayerInfo> layers = getCatalog().getLayers((ResourceInfo) original); if (!layers.isEmpty()) { layer = layers.get(0); } } else { modelObject = store.getModelObject(); if (modelObject != null) { original = getCatalog().getStore(modelObject.id, StoreInfo.class); } else { modelObject = workspace.getModelObject(); if (modelObject != null) { original = getCatalog().getWorkspace(modelObject.id); } else { throw new IllegalStateException(); } } } } System.err.println("Creating " + numCopies + " copies of " + original + " with sufix " + s); final Catalog catalog = getCatalog(); final Class<? extends CatalogInfo> clazz = interfaceOf(original); Stopwatch globalTime = Stopwatch.createUnstarted(); Stopwatch sw = Stopwatch.createUnstarted(); sw.start(); final int padLength = (int) Math.ceil(Math.log10(numCopies)); for (int curr = 0; curr < numCopies; curr++) { String paddedIndex = Strings.padStart(String.valueOf(curr), padLength, '0'); String nameSuffix = s + paddedIndex; copyOne(catalog, original, (Class<CatalogInfo>) clazz, layer, nameSuffix, globalTime, recursive, null); if ((curr + 1) % 100 == 0) { sw.stop(); System.out.printf("inserted %s so far in %s (last 100 in %s)\n", (curr + 1), globalTime, sw); sw.reset(); sw.start(); } } String localizerString = this.getLocalizer().getString("CatalogStressTester.progressStatusMessage", this, "Inserted {0} copies of {1} in {2}"); String progressMessage = MessageFormat.format(localizerString, numCopies, original, globalTime); System.out.println(progressMessage); progress.setDefaultModelObject(progressMessage); target.add(progress); } private Class<? extends CatalogInfo> interfaceOf(CatalogInfo original) { Class<?>[] interfaces = { LayerGroupInfo.class, LayerInfo.class, NamespaceInfo.class, WorkspaceInfo.class, StyleInfo.class, CoverageStoreInfo.class, DataStoreInfo.class, WMSStoreInfo.class, CoverageInfo.class, FeatureTypeInfo.class, WMSLayerInfo.class }; for (Class c : interfaces) { if (c.isAssignableFrom(original.getClass())) { return c; } } throw new IllegalArgumentException(); } private void copyOne(Catalog catalog, final CatalogInfo original, final Class<CatalogInfo> clazz, final LayerInfo layer, final String nameSuffix, final Stopwatch sw, boolean recursive, CatalogInfo parent) { CatalogInfo prototype = prototype(original, catalog); try { OwsUtils.set(prototype, "id", null); OwsUtils.copy(clazz.cast(original), clazz.cast(prototype), clazz); final String newName = OwsUtils.get(prototype, "name") + nameSuffix; OwsUtils.set(prototype, "name", newName); if (prototype instanceof WorkspaceInfo) { sw.start(); catalog.add((WorkspaceInfo) prototype); sw.stop(); String originalWsName = ((WorkspaceInfo) original).getName(); NamespaceInfo ns = catalog.getNamespaceByPrefix(originalWsName); NamespaceInfoImpl ns2 = new NamespaceInfoImpl(); ns2.setPrefix(newName); ns2.setURI(ns.getURI() + newName); sw.start(); catalog.add(ns2); sw.stop(); if(recursive) { for (StoreInfo store : catalog.getStoresByWorkspace((WorkspaceInfo) original, StoreInfo.class)) { copyOne(catalog, store, (Class<CatalogInfo>) interfaceOf(store), (LayerInfo) null, nameSuffix, sw, true, prototype); } } } else if (prototype instanceof StoreInfo) { sw.start(); final StoreInfo ps = (StoreInfo) prototype; if(parent != null) { ps.setWorkspace((WorkspaceInfo) parent); } // reset the cache, or we might stumble into a error about too many connections // while cloning many jdbc stores catalog.getResourcePool().dispose(); catalog.add(ps); sw.stop(); if(recursive) { for (ResourceInfo resource : catalog.getResourcesByStore((StoreInfo) original, ResourceInfo.class)) { LayerInfo resourceLayer = catalog.getLayerByName(resource.prefixedName()); copyOne(catalog, resource, (Class<CatalogInfo>) interfaceOf(resource), resourceLayer, nameSuffix, sw, true, prototype); } } } else if (prototype instanceof ResourceInfo) { ((ResourceInfo) prototype).setNativeName(((ResourceInfo) original).getNativeName()); ((ResourceInfo) prototype).setName(newName); if(parent != null) { ((ResourceInfo) prototype).setStore((StoreInfo) parent); } sw.start(); catalog.add((ResourceInfo) prototype); sw.stop(); String id = prototype.getId(); prototype = catalog.getResource(id, ResourceInfo.class); if (layer == null) { return; } LayerInfoImpl layerCopy; { layerCopy = new LayerInfoImpl(); OwsUtils.copy(LayerInfo.class.cast(layer), layerCopy, LayerInfo.class); layerCopy.setResource((ResourceInfo) prototype); layerCopy.setId(null); } sw.start(); catalog.add(layerCopy); sw.stop(); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage(), e); } } private CatalogInfo prototype(CatalogInfo original, Catalog catalog) { CatalogInfo prototype; if (original instanceof WorkspaceInfo) { prototype = new WorkspaceInfoImpl(); } else if (original instanceof DataStoreInfo) { prototype = new DataStoreInfoImpl(catalog); } else if (original instanceof CoverageStoreInfo) { prototype = new CoverageStoreInfoImpl(catalog); } else if (original instanceof WMSStoreInfo) { prototype = new WMSStoreInfoImpl((CatalogImpl) SecureCatalogImpl.unwrap(catalog)); } else if (original instanceof FeatureTypeInfo) { prototype = new FeatureTypeInfoImpl(catalog); } else if (original instanceof CoverageInfo) { prototype = new CoverageInfoImpl(catalog); } else if (original instanceof WMSLayerInfo) { prototype = new WMSLayerInfoImpl((CatalogImpl) SecureCatalogImpl.unwrap(catalog)); } else { throw new IllegalArgumentException(original.toString()); } return prototype; } }