From 6458f1b3d7bbcbbafaf7f06865b423f9349a8578 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 19 Mar 2013 11:43:46 +0100 Subject: [PATCH] merge cache configuration from default location, config directory and plugins --- .../scm/cache/EhCacheConfigurationReader.java | 477 ++++++++++++++++++ .../java/sonia/scm/cache/EhCacheManager.java | 18 +- .../scm/cache/EhConfigurationReaderTest.java | 434 ++++++++++++++++ .../resources/sonia/scm/cache/cache.001.xml | 50 ++ .../resources/sonia/scm/cache/cache.002.xml | 44 ++ .../resources/sonia/scm/cache/cache.003.xml | 44 ++ .../resources/sonia/scm/cache/cache.004.xml | 44 ++ .../resources/sonia/scm/cache/cache.005.xml | 41 ++ 8 files changed, 1150 insertions(+), 2 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/cache/EhCacheConfigurationReader.java create mode 100644 scm-webapp/src/test/java/sonia/scm/cache/EhConfigurationReaderTest.java create mode 100644 scm-webapp/src/test/resources/sonia/scm/cache/cache.001.xml create mode 100644 scm-webapp/src/test/resources/sonia/scm/cache/cache.002.xml create mode 100644 scm-webapp/src/test/resources/sonia/scm/cache/cache.003.xml create mode 100644 scm-webapp/src/test/resources/sonia/scm/cache/cache.004.xml create mode 100644 scm-webapp/src/test/resources/sonia/scm/cache/cache.005.xml diff --git a/scm-webapp/src/main/java/sonia/scm/cache/EhCacheConfigurationReader.java b/scm-webapp/src/main/java/sonia/scm/cache/EhCacheConfigurationReader.java new file mode 100644 index 0000000000..66c5380ff3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/EhCacheConfigurationReader.java @@ -0,0 +1,477 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.cache; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.google.common.io.Closeables; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import sonia.scm.SCMContext; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import java.net.MalformedURLException; +import java.net.URL; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +/** + * + * @author Sebastian Sdorra + */ +public class EhCacheConfigurationReader +{ + + /** Field description */ + private static final String ATTRIBUTE_NAME = "name"; + + /** Field description */ + private static final String DEFAULT = "/config/ehcache.xml"; + + /** Field description */ + private static final String MANUAL_RESOURCE = + "ext".concat(File.separator).concat("cache.xml"); + + /** Field description */ + private static final String MODULE_RESOURCES = "/META-INF/scm/cache.xml"; + + /** + * the logger for EhCacheConfigurationReader + */ + private static final Logger logger = + LoggerFactory.getLogger(EhCacheConfigurationReader.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + public EhCacheConfigurationReader() + { + try + { + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch (ParserConfigurationException ex) + { + throw new RuntimeException("could not create document builder", ex); + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public InputStream read() + { + URL defaultConfig = getDefaultResource(); + + if (defaultConfig == null) + { + throw new IllegalStateException( + "could not find default cache configuration"); + } + + readConfiguration(defaultConfig, true); + + Iterator it = getModuleResources(); + + while (it.hasNext()) + { + readConfiguration(it.next(), false); + } + + File manualFile = getManualFileResource(); + + if (manualFile.exists()) + { + try + { + readConfiguration(manualFile.toURI().toURL(), false); + } + catch (MalformedURLException ex) + { + logger.error("malformed url", ex); + } + } + else + { + logger.warn("could not find manual configuration at {}", manualFile); + } + + Document doc = createMergedConfiguration(); + + return createInputStream(doc); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param path + * + * @return + */ + @VisibleForTesting + protected URL getDefaultResource() + { + return EhCacheConfigurationReader.class.getResource(DEFAULT); + } + + /** + * Method description + * + * + * @return + */ + @VisibleForTesting + protected File getManualFileResource() + { + return new File(SCMContext.getContext().getBaseDirectory(), + MANUAL_RESOURCE); + } + + /** + * Method description + * + * + * @param path + * + * @return + */ + @VisibleForTesting + protected Iterator getModuleResources() + { + Iterator it = null; + + try + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + if (classLoader == null) + { + classLoader = EhCacheConfigurationReader.class.getClassLoader(); + } + + Enumeration enm = classLoader.getResources(MODULE_RESOURCES); + + if (enm != null) + { + it = Iterators.forEnumeration(enm); + } + + } + catch (IOException ex) + { + logger.error("could not read cache module resources", ex); + } + + if (it == null) + { + it = Iterators.emptyIterator(); + } + + return it; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param doc + * + * @return + */ + private InputStream createInputStream(Document doc) + { + InputStream stream; + Transformer transformer; + + try + { + transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + transformer.transform(new DOMSource(doc), new StreamResult(baos)); + + if (logger.isTraceEnabled()) + { + logger.trace("effective ehcache configuration: {}", baos.toString()); + } + + stream = new ByteArrayInputStream(baos.toByteArray()); + } + catch (Exception ex) + { + throw new IllegalStateException("could not create transformer", ex); + } + + return stream; + } + + /** + * Method description + * + * + * @return + */ + private Document createMergedConfiguration() + { + Document merged = builder.newDocument(); + + Element rootEl = merged.createElementNS("http://ehcache.org/ehcache.xsd", + "ehcache"); + + for (Node node : map.values()) + { + Node mergedNode = merged.adoptNode(node); + + rootEl.appendChild(mergedNode); + } + + merged.appendChild(rootEl); + + return merged; + } + + /** + * Method description + * + * + * @param url + * @param throwException + */ + private void readConfiguration(URL url, boolean throwException) + { + logger.debug("read cache configuration from url {}", url.toExternalForm()); + + InputStream stream = null; + + try + { + stream = url.openStream(); + + Document document = builder.parse(stream); + Element rootEl = document.getDocumentElement(); + + readConfiguration(rootEl); + } + catch (Exception ex) + { + if (throwException) + { + throw new RuntimeException( + "could not read configuration at ".concat(url.toExternalForm()), ex); + } + else + { + logger.warn("could not read configuration at {}", url.toExternalForm()); + } + } + finally + { + Closeables.closeQuietly(stream); + } + } + + /** + * Method description + * + * + * @param rootEl + */ + private void readConfiguration(Element rootEl) + { + NodeList list = rootEl.getChildNodes(); + + for (int i = 0; i < list.getLength(); i++) + { + Node node = list.item(i); + + if (Node.ELEMENT_NODE == node.getNodeType()) + { + String element = node.getNodeName(); + String name = null; + Node nameNode = node.getAttributes().getNamedItem(ATTRIBUTE_NAME); + + if (nameNode != null) + { + name = nameNode.getNodeValue(); + } + + map.put(new Id(element, name), node); + } + } + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 13/03/19 + * @author Enter your name here... + */ + private static class Id + { + + /** + * Constructs ... + * + * + * @param element + * @param name + */ + public Id(String element, String name) + { + this.element = element; + this.name = name; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final Id other = (Id) obj; + + return Objects.equal(element, other.element) + && Objects.equal(name, other.name); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(element, name); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + + //J- + return Objects.toStringHelper(this) + .add("element", element) + .add("name", name) + .toString(); + //J+ + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private String element; + + /** Field description */ + private String name; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private DocumentBuilder builder; + + /** Field description */ + private Map map = Maps.newLinkedHashMap(); +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/EhCacheManager.java b/scm-webapp/src/main/java/sonia/scm/cache/EhCacheManager.java index 8228ef698e..70d068335d 100644 --- a/scm-webapp/src/main/java/sonia/scm/cache/EhCacheManager.java +++ b/scm-webapp/src/main/java/sonia/scm/cache/EhCacheManager.java @@ -35,6 +35,7 @@ package sonia.scm.cache; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.io.Closeables; import com.google.inject.Singleton; import org.slf4j.Logger; @@ -43,6 +44,7 @@ import org.slf4j.LoggerFactory; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.io.InputStream; /** * @@ -67,8 +69,20 @@ public class EhCacheManager implements CacheManager */ public EhCacheManager() { - cacheManager = - new net.sf.ehcache.CacheManager(EhCacheManager.class.getResource(CONFIG)); + + InputStream stream = null; + + try + { + EhCacheConfigurationReader reader = new EhCacheConfigurationReader(); + + stream = reader.read(); + cacheManager = new net.sf.ehcache.CacheManager(stream); + } + finally + { + Closeables.closeQuietly(stream); + } } /** diff --git a/scm-webapp/src/test/java/sonia/scm/cache/EhConfigurationReaderTest.java b/scm-webapp/src/test/java/sonia/scm/cache/EhConfigurationReaderTest.java new file mode 100644 index 0000000000..9a87361831 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/EhConfigurationReaderTest.java @@ -0,0 +1,434 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.cache; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Function; +import com.google.common.collect.Iterators; +import com.google.common.io.Closeables; +import com.google.common.io.Files; +import com.google.common.io.Resources; + +import net.sf.ehcache.config.CacheConfiguration; +import net.sf.ehcache.config.Configuration; +import net.sf.ehcache.config.ConfigurationFactory; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import java.net.URL; + +import java.util.Iterator; + +/** + * + * @author Sebastian Sdorra + */ +public class EhConfigurationReaderTest +{ + + /** + * Method description + * + */ + @Test + public void testDefaultConfiguration() + { + EhConfigurationTestReader reader = + new EhConfigurationTestReader("cache.001.xml"); + Configuration c = createConfiguration(reader); + + checkDefaultConfiguration(c); + checkCacheConfiguration(c, "sonia.test.cache.001", 1000l, 30l, 60l); + + } + + /** + * Method description + * + */ + @Test + public void testMergeAndOverride() + { + //J- + EhConfigurationTestReader reader = new EhConfigurationTestReader( + "cache.001.xml", + Iterators.forArray("cache.002.xml", "cache.003.xml"), + "cache.004.xml" + ); + //J+ + + Configuration c = createConfiguration(reader); + + // cache sonia.test.cache.001 override by cache.004.xml + checkCacheConfiguration(c, "sonia.test.cache.001", 6l, 2l, 8l); + checkCacheConfiguration(c, "sonia.test.cache.002", 2000l, 60l, 120l); + checkCacheConfiguration(c, "sonia.test.cache.003", 3000l, 120l, 2400l); + } + + /** + * Method description + * + */ + @Test + public void testMergeWithManualConfiguration() + { + EhConfigurationTestReader reader = + new EhConfigurationTestReader("cache.001.xml", null, "cache.002.xml"); + + Configuration c = createConfiguration(reader); + + checkDefaultConfiguration(c); + + checkCacheConfiguration(c, "sonia.test.cache.001", 1000l, 30l, 60l); + checkCacheConfiguration(c, "sonia.test.cache.002", 2000l, 60l, 120l); + } + + /** + * Method description + * + */ + @Test + public void testMergeWithModuleConfigurations() + { + EhConfigurationTestReader reader = + new EhConfigurationTestReader("cache.001.xml", + Iterators.forArray("cache.002.xml", "cache.003.xml")); + + Configuration c = createConfiguration(reader); + + checkDefaultConfiguration(c); + + checkCacheConfiguration(c, "sonia.test.cache.001", 1000l, 30l, 60l); + checkCacheConfiguration(c, "sonia.test.cache.002", 2000l, 60l, 120l); + checkCacheConfiguration(c, "sonia.test.cache.003", 3000l, 120l, 2400l); + } + + /** + * Method description + * + */ + @Test(expected = IllegalStateException.class) + public void testMissingDefaultConfiguration() + { + EhConfigurationTestReader reader = new EhConfigurationTestReader(); + + reader.read(); + } + + /** + * Method description + * + */ + @Test + public void testOverrideDefaultConfiguration() + { + //J- + EhConfigurationTestReader reader = new EhConfigurationTestReader( + "cache.001.xml", + Iterators.forArray("cache.005.xml") + ); + //J+ + Configuration c = createConfiguration(reader); + + checkDefaultConfiguration(c, 170l, 18900l); + } + + /** + * Method description + * + * + * @param c + * @param name + * @param maxEntriesLocalHeap + * @param timeToIdleSeconds + * @param timeToLiveSeconds + */ + private void checkCacheConfiguration(Configuration c, String name, + long maxEntriesLocalHeap, long timeToIdleSeconds, long timeToLiveSeconds) + { + CacheConfiguration cc = c.getCacheConfigurations().get(name); + + assertNotNull(cc); + assertEquals(maxEntriesLocalHeap, cc.getMaxEntriesLocalHeap()); + assertEquals(timeToIdleSeconds, cc.getTimeToIdleSeconds()); + assertEquals(timeToLiveSeconds, cc.getTimeToLiveSeconds()); + } + + /** + * Method description + * + * + * @param c + */ + private void checkDefaultConfiguration(Configuration c) + { + checkDefaultConfiguration(c, 100l, 10000l); + } + + /** + * Method description + * + * + * @param c + * @param maxEntriesLocalHeap + * @param maxEntriesLocalDisk + */ + private void checkDefaultConfiguration(Configuration c, + long maxEntriesLocalHeap, long maxEntriesLocalDisk) + { + CacheConfiguration dcc = c.getDefaultCacheConfiguration(); + + assertNotNull(dcc); + assertEquals(maxEntriesLocalHeap, dcc.getMaxEntriesLocalHeap()); + assertEquals(maxEntriesLocalDisk, dcc.getMaxEntriesLocalDisk()); + } + + /** + * Method description + * + * + * @param reader + * + * @return + */ + private Configuration createConfiguration(EhCacheConfigurationReader reader) + { + Configuration config = null; + InputStream content = null; + + try + { + content = reader.read(); + config = ConfigurationFactory.parseConfiguration(content); + } + finally + { + Closeables.closeQuietly(content); + } + + assertNotNull(config); + + return config; + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 13/03/19 + * @author Enter your name here... + */ + private class EhConfigurationTestReader extends EhCacheConfigurationReader + { + + /** + * Constructs ... + * + */ + public EhConfigurationTestReader() {} + + /** + * Constructs ... + * + * + * @param defaultConfiguration + */ + public EhConfigurationTestReader(String defaultConfiguration) + { + this.defaultConfiguration = defaultConfiguration; + } + + /** + * Constructs ... + * + * + * @param defaultConfiguration + * @param moduleConfigurations + */ + public EhConfigurationTestReader(String defaultConfiguration, + Iterator moduleConfigurations) + { + this.defaultConfiguration = defaultConfiguration; + this.moduleConfigurations = moduleConfigurations; + } + + /** + * Constructs ... + * + * + * @param defaultConfiguration + * @param moduleConfigurations + * @param manualConfiguration + */ + public EhConfigurationTestReader(String defaultConfiguration, + Iterator moduleConfigurations, String manualConfiguration) + { + this.defaultConfiguration = defaultConfiguration; + this.moduleConfigurations = moduleConfigurations; + this.manualConfiguration = manualConfiguration; + } + + //~--- get methods -------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public URL getDefaultResource() + { + URL url = null; + + if (defaultConfiguration != null) + { + url = getResource(defaultConfiguration); + } + + return url; + } + + /** + * Method description + * + * + * @return + */ + @Override + public Iterator getModuleResources() + { + Iterator urlIterator; + + if (moduleConfigurations == null) + { + urlIterator = Iterators.emptyIterator(); + } + else + { + urlIterator = Iterators.transform(moduleConfigurations, + new Function() + { + + @Override + public URL apply(String resource) + { + return getResource(resource); + } + }); + } + + return urlIterator; + } + + /** + * Method description + * + * + * @return + */ + @Override + protected File getManualFileResource() + { + File file; + + if (manualConfiguration == null) + { + file = mock(File.class); + when(file.exists()).thenReturn(Boolean.FALSE); + } + else + { + try + { + file = tempFolder.newFile(); + + URL manual = getResource(manualConfiguration); + + Files.copy(Resources.newInputStreamSupplier(manual), file); + } + catch (IOException ex) + { + throw new RuntimeException("could not create manual config file", ex); + } + } + + return file; + } + + /** + * Method description + * + * + * @param name + * + * @return + */ + private URL getResource(String name) + { + return Resources.getResource("sonia/scm/cache/".concat(name)); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private String defaultConfiguration; + + /** Field description */ + private String manualConfiguration; + + /** Field description */ + private Iterator moduleConfigurations; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); +} diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.001.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.001.xml new file mode 100644 index 0000000000..267866bc8d --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.001.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.002.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.002.xml new file mode 100644 index 0000000000..6abcd39ad1 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.002.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.003.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.003.xml new file mode 100644 index 0000000000..ee3f6e2ded --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.003.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.004.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.004.xml new file mode 100644 index 0000000000..5252b010bd --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.004.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.005.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.005.xml new file mode 100644 index 0000000000..445d66b591 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.005.xml @@ -0,0 +1,41 @@ + + + + + + + + +