diff --git a/scm-core/src/main/java/sonia/scm/io/DeepCopy.java b/scm-core/src/main/java/sonia/scm/io/DeepCopy.java new file mode 100644 index 0000000000..5f6eece040 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/io/DeepCopy.java @@ -0,0 +1,95 @@ +/** + * 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.io; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Utility for making deep copies (vs. clone()'s shallow copies) of + * objects. Objects are first serialized and then deserialized. Error + * checking is fairly minimal in this implementation. If an object is + * encountered that cannot be serialized (or that references an object + * that cannot be serialized) an error is printed to System.err and + * null is returned. Depending on your specific application, it might + * make more sense to have copy(...) re-throw the exception. + * + * @author Sebastian Sdorra + * @since 1.29 + * @see http://javatechniques.com/blog/faster-deep-copies-of-java-objects + */ +public final class DeepCopy +{ + + /** + * Returns a copy of the object, or null if the object cannot + * be serialized. + * + * @param orig + * @param + * + * @return + * + * @throws IOException + */ + public static T copy(T orig) throws IOException + { + T obj = null; + + try + { + + // Write the object out to a byte array + FastByteArrayOutputStream fbos = new FastByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(fbos); + + out.writeObject(orig); + out.flush(); + out.close(); + + // Retrieve an input stream from the byte array and read + // a copy of the object back in. + ObjectInputStream in = new ObjectInputStream(fbos.getInputStream()); + + obj = (T) in.readObject(); + } + catch (Exception ex) + { + throw new IOException("could not copy object", ex); + } + + return obj; + } +} diff --git a/scm-core/src/main/java/sonia/scm/io/FastByteArrayInputStream.java b/scm-core/src/main/java/sonia/scm/io/FastByteArrayInputStream.java new file mode 100644 index 0000000000..095291ac3b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/io/FastByteArrayInputStream.java @@ -0,0 +1,161 @@ +/** + * 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.io; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.InputStream; + +/** + * ByteArrayInputStream implementation that does not synchronize methods. + * + * @author Sebastian Sdorra + * @since 1.29 + * @see http://javatechniques.com/blog/faster-deep-copies-of-java-objects + */ +public final class FastByteArrayInputStream extends InputStream +{ + + /** + * Constructs ... + * + * + * @param buf + * @param count + */ + public FastByteArrayInputStream(byte[] buf, int count) + { + this.buf = buf; + this.count = count; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public final int available() + { + return count - pos; + } + + /** + * Method description + * + * + * @return + */ + @Override + public final int read() + { + return (pos < count) + ? (buf[pos++] & 0xff) + : -1; + } + + /** + * Method description + * + * + * @param b + * @param off + * @param len + * + * @return + */ + @Override + public final int read(byte[] b, int off, int len) + { + if (pos >= count) + { + return -1; + } + + if ((pos + len) > count) + { + len = (count - pos); + } + + System.arraycopy(buf, pos, b, off, len); + pos += len; + + return len; + } + + /** + * Method description + * + * + * @param n + * + * @return + */ + @Override + public final long skip(long n) + { + if ((pos + n) > count) + { + n = count - pos; + } + + if (n < 0) + { + return 0; + } + + pos += n; + + return n; + } + + //~--- fields --------------------------------------------------------------- + + /** + * Our byte buffer + */ + private byte[] buf = null; + + /** + * Number of bytes that we can read from the buffer + */ + private int count = 0; + + /** + * Number of bytes that have been read from the buffer + */ + private int pos = 0; +} diff --git a/scm-core/src/main/java/sonia/scm/io/FastByteArrayOutputStream.java b/scm-core/src/main/java/sonia/scm/io/FastByteArrayOutputStream.java new file mode 100644 index 0000000000..e130402d8e --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/io/FastByteArrayOutputStream.java @@ -0,0 +1,187 @@ +/** + * 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.io; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * ByteArrayOutputStream implementation that doesn't synchronize methods + * and doesn't copy the data on toByteArray(). + * + * @author Sebastian Sdorra + * @since 1.29 + * @see http://javatechniques.com/blog/faster-deep-copies-of-java-objects + */ +public final class FastByteArrayOutputStream extends OutputStream +{ + + /** + * Constructs a stream with buffer capacity size 5K + */ + public FastByteArrayOutputStream() + { + this(5 * 1024); + } + + /** + * Constructs a stream with the given initial size + * + * @param initSize + */ + public FastByteArrayOutputStream(int initSize) + { + this.size = 0; + this.buf = new byte[initSize]; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param b + */ + @Override + public final void write(byte b[]) + { + verifyBufferSize(size + b.length); + System.arraycopy(b, 0, buf, size, b.length); + size += b.length; + } + + /** + * Method description + * + * + * @param b + * @param off + * @param len + */ + @Override + public final void write(byte b[], int off, int len) + { + verifyBufferSize(size + len); + System.arraycopy(b, off, buf, size, len); + size += len; + } + + /** + * Method description + * + * + * @param b + */ + @Override + public final void write(int b) + { + verifyBufferSize(size + 1); + buf[size++] = (byte) b; + } + + /** + * Method description + * + */ + public void reset() + { + size = 0; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns the byte array containing the written data. Note that this + * array will almost always be larger than the amount of data actually + * written. + * + * @return + */ + public byte[] getByteArray() + { + return buf; + } + + /** + * Returns a ByteArrayInputStream for reading back the written data + * + * @return + */ + public InputStream getInputStream() + { + return new FastByteArrayInputStream(buf, size); + } + + /** + * Method description + * + * + * @return + */ + public int getSize() + { + return size; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Ensures that we have a large enough buffer for the given size. + * + * @param sz + */ + private void verifyBufferSize(int sz) + { + if (sz > buf.length) + { + byte[] old = buf; + + buf = new byte[Math.max(sz, 2 * buf.length)]; + System.arraycopy(old, 0, buf, 0, old.length); + old = null; + } + } + + //~--- fields --------------------------------------------------------------- + + /** + * Buffer and size + */ + private byte[] buf = null; + + /** Field description */ + private int size = 0; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/ChangesetPagingResult.java b/scm-core/src/main/java/sonia/scm/repository/ChangesetPagingResult.java index 0d2faad088..0712ed40af 100644 --- a/scm-core/src/main/java/sonia/scm/repository/ChangesetPagingResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/ChangesetPagingResult.java @@ -33,6 +33,10 @@ package sonia.scm.repository; +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + //~--- JDK imports ------------------------------------------------------------ import java.io.Serializable; @@ -47,7 +51,7 @@ import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; /** - * The changeset paging result is used to do a paging over the + * The changeset paging result is used to do a paging over the * {@link Changeset}s of a {@link Repository}. * * @author Sebastian Sdorra @@ -83,6 +87,45 @@ public class ChangesetPagingResult implements Iterable, Serializable //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final ChangesetPagingResult other = (ChangesetPagingResult) obj; + + return Objects.equal(changesets, other.changesets) + && Objects.equal(total, other.total); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(changesets, total); + } + /** * Returns an iterator which can iterate over the current list of changesets. * @@ -103,6 +146,23 @@ public class ChangesetPagingResult implements Iterable, Serializable return it; } + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("changesets", changesets) + .add("total", total) + .toString(); + //J+ + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommandRequest.java index feed50abda..21391e4b3a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommandRequest.java @@ -77,11 +77,14 @@ public final class LogCommandRequest implements Serializable, Resetable final LogCommandRequest other = (LogCommandRequest) obj; + //J- return Objects.equal(startChangeset, other.startChangeset) && Objects.equal(endChangeset, other.endChangeset) && Objects.equal(pagingStart, other.pagingStart) && Objects.equal(pagingLimit, other.pagingLimit) - && Objects.equal(path, other.path) && Objects.equal(branch, other.branch); + && Objects.equal(path, other.path) + && Objects.equal(branch, other.branch); + //J+ } /** diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java index 7277ad55ab..27accf504f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java @@ -212,9 +212,10 @@ public class GitChangesetConverter implements Closeable Collection tagCollection = tags.get(commit.getId()); - if (tagCollection != null) + if (Util.isNotEmpty(tagCollection)) { - changeset.getTags().addAll(tagCollection); + // create a copy of the tag collection to reduce memory on caching + changeset.getTags().addAll(Lists.newArrayList(tagCollection)); } changeset.setBranches(branches); diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index ec14dbe9c4..35cb5192e6 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -142,6 +142,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import sonia.scm.cache.GuavaCacheManager; /** * @@ -272,8 +273,8 @@ public class ScmServletModule extends ServletModule bind(WebSecurityContext.class).to(BasicSecurityContext.class); bind(AdministrationContext.class, DefaultAdministrationContext.class); - // bind security cache - bind(CacheManager.class, EhCacheManager.class); + // bind cache + bind(CacheManager.class, GuavaCacheManager.class); // bind dao bind(GroupDAO.class, XmlGroupDAO.class); diff --git a/scm-webapp/src/main/java/sonia/scm/cache/CacheConfigurations.java b/scm-webapp/src/main/java/sonia/scm/cache/CacheConfigurations.java new file mode 100644 index 0000000000..2ebf1bdb77 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/CacheConfigurations.java @@ -0,0 +1,116 @@ +/** + * 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.collect.Iterators; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.net.URL; + +import java.util.Enumeration; +import java.util.Iterator; + +/** + * + * @author Sebastian Sdorra + */ +public final class CacheConfigurations +{ + + /** + * the logger for CacheConfigurations + */ + private static final Logger logger = + LoggerFactory.getLogger(CacheConfigurations.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + private CacheConfigurations() {} + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param loadingClass + * @param resource + * + * @return + */ + public static Iterator findModuleResources(Class loadingClass, + String resource) + { + Iterator it = null; + + try + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + if (classLoader == null) + { + classLoader = loadingClass.getClassLoader(); + } + + Enumeration enm = classLoader.getResources(resource); + + if (enm != null) + { + it = Iterators.forEnumeration(enm); + } + + } + catch (IOException ex) + { + logger.error("could not read module resources", ex); + } + + if (it == null) + { + it = Iterators.emptyIterator(); + } + + return it; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/CacheException.java b/scm-webapp/src/main/java/sonia/scm/cache/CacheException.java new file mode 100644 index 0000000000..817ea81656 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/CacheException.java @@ -0,0 +1,85 @@ +/** + * 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; + +/** + * + * @author Sebastian Sdorra + */ +public class CacheException extends RuntimeException +{ + + /** Field description */ + private static final long serialVersionUID = -1108209749696572319L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + public CacheException() {} + + /** + * Constructs ... + * + * + * @param message + */ + public CacheException(String message) + { + super(message); + } + + /** + * Constructs ... + * + * + * @param cause + */ + public CacheException(Throwable cause) + { + super(cause); + } + + /** + * Constructs ... + * + * + * @param message + * @param cause + */ + public CacheException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/CopyStrategy.java b/scm-webapp/src/main/java/sonia/scm/cache/CopyStrategy.java new file mode 100644 index 0000000000..203f390944 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/CopyStrategy.java @@ -0,0 +1,147 @@ +/** + * 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 sonia.scm.io.DeepCopy; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.Locale; + +/** + * + * @author Sebastian Sdorra + */ +public enum CopyStrategy +{ + + NONE(false, false), READ(true, false), WRITE(false, true), + READWRITE(true, true); + + /** + * Constructs ... + * + * + * @param copyOnRead + * @param copyOnWrite + */ + private CopyStrategy(boolean copyOnRead, boolean copyOnWrite) + { + this.copyOnRead = copyOnRead; + this.copyOnWrite = copyOnWrite; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param value + * + * @return + */ + public static CopyStrategy fromString(String value) + { + return valueOf(value.toUpperCase(Locale.ENGLISH).replace("-", "")); + } + + /** + * Method description + * + * + * @param object + * @param + * + * @return + */ + public T copyOnRead(T object) + { + return copyOnRead + ? deepCopy(object) + : object; + } + + /** + * Method description + * + * + * @param object + * @param + * + * @return + */ + public T copyOnWrite(T object) + { + return copyOnWrite + ? deepCopy(object) + : object; + } + + /** + * Method description + * + * + * @param object + * @param + * + * @return + */ + private T deepCopy(T object) + { + T copy = null; + + try + { + copy = DeepCopy.copy(object); + } + catch (IOException ex) + { + throw new CacheException( + "could not create a copy of ".concat(object.toString()), ex); + } + + return copy; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private boolean copyOnRead; + + /** Field description */ + private boolean copyOnWrite; +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/EhCache.java b/scm-webapp/src/main/java/sonia/scm/cache/EhCache.java index 802db9d45c..4ac0e2a49e 100644 --- a/scm-webapp/src/main/java/sonia/scm/cache/EhCache.java +++ b/scm-webapp/src/main/java/sonia/scm/cache/EhCache.java @@ -35,6 +35,8 @@ package sonia.scm.cache; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; + import net.sf.ehcache.Element; import org.slf4j.Logger; @@ -186,6 +188,18 @@ public class EhCache implements Cache return value; } + /** + * Method description + * + * + * @return + */ + @VisibleForTesting + net.sf.ehcache.Cache getCacheImplementation() + { + return cache; + } + //~--- fields --------------------------------------------------------------- /** Field description */ 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..bf3051cb3f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/EhCacheConfigurationReader.java @@ -0,0 +1,475 @@ +/** + * 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.Maps; +import com.google.common.io.Closeables; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +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.InputStream; + +import java.net.MalformedURLException; +import java.net.URL; + +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("ehcache.xml"); + + /** Field description */ + private static final String MODULE_RESOURCES = "/META-INF/scm/ehcache.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() + { + return CacheConfigurations.findModuleResources( + EhCacheConfigurationReader.class, MODULE_RESOURCES); + } + + //~--- 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 (Attr attribute : attributeMap.values()) + { + Attr mergedAttr = (Attr) merged.adoptNode(attribute); + + rootEl.setAttributeNode(mergedAttr); + } + + for (Node node : nodeMap.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) + { + NamedNodeMap attributes = rootEl.getAttributes(); + + for (int i = 0; i < attributes.getLength(); i++) + { + Node node = attributes.item(i); + + if (Node.ATTRIBUTE_NODE == node.getNodeType()) + { + String name = node.getNodeName(); + + if (!name.startsWith("xmlns") && (node instanceof Attr)) + { + attributeMap.put(node.getNodeName(), (Attr) node); + } + } + } + + 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(); + } + + nodeMap.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 nodeMap = Maps.newLinkedHashMap(); + + /** Field description */ + private Map attributeMap = 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..0342ecf32b 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); + } } /** @@ -111,7 +125,8 @@ public class EhCacheManager implements CacheManager * @return */ @Override - public Cache getCache(Class key, Class value, String name) + public synchronized Cache getCache(Class key, Class value, + String name) { net.sf.ehcache.Cache c = cacheManager.getCache(name); diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java new file mode 100644 index 0000000000..10ba782bc2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java @@ -0,0 +1,236 @@ +/** + * 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.collect.Sets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.Filter; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Set; + +/** + * + * @author Sebastian Sdorra + * + * @param + * @param + */ +public class GuavaCache implements Cache +{ + + /** + * the logger for GuavaCache + */ + private static final Logger logger = + LoggerFactory.getLogger(GuavaCache.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param configuration + */ + public GuavaCache(GuavaNamedCacheConfiguration configuration) + { + this(configuration, configuration.getName()); + } + + /** + * Constructs ... + * + * + * @param configuration + * @param name + */ + public GuavaCache(GuavaCacheConfiguration configuration, String name) + { + this(GuavaCaches.create(configuration, name), + configuration.getCopyStrategy(), name); + } + + /** + * Constructs ... + * + * + * @param cache + * @param copyStrategy + * @param name + */ + @VisibleForTesting + protected GuavaCache(com.google.common.cache.Cache cache, + CopyStrategy copyStrategy, String name) + { + this.cache = cache; + this.name = name; + + if (copyStrategy != null) + { + this.copyStrategy = copyStrategy; + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Override + public void clear() + { + if (logger.isDebugEnabled()) + { + logger.debug("clear cache {}", name); + } + + cache.invalidateAll(); + } + + /** + * Method description + * + * + * @param key + * + * @return + */ + @Override + public boolean contains(K key) + { + return cache.getIfPresent(key) != null; + } + + /** + * Method description + * + * + * @param key + * @param value + */ + @Override + public void put(K key, V value) + { + cache.put(key, copyStrategy.copyOnWrite(value)); + } + + /** + * Method description + * + * + * @param key + * + * @return + */ + @Override + public boolean remove(K key) + { + cache.invalidate(key); + + return true; + } + + /** + * Method description + * + * + * @param filter + * + * @return + */ + @Override + public boolean removeAll(Filter filter) + { + Set keysToRemove = Sets.newHashSet(); + + for (K key : cache.asMap().keySet()) + { + if (filter.accept(key)) + { + keysToRemove.add(key); + } + } + + boolean result = false; + + if (!keysToRemove.isEmpty()) + { + cache.invalidateAll(keysToRemove); + result = true; + } + + return result; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param key + * + * @return + */ + @Override + public V get(K key) + { + V value = cache.getIfPresent(key); + + if (value != null) + { + value = copyStrategy.copyOnRead(value); + } + + return value; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private com.google.common.cache.Cache cache; + + /** Field description */ + private CopyStrategy copyStrategy = CopyStrategy.NONE; + + /** Field description */ + private String name; +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheConfiguration.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheConfiguration.java new file mode 100644 index 0000000000..d39fd4eab2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheConfiguration.java @@ -0,0 +1,223 @@ +/** + * 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; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + */ +@XmlRootElement(name = "cache") +@XmlAccessorType(XmlAccessType.FIELD) +public class GuavaCacheConfiguration implements Serializable +{ + + /** Field description */ + private static final long serialVersionUID = -8734373158089010603L; + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public Integer getConcurrencyLevel() + { + return concurrencyLevel; + } + + /** + * Method description + * + * + * @return + */ + public CopyStrategy getCopyStrategy() + { + return copyStrategy; + } + + /** + * Method description + * + * + * @return + */ + public Long getExpireAfterAccess() + { + return expireAfterAccess; + } + + /** + * Method description + * + * + * @return + */ + public Long getExpireAfterWrite() + { + return expireAfterWrite; + } + + /** + * Method description + * + * + * @return + */ + public Integer getInitialCapacity() + { + return initialCapacity; + } + + /** + * Method description + * + * + * @return + */ + public Long getMaximumSize() + { + return maximumSize; + } + + /** + * Method description + * + * + * @return + */ + public Long getMaximumWeight() + { + return maximumWeight; + } + + /** + * Method description + * + * + * @return + */ + public Boolean getRecordStats() + { + return recordStats; + } + + /** + * Method description + * + * + * @return + */ + public Boolean getSoftValues() + { + return softValues; + } + + /** + * Method description + * + * + * @return + */ + public Boolean getWeakKeys() + { + return weakKeys; + } + + /** + * Method description + * + * + * @return + */ + public Boolean getWeakValues() + { + return weakValues; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @XmlAttribute + private Integer concurrencyLevel; + + /** Field description */ + @XmlAttribute + private CopyStrategy copyStrategy; + + /** Field description */ + @XmlAttribute + private Long expireAfterAccess; + + /** Field description */ + @XmlAttribute + private Long expireAfterWrite; + + /** Field description */ + @XmlAttribute + private Integer initialCapacity; + + /** Field description */ + @XmlAttribute + private Long maximumSize; + + /** Field description */ + @XmlAttribute + private Long maximumWeight; + + /** Field description */ + @XmlAttribute + private Boolean recordStats; + + /** Field description */ + @XmlAttribute + private Boolean softValues; + + /** Field description */ + @XmlAttribute + private Boolean weakKeys; + + /** Field description */ + @XmlAttribute + private Boolean weakValues; +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheConfigurationReader.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheConfigurationReader.java new file mode 100644 index 0000000000..4c28f5ce21 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheConfigurationReader.java @@ -0,0 +1,308 @@ +/** + * 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.collect.ImmutableList; +import com.google.common.collect.Maps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.SCMContext; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; + +import java.net.MalformedURLException; +import java.net.URL; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; + +/** + * + * @author Sebastian Sdorra + */ +public class GuavaCacheConfigurationReader +{ + + /** Field description */ + private static final String DEFAULT = "/config/gcache.xml"; + + /** Field description */ + private static final String MANUAL_RESOURCE = + "ext".concat(File.separator).concat("gcache.xml"); + + /** Field description */ + private static final String MODULE_RESOURCES = "/META-INF/scm/gcache.xml"; + + /** + * the logger for CacheConfigurationReader + */ + private static final Logger logger = + LoggerFactory.getLogger(GuavaCacheConfigurationReader.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + private GuavaCacheConfigurationReader() + { + try + { + this.context = + JAXBContext.newInstance(GuavaCacheManagerConfiguration.class); + } + catch (JAXBException ex) + { + throw new CacheException( + "could not create jaxb context for cache configuration", ex); + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public static GuavaCacheManagerConfiguration read() + { + return new GuavaCacheConfigurationReader().doRead(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @VisibleForTesting + protected URL getDefaultResource() + { + return GuavaCacheConfigurationReader.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() + { + return CacheConfigurations.findModuleResources( + EhCacheConfigurationReader.class, MODULE_RESOURCES); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param caches + * + * @return + */ + private Map createNamedCacheMap( + List caches) + { + Map map = Maps.newLinkedHashMap(); + + for (GuavaNamedCacheConfiguration ncc : caches) + { + map.put(ncc.getName(), ncc); + } + + return map; + } + + /** + * Method description + * + * + * @return + */ + private GuavaCacheManagerConfiguration doRead() + { + URL defaultConfigUrl = getDefaultResource(); + + if (defaultConfigUrl == null) + { + throw new IllegalStateException( + "could not find default cache configuration"); + } + + GuavaCacheManagerConfiguration config = readConfiguration(defaultConfigUrl, + true); + + Iterator it = getModuleResources(); + + while (it.hasNext()) + { + GuavaCacheManagerConfiguration moduleConfig = + readConfiguration(it.next(), false); + + if (moduleConfig != null) + { + config = merge(config, moduleConfig); + } + } + + File manualFile = getManualFileResource(); + + if (manualFile.exists()) + { + try + { + GuavaCacheManagerConfiguration manualConfig = + readConfiguration(manualFile.toURI().toURL(), false); + + config = merge(config, manualConfig); + } + catch (MalformedURLException ex) + { + logger.error("malformed url", ex); + } + } + else + { + logger.warn("could not find manual configuration at {}", manualFile); + } + + return config; + } + + /** + * Method description + * + * + * @param config + * @param other + * + * @return + */ + private GuavaCacheManagerConfiguration merge( + GuavaCacheManagerConfiguration config, GuavaCacheManagerConfiguration other) + { + GuavaCacheConfiguration defaultCache = config.getDefaultCache(); + Map namedCaches = + createNamedCacheMap(config.getCaches()); + + if (other.getDefaultCache() != null) + { + defaultCache = other.getDefaultCache(); + } + + List otherNamedCaches = other.getCaches(); + + for (GuavaNamedCacheConfiguration ncc : otherNamedCaches) + { + namedCaches.put(ncc.getName(), ncc); + } + + return new GuavaCacheManagerConfiguration(defaultCache, + ImmutableList.copyOf(namedCaches.values())); + } + + /** + * Method description + * + * + * @param url + * @param fail + * + * @return + */ + private GuavaCacheManagerConfiguration readConfiguration(URL url, + boolean fail) + { + GuavaCacheManagerConfiguration config = null; + + try + { + config = + (GuavaCacheManagerConfiguration) context.createUnmarshaller().unmarshal( + url); + } + catch (JAXBException ex) + { + if (fail) + { + throw new CacheException("could not unmarshall cache configuration", + ex); + } + else + { + logger.warn("could not unmarshall cache configuration", ex); + } + } + + return config; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private JAXBContext context; +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java new file mode 100644 index 0000000000..c0a5a19914 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java @@ -0,0 +1,153 @@ +/** + * 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.collect.Maps; +import com.google.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.Map; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class GuavaCacheManager implements CacheManager +{ + + /** + * the logger for GuavaCacheManager + */ + private static final Logger logger = + LoggerFactory.getLogger(GuavaCacheManager.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + public GuavaCacheManager() + { + this(GuavaCacheConfigurationReader.read()); + } + + /** + * Constructs ... + * + * + * @param config + */ + @VisibleForTesting + protected GuavaCacheManager(GuavaCacheManagerConfiguration config) + { + defaultConfiguration = config.getDefaultCache(); + + for (GuavaNamedCacheConfiguration ncc : config.getCaches()) + { + cacheMap.put(ncc.getName(), new GuavaCache(ncc)); + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @throws IOException + */ + @Override + public void close() throws IOException + { + logger.info("close guava cache manager"); + + for (Cache c : cacheMap.values()) + { + c.clear(); + } + + cacheMap.clear(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param key + * @param value + * @param name + * @param + * @param + * + * @return + */ + @Override + public synchronized GuavaCache getCache(Class key, + Class value, String name) + { + logger.trace("try to retrieve cache {}", name); + + GuavaCache cache = cacheMap.get(name); + + if (cache == null) + { + logger.debug( + "cache {} does not exists, creating a new instance from default configuration: {}", + name, defaultConfiguration); + cache = new GuavaCache(defaultConfiguration, name); + cacheMap.put(name, cache); + } + + return cache; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private volatile Map cacheMap = Maps.newHashMap(); + + /** Field description */ + private GuavaCacheConfiguration defaultConfiguration; +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManagerConfiguration.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManagerConfiguration.java new file mode 100644 index 0000000000..cb9c306bdf --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManagerConfiguration.java @@ -0,0 +1,111 @@ +/** + * 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; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collections; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + */ +@XmlRootElement(name = "caches") +@XmlAccessorType(XmlAccessType.FIELD) +public class GuavaCacheManagerConfiguration +{ + + /** + * Constructs ... + * + */ + public GuavaCacheManagerConfiguration() {} + + /** + * Constructs ... + * + * + * @param defaultCache + * @param caches + */ + public GuavaCacheManagerConfiguration(GuavaCacheConfiguration defaultCache, + List caches) + { + this.defaultCache = defaultCache; + this.caches = caches; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public List getCaches() + { + if (caches == null) + { + caches = Collections.EMPTY_LIST; + } + + return caches; + } + + /** + * Method description + * + * + * @return + */ + public GuavaCacheConfiguration getDefaultCache() + { + return defaultCache; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @XmlElement(name = "cache") + private List caches; + + /** Field description */ + @XmlElement(name = "defaultCache") + private GuavaCacheConfiguration defaultCache; +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCaches.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCaches.java new file mode 100644 index 0000000000..42b36a6fd6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCaches.java @@ -0,0 +1,156 @@ +/** + * 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.cache.CacheBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.concurrent.TimeUnit; + +/** + * + * @author Sebastian Sdorra + */ +public final class GuavaCaches +{ + + /** + * the logger for GuavaCaches + */ + private static final Logger logger = + LoggerFactory.getLogger(GuavaCaches.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + private GuavaCaches() {} + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param configuration + * @param name + * + * @return + */ + public static com.google.common.cache.Cache create( + GuavaCacheConfiguration configuration, String name) + { + CacheBuilder builder = CacheBuilder.newBuilder(); + + if (configuration.getConcurrencyLevel() != null) + { + builder.concurrencyLevel(configuration.getConcurrencyLevel()); + } + + if (configuration.getExpireAfterAccess() != null) + { + builder.expireAfterAccess(configuration.getExpireAfterAccess(), + TimeUnit.SECONDS); + } + + if (configuration.getExpireAfterWrite() != null) + { + builder.expireAfterWrite(configuration.getExpireAfterWrite(), + TimeUnit.SECONDS); + } + + if (configuration.getInitialCapacity() != null) + { + builder.initialCapacity(configuration.getInitialCapacity()); + } + + if (configuration.getMaximumSize() != null) + { + builder.maximumSize(configuration.getMaximumSize()); + } + + if (configuration.getMaximumWeight() != null) + { + builder.maximumWeight(configuration.getMaximumWeight()); + } + + if (isEnabled(configuration.getRecordStats())) + { + builder.recordStats(); + } + + if (isEnabled(configuration.getSoftValues())) + { + builder.softValues(); + } + + if (isEnabled(configuration.getWeakKeys())) + { + builder.weakKeys(); + } + + if (isEnabled(configuration.getWeakValues())) + { + builder.weakKeys(); + } + + if (logger.isTraceEnabled()) + { + logger.trace("create new cache {} from builder: {}", name, builder); + } + + return builder.build(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param v + * + * @return + */ + private static boolean isEnabled(Boolean v) + { + return (v != null) && v; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaNamedCacheConfiguration.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaNamedCacheConfiguration.java new file mode 100644 index 0000000000..a8ae2ac8c5 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaNamedCacheConfiguration.java @@ -0,0 +1,71 @@ +/** + * 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; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + */ +@XmlRootElement(name = "cache") +@XmlAccessorType(XmlAccessType.FIELD) +public class GuavaNamedCacheConfiguration extends GuavaCacheConfiguration +{ + + /** Field description */ + private static final long serialVersionUID = -624795324874828475L; + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public String getName() + { + return name; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @XmlAttribute + private String name; +} diff --git a/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java b/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java index b06c11b69f..e3a0f25c5c 100644 --- a/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java +++ b/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java @@ -37,6 +37,7 @@ package sonia.scm.search; import com.google.common.base.Function; import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; @@ -139,7 +140,11 @@ public class SearchHandler Collections2.transform(users, function); result.setSuccess(true); - result.setResults(resultCollection); + + // create a copy of the result collection to reduce memory + // use ArrayList instead of ImmutableList for copy, + // because the list must be mutable for decorators + result.setResults(Lists.newArrayList(resultCollection)); cache.put(queryString, result); } } diff --git a/scm-webapp/src/main/resources/config/ehcache.xml b/scm-webapp/src/main/resources/config/ehcache.xml index 73ff38a498..446e35b505 100644 --- a/scm-webapp/src/main/resources/config/ehcache.xml +++ b/scm-webapp/src/main/resources/config/ehcache.xml @@ -40,7 +40,9 @@ Purpose of the document follows. --> - + + - + + + + + + + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - diff --git a/scm-webapp/src/main/resources/config/gcache.xml b/scm-webapp/src/main/resources/config/gcache.xml new file mode 100644 index 0000000000..7ff9a2053f --- /dev/null +++ b/scm-webapp/src/main/resources/config/gcache.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml index 10fd6951a6..280f08bf93 100644 --- a/scm-webapp/src/main/resources/logback.default.xml +++ b/scm-webapp/src/main/resources/logback.default.xml @@ -76,6 +76,11 @@ + + + diff --git a/scm-webapp/src/main/resources/logback.release.xml b/scm-webapp/src/main/resources/logback.release.xml index d1c7ab7624..1d7e15cad5 100644 --- a/scm-webapp/src/main/resources/logback.release.xml +++ b/scm-webapp/src/main/resources/logback.release.xml @@ -98,6 +98,11 @@ + + + diff --git a/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java b/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java new file mode 100644 index 0000000000..14d10eee20 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java @@ -0,0 +1,224 @@ +/** + * 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.collect.Sets; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * + * @author Sebastian Sdorra + * + * @param + */ +public abstract class CacheManagerTestBase +{ + + /** + * Method description + * + * + * @return + */ + protected abstract CacheManager createCacheManager(); + + /** + * Method description + * + * + * @throws IOException + */ + @After + public void tearDown() throws IOException + { + cacheManager.close(); + } + + /** + * Method description + * + */ + @Test + public void testSameReference() + { + Cache c1 = cacheManager.getCache(String.class, + String.class, "test-1"); + Cache c2 = cacheManager.getCache(String.class, + String.class, "test-1"); + + assertIsSame(c1, c2); + } + + /** + * Method description + * + * + * @throws ExecutionException + * @throws InterruptedException + */ + @Test + public void testSameReferenceMultiThreaded() + throws InterruptedException, ExecutionException + { + ExecutorService executor = Executors.newFixedThreadPool(2); + + Set> caches = Sets.newHashSet(); + + for (int i = 0; i < 20; i++) + { + //J- + caches.add( + executor.submit(new AcquireCacheCallable(cacheManager, "test-2")) + ); + //J+ + } + + executor.shutdown(); + + Cache c = null; + + for (Future f : caches) + { + Cache nc = f.get(); + + if (c != null) + { + assertIsSame(c, nc); + } + + c = nc; + } + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void setUp() + { + cacheManager = createCacheManager(); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param c1 + * @param c2 + */ + protected void assertIsSame(Cache c1, + Cache c2) + { + assertSame(c1, c2); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 13/03/25 + * @author Enter your name here... + */ + private static class AcquireCacheCallable implements Callable + { + + /** + * Constructs ... + * + * + * @param cacheManager + * @param name + */ + public AcquireCacheCallable(CacheManager cacheManager, String name) + { + this.cacheManager = cacheManager; + this.name = name; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @return + * + * @throws Exception + */ + @Override + public Cache call() throws Exception + { + return cacheManager.getCache(String.class, String.class, name); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private CacheManager cacheManager; + + /** Field description */ + private String name; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private CacheManager cacheManager; +} diff --git a/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java b/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java new file mode 100644 index 0000000000..17a3d8ad84 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java @@ -0,0 +1,165 @@ +/** + * 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 org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import sonia.scm.Filter; +import sonia.scm.util.IOUtil; + +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class CacheTestBase +{ + + /** + * Method description + * + * + * @return + */ + protected abstract CacheManager createCacheManager(); + + /** + * Method description + * + */ + @After + public void after() + { + IOUtil.close(cm); + } + + /** + * Method description + * + */ + @Before + public void before() + { + cm = createCacheManager(); + cache = cm.getCache(String.class, String.class, "test"); + } + + /** + * Method description + * + */ + @Test + public void testClear() + { + cache.put("test", "test123"); + cache.put("test-1", "test123"); + cache.clear(); + assertNull(cache.get("test")); + assertNull(cache.get("test-1")); + } + + /** + * Method description + * + */ + @Test + public void testContains() + { + cache.put("test", "test123"); + cache.put("test-1", "test123"); + assertTrue(cache.contains("test")); + assertTrue(cache.contains("test-1")); + assertFalse(cache.contains("test-2")); + } + + /** + * Method description + * + */ + @Test + public void testPutAndGet() + { + cache.put("test", "test123"); + assertEquals("test123", cache.get("test")); + } + + /** + * Method description + * + */ + @Test + public void testRemove() + { + cache.put("test", "test123"); + assertEquals("test123", cache.get("test")); + cache.remove("test"); + assertNull(cache.get("test")); + } + + /** + * Method description + * + */ + @Test + public void testRemoveAll() + { + cache.put("test-1", "test123"); + cache.put("test-2", "test123"); + cache.put("a-1", "test123"); + cache.put("a-2", "test123"); + cache.removeAll(new Filter() + { + @Override + public boolean accept(String item) + { + return item.startsWith("test"); + } + }); + assertNull(cache.get("test-1")); + assertNull(cache.get("test-2")); + assertNotNull(cache.get("a-1")); + assertNotNull(cache.get("a-2")); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private Cache cache; + + /** Field description */ + private CacheManager cm; +} diff --git a/scm-webapp/src/test/java/sonia/scm/cache/CacheTestUtil.java b/scm-webapp/src/test/java/sonia/scm/cache/CacheTestUtil.java new file mode 100644 index 0000000000..62f285d50b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/CacheTestUtil.java @@ -0,0 +1,77 @@ +/** + * 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; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collections; + +/** + * + * @author Sebastian Sdorra + */ +public final class CacheTestUtil +{ + + /** + * Constructs ... + * + */ + private CacheTestUtil() {} + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public static EhCacheManager createDefaultEhCacheManager() + { + return new EhCacheManager(net.sf.ehcache.CacheManager.create()); + } + + /** + * Method description + * + * + * @return + */ + public static GuavaCacheManager createDefaultGuavaCacheManager() + { + GuavaCacheConfiguration config = new GuavaCacheConfiguration(); + + return new GuavaCacheManager(new GuavaCacheManagerConfiguration(config, + Collections.EMPTY_LIST)); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/cache/EhCacheManagerTest.java b/scm-webapp/src/test/java/sonia/scm/cache/EhCacheManagerTest.java new file mode 100644 index 0000000000..899de9839b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/EhCacheManagerTest.java @@ -0,0 +1,84 @@ +/** + * 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 org.junit.Assert; + +/** + * + * @author Sebastian Sdorra + */ +public class EhCacheManagerTest extends CacheManagerTestBase +{ + + /** + * Method description + * + * + * @param c1 + * @param c2 + */ + @Override + protected void assertIsSame(Cache c1, Cache c2) + { + Assert.assertSame(getCacheImplementation(c1), getCacheImplementation(c1)); + } + + /** + * Method description + * + * + * @return + */ + @Override + protected CacheManager createCacheManager() + { + return CacheTestUtil.createDefaultEhCacheManager(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param c1 + * + * @return + */ + private net.sf.ehcache.Cache getCacheImplementation(Cache c1) + { + return ((EhCache) c1).getCacheImplementation(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/cache/EhCacheTest.java b/scm-webapp/src/test/java/sonia/scm/cache/EhCacheTest.java index 54bb5b9b1a..4dc349a3d7 100644 --- a/scm-webapp/src/test/java/sonia/scm/cache/EhCacheTest.java +++ b/scm-webapp/src/test/java/sonia/scm/cache/EhCacheTest.java @@ -33,127 +33,22 @@ package sonia.scm.cache; -//~--- non-JDK imports -------------------------------------------------------- - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import sonia.scm.Filter; -import sonia.scm.util.IOUtil; - -import static org.junit.Assert.*; - /** * * @author Sebastian Sdorra */ -public class EhCacheTest +public class EhCacheTest extends CacheTestBase { - - /** - * Method description - * - */ - @After - public void after() - { - IOUtil.close(cm); - } /** * Method description * - */ - @Before - public void before() - { - cm = new EhCacheManager(net.sf.ehcache.CacheManager.create()); - cache = cm.getCache(String.class, String.class, "test"); - } - - /** - * Method description * + * @return */ - @Test - public void testClear() + @Override + protected CacheManager createCacheManager() { - cache.put("test", "test123"); - cache.put("test-1", "test123"); - cache.clear(); - assertNull(cache.get("test")); - assertNull(cache.get("test-1")); + return CacheTestUtil.createDefaultEhCacheManager(); } - - /** - * Method description - * - */ - @Test - public void testContains() - { - cache.put("test", "test123"); - cache.put("test-1", "test123"); - assertTrue(cache.contains("test")); - assertTrue(cache.contains("test-1")); - assertFalse(cache.contains("test-2")); - } - - /** - * Method description - * - */ - @Test - public void testPutAndGet() - { - cache.put("test", "test123"); - assertEquals("test123", cache.get("test")); - } - - /** - * Method description - * - */ - @Test - public void testRemove() - { - cache.put("test", "test123"); - assertEquals("test123", cache.get("test")); - cache.remove("test"); - assertNull(cache.get("test")); - } - - /** - * Method description - * - */ - @Test - public void testRemoveAll() - { - cache.put("test-1", "test123"); - cache.put("test-2", "test123"); - cache.put("a-1", "test123"); - cache.put("a-2", "test123"); - cache.removeAll(new Filter() - { - @Override - public boolean accept(String item) - { - return item.startsWith("test"); - } - }); - assertNull(cache.get("test-1")); - assertNull(cache.get("test-2")); - assertNotNull(cache.get("a-1")); - assertNotNull(cache.get("a-2")); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Cache cache; - - /** Field description */ - private CacheManager cm; } 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..c501d21076 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/EhConfigurationReaderTest.java @@ -0,0 +1,464 @@ +/** + * 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 testGlobalAttributes() + { + EhConfigurationTestReader reader = + new EhConfigurationTestReader("cache.006.xml"); + Configuration c = createConfiguration(reader); + + assertFalse(c.getUpdateCheck()); + assertEquals("512M", c.getMaxBytesLocalDiskAsString()); + } + + /** + * 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 + * + */ + @Test + public void testOverrideGlobalAttributes() + { + EhConfigurationTestReader reader = + new EhConfigurationTestReader("cache.006.xml", null, "cache.007.xml"); + Configuration c = createConfiguration(reader); + + assertTrue(c.getUpdateCheck()); + assertEquals("1G", c.getMaxBytesLocalDiskAsString()); + } + + /** + * 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/java/sonia/scm/cache/GuavaCacheManagerTest.java b/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheManagerTest.java new file mode 100644 index 0000000000..6a9380810c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheManagerTest.java @@ -0,0 +1,52 @@ +/** + * 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; + +/** + * + * @author Sebastian Sdorra + */ +public class GuavaCacheManagerTest extends CacheManagerTestBase +{ + + /** + * Method description + * + * + * @return + */ + @Override + protected CacheManager createCacheManager() + { + return CacheTestUtil.createDefaultGuavaCacheManager(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheTest.java b/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheTest.java new file mode 100644 index 0000000000..9be9b5c2d6 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheTest.java @@ -0,0 +1,54 @@ +/** + * 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; + +//~--- JDK imports ------------------------------------------------------------ + +/** + * + * @author Sebastian Sdorra + */ +public class GuavaCacheTest extends CacheTestBase +{ + + /** + * Method description + * + * + * @return + */ + @Override + protected CacheManager createCacheManager() + { + return CacheTestUtil.createDefaultGuavaCacheManager(); + } +} 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 @@ + + + + + + + + + diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.006.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.006.xml new file mode 100644 index 0000000000..fab44312be --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.006.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/scm-webapp/src/test/resources/sonia/scm/cache/cache.007.xml b/scm-webapp/src/test/resources/sonia/scm/cache/cache.007.xml new file mode 100644 index 0000000000..cc24dd3ca9 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/cache/cache.007.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/support/scm-script-plugin/analyze-ehcache.groovy b/support/scm-script-plugin/analyze-ehcache.groovy new file mode 100644 index 0000000000..dd26cc423c --- /dev/null +++ b/support/scm-script-plugin/analyze-ehcache.groovy @@ -0,0 +1,86 @@ +/** + * 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 + * + */ + +// see https://bitbucket.org/sdorra/scm-manager/issue/345/ehcache-eating-up-all-memory + +def humanReadable(def bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + String pre = "KMGTPE".charAt(exp-1); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre); +} + +def sizeOf(Serializable object){ + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(object); + oos.close(); + return baos.size(); +} + +Runtime runtime = Runtime.getRuntime(); +def maxMemory = runtime.maxMemory(); +def allocatedMemory = runtime.totalMemory(); +def freeMemory = runtime.freeMemory(); + +println "memory:" +println " - max : " + humanReadable(maxMemory); +println " - allocated : " + humanReadable(allocatedMemory); +println " - free : " + humanReadable(freeMemory); + +println ""; +println ""; + +def cacheManager = injector.getInstance(sonia.scm.cache.CacheManager.class).cacheManager; + +def totalCMax = 0; + +def cacheNames = cacheManager.getCacheNames(); +for ( def cacheName : cacheNames ){ + def cache = cacheManager.getCache(cacheName); + def totalMemory = cache.calculateInMemorySize(); + def average = cache.getSize() > 0 ? Math.round( totalMemory / cache.getSize() ) : 0; + def max = cache.getCacheConfiguration().getMaxEntriesLocalHeap(); + def maxSize = average * max; + + totalCMax += maxSize; + + println cache.name + ":"; + println " - size : " + cache.getSize(); + println " - maxsize : " + max; + println " - average : " + humanReadable( average ); + println " - current : " + humanReadable( totalMemory ); + println " - cmax : " + humanReadable( maxSize ); +} + +println ""; +println ""; +println "Total CMax: " + humanReadable(totalCMax); \ No newline at end of file