Author: mgrigorov Date: Tue Dec 14 13:26:27 2010 New Revision: 1049077 URL: http://svn.apache.org/viewvc?rev=1049077&view=rev Log: WICKET-3209 WebApplication MostRecentlyUsedMap based upon Key age not Value age
Introduce a map with maximum capacity and lifetime for its entries. This map is used to store the responses when redirect to buffer strategy is used. Added: wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/ wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/MostRecentlyUsedMapTest.java (with props) wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/StoredResponsesMap.java (with props) wicket/trunk/wicket/src/test/java/org/apache/wicket/protocol/http/StoredResponsesMapTest.java (with props) Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/collections/MostRecentlyUsedMap.java wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/collections/MostRecentlyUsedMap.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/collections/MostRecentlyUsedMap.java?rev=1049077&r1=1049076&r2=1049077&view=diff ============================================================================== --- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/collections/MostRecentlyUsedMap.java (original) +++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/collections/MostRecentlyUsedMap.java Tue Dec 14 13:26:27 2010 @@ -34,7 +34,7 @@ public class MostRecentlyUsedMap<K, V> e private static final long serialVersionUID = 1L; /** Value most recently removed from map */ - V removedValue; + protected V removedValue; /** Maximum number of entries allowed in this map */ private final int maxEntries; Added: wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/MostRecentlyUsedMapTest.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/MostRecentlyUsedMapTest.java?rev=1049077&view=auto ============================================================================== --- wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/MostRecentlyUsedMapTest.java (added) +++ wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/MostRecentlyUsedMapTest.java Tue Dec 14 13:26:27 2010 @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.util.collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * + */ +public class MostRecentlyUsedMapTest +{ + /** + * Tests that {...@link MostRecentlyUsedMap} contains at most 2 entries + * + * @see <a href="https://issues.apache.org/jira/browse/WICKET-3209">WICKET-3209</a> + */ + @Test + public void max2Entries() + { + MostRecentlyUsedMap<String, String> map = new MostRecentlyUsedMap<String, String>(2); + assertEquals(0, map.size()); + map.put("1", "one"); + assertEquals(1, map.size()); + map.put("2", "two"); + assertEquals(2, map.size()); + map.put("3", "three"); + assertEquals(2, map.size()); + assertTrue(map.containsKey("2")); + assertTrue(map.containsKey("3")); + } +} Propchange: wicket/trunk/wicket-util/src/test/java/org/apache/wicket/util/collections/MostRecentlyUsedMapTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/StoredResponsesMap.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/StoredResponsesMap.java?rev=1049077&view=auto ============================================================================== --- wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/StoredResponsesMap.java (added) +++ wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/StoredResponsesMap.java Tue Dec 14 13:26:27 2010 @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.protocol.http; + +import java.util.Map; + +import org.apache.wicket.util.collections.MostRecentlyUsedMap; +import org.apache.wicket.util.time.Duration; +import org.apache.wicket.util.time.Time; + +/** + * A map that contains the buffered responses. It has a constraint on the maximum entries that it + * can contain, and a constraint on the duration of time an entry is considered valid/non-expired + */ +class StoredResponsesMap extends MostRecentlyUsedMap<String, Object> +{ + private static final long serialVersionUID = 1L; + + /** + * The actual object that is stored as a value of the map. It wraps the buffered response and + * assigns it a creation time. + */ + private static class Value + { + /** the original response to store */ + private BufferedWebResponse response; + + /** the time when this response is stored */ + private Time creationTime; + } + + /** + * The duration of time before a {...@link Value} is considered as expired + */ + private final Duration lifetime; + + /** + * Construct. + * + * @param maxEntries + * how much entries this map can contain + * @param lifetime + * the duration of time to keep an entry in the map before considering it expired + */ + public StoredResponsesMap(int maxEntries, Duration lifetime) + { + super(maxEntries); + + this.lifetime = lifetime; + } + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry<String, Object> eldest) + { + boolean removed = super.removeEldestEntry(eldest); + if (removed == false) + { + Value value = (Value)eldest.getValue(); + Duration elapsedTime = Time.now().subtract(value.creationTime); + if (lifetime.lessThanOrEqual(elapsedTime)) + { + removedValue = value.response; + removed = true; + } + } + return removed; + } + + @Override + public BufferedWebResponse put(String key, Object bufferedResponse) + { + if (!(bufferedResponse instanceof BufferedWebResponse)) + { + throw new IllegalArgumentException(StoredResponsesMap.class.getSimpleName() + + " can store only instances of " + BufferedWebResponse.class.getSimpleName()); + } + + Value value = new Value(); + value.creationTime = Time.now(); + value.response = (BufferedWebResponse)bufferedResponse; + Value oldValue = (Value)super.put(key, value); + + return oldValue != null ? oldValue.response : null; + } + + @Override + public BufferedWebResponse get(Object key) + { + BufferedWebResponse result = null; + Value value = (Value)super.get(key); + if (value != null) + { + Duration elapsedTime = Time.now().subtract(value.creationTime); + if (lifetime.greaterThan(elapsedTime)) + { + result = value.response; + } + else + { + // expired, remove it + remove(key); + } + } + return result; + } + + @Override + public BufferedWebResponse remove(Object key) + { + Value removedValue = (Value)super.remove(key); + return removedValue != null ? removedValue.response : null; + } + + @Override + public void putAll(Map<? extends String, ? extends Object> m) + { + throw new UnsupportedOperationException(); + } +} Propchange: wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/StoredResponsesMap.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java?rev=1049077&r1=1049076&r2=1049077&view=diff ============================================================================== --- wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java (original) +++ wicket/trunk/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java Tue Dec 14 13:26:27 2010 @@ -16,10 +16,6 @@ */ package org.apache.wicket.protocol.http; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -54,13 +50,12 @@ import org.apache.wicket.request.resourc import org.apache.wicket.session.HttpSessionStore; import org.apache.wicket.session.ISessionStore; import org.apache.wicket.util.IProvider; -import org.apache.wicket.util.collections.MostRecentlyUsedMap; import org.apache.wicket.util.file.FileUploadCleaner; import org.apache.wicket.util.file.IFileUploadCleaner; import org.apache.wicket.util.file.IResourceFinder; import org.apache.wicket.util.file.WebApplicationPath; import org.apache.wicket.util.lang.Args; -import org.apache.wicket.util.lang.Generics; +import org.apache.wicket.util.time.Duration; import org.apache.wicket.util.watch.IModificationWatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -133,12 +128,6 @@ public abstract class WebApplication ext } /** - * Map of buffered responses that are in progress per session. Buffered responses are - * temporarily stored - */ - private final ConcurrentHashMap<String, Map<String, BufferedHttpServletResponse>> bufferedResponses = Generics.newConcurrentHashMap(); - - /** * the prefix for storing variables in the actual session (typically {...@link HttpSession} for * this application instance. */ @@ -391,8 +380,6 @@ public abstract class WebApplication ext { super.sessionUnbound(sessionId); - bufferedResponses.remove(sessionId); - IRequestLogger logger = getRequestLogger(); if (logger != null) { @@ -438,7 +425,6 @@ public abstract class WebApplication ext { resourceWatcher.destroy(); } - bufferedResponses.clear(); IFileUploadCleaner fileUploadCleaner = getResourceSettings().getFileUploadCleaner(); if (fileUploadCleaner != null) @@ -577,35 +563,6 @@ public abstract class WebApplication ext } /** - * Add a buffered response to the redirect buffer. - * - * @param sessionId - * the session id - * @param bufferId - * the id that should be used for storing the buffer - * @param renderedResponse - * the response to buffer - */ - final void addBufferedResponse(String sessionId, String bufferId, - BufferedHttpServletResponse renderedResponse) - { - Map<String, BufferedHttpServletResponse> responsesPerSession = bufferedResponses.get(sessionId); - if (responsesPerSession == null) - { - responsesPerSession = Collections.synchronizedMap(new MostRecentlyUsedMap<String, BufferedHttpServletResponse>( - 4)); - Map<String, BufferedHttpServletResponse> previousValue = bufferedResponses.putIfAbsent( - sessionId, responsesPerSession); - if (previousValue != null) - { - responsesPerSession = previousValue; - } - } - String bufferKey = bufferId.startsWith("/") ? bufferId.substring(1) : bufferId; - responsesPerSession.put(bufferKey, renderedResponse); - } - - /** * Log that this application is started. */ final void logStarted() @@ -646,8 +603,12 @@ public abstract class WebApplication ext + "********************************************************************\n"); } - // TODO: Do this properly - private final Map<String, BufferedWebResponse> storedResponses = new ConcurrentHashMap<String, BufferedWebResponse>(); + /* + * Can contain at most 1000 responses and each entry can live at most one minute for now there + * is no need to configure these parameters externally + */ + private final StoredResponsesMap storedResponses = new StoredResponsesMap(1000, + Duration.seconds(60)); /** * @@ -685,35 +646,6 @@ public abstract class WebApplication ext storedResponses.put(key, response); } - // TODO remove after deprecation release - - /** - * Returns the redirect map where the buffered render pages are stored in and removes it - * immediately. - * - * @param sessionId - * the session id - * - * @param bufferId - * the id of the buffer as passed in as a request parameter - * @return the buffered response or null if not found (when this request is on a different box - * than the original request came in - */ - final BufferedHttpServletResponse popBufferedResponse(String sessionId, String bufferId) - { - Map<String, BufferedHttpServletResponse> responsesPerSession = bufferedResponses.get(sessionId); - if (responsesPerSession != null) - { - BufferedHttpServletResponse buffered = responsesPerSession.remove(bufferId); - if (responsesPerSession.size() == 0) - { - bufferedResponses.remove(sessionId); - } - return buffered; - } - return null; - } - @Override public String getMimeType(String fileName) { Added: wicket/trunk/wicket/src/test/java/org/apache/wicket/protocol/http/StoredResponsesMapTest.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/test/java/org/apache/wicket/protocol/http/StoredResponsesMapTest.java?rev=1049077&view=auto ============================================================================== --- wicket/trunk/wicket/src/test/java/org/apache/wicket/protocol/http/StoredResponsesMapTest.java (added) +++ wicket/trunk/wicket/src/test/java/org/apache/wicket/protocol/http/StoredResponsesMapTest.java Tue Dec 14 13:26:27 2010 @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.protocol.http; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; + +import org.apache.wicket.util.time.Duration; +import org.junit.Test; + +/** + * @see <a href="https://issues.apache.org/jira/browse/WICKET-3209">WICKET-3209</a> + */ +public class StoredResponsesMapTest +{ + /** + * Verifies that {...@link StoredResponsesMap} will expire the oldest entry if it is older than 2 + * seconds + * + * @throws Exception + */ + @Test + public void entriesLife2Seconds() throws Exception + { + StoredResponsesMap map = new StoredResponsesMap(1000, Duration.seconds(2)); + assertEquals(0, map.size()); + map.put("1", new BufferedWebResponse(null)); + assertEquals(1, map.size()); + TimeUnit.SECONDS.sleep(3); + map.put("2", new BufferedWebResponse(null)); + assertEquals(1, map.size()); + assertTrue(map.containsKey("2")); + } + + /** + * Verifies that getting a value which is expired will return <code>null</code>. + * + * @throws Exception + */ + @Test + public void getExpiredValue() throws Exception + { + StoredResponsesMap map = new StoredResponsesMap(1000, Duration.milliseconds(50)); + assertEquals(0, map.size()); + map.put("1", new BufferedWebResponse(null)); + assertEquals(1, map.size()); + TimeUnit.MILLISECONDS.sleep(51); + Object value = map.get("1"); + assertNull(value); + } + + /** + * Verifies that {...@link StoredResponsesMap} can have only {...@link BufferedWebResponse} values + */ + @Test(expected = IllegalArgumentException.class) + public void cannotPutArbitraryValue() + { + StoredResponsesMap map = new StoredResponsesMap(1000, Duration.days(1)); + map.put("1", new Object()); + } +} Propchange: wicket/trunk/wicket/src/test/java/org/apache/wicket/protocol/http/StoredResponsesMapTest.java ------------------------------------------------------------------------------ svn:eol-style = native