/*
* Copyright 2011 Martin Grotzke
*
* Licensed 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 de.javakaffee.web.msm;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Stores readonly requests and a blacklist (requests that modified the session).
*
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
*/
public class ReadOnlyRequestsCache {
private static final Comparator<AtomicLong> ATOMLONG_COMP = new Comparator<AtomicLong>() {
@Override
public int compare( final AtomicLong o1, final AtomicLong o2 ) {
final long val1 = o1.longValue();
final long val2 = o2.longValue();
return val1 < val2 ? -1 : ( val1 == val2 ? 0 : 1);
}
};
private final Log _log = LogFactory.getLog( getClass() );
private final LRUCache<String, AtomicLong> _readOnlyRequests;
private final LRUCache<String, AtomicLong> _blacklist;
public ReadOnlyRequestsCache() {
final long sixHours = TimeUnit.HOURS.toMillis( 6 );
_readOnlyRequests = new LRUCache<String, AtomicLong>( 1000, sixHours );
_blacklist = new LRUCache<String, AtomicLong>( 50000, sixHours );
}
/**
* Registers the given requestURI as a readonly request, as long as it has not been tracked
* before as a modifying request (via {@link #modifyingRequest(String)}).
* <p>
* There's a limit on the number and the time readonly requests are beeing stored (simply a LRU cache),
* so that the most frequently accessed readonly requests are stored.
* </p>
* @param requestId the request uri to track.
* @return <code>true</code> if the requestURI was stored as readonly, <code>false</code> if it was on the blacklist.
* @see #modifyingRequest(String)
*/
public boolean readOnlyRequest( final String requestId ) {
if ( !_blacklist.containsKey( requestId ) ) {
if ( _log.isDebugEnabled() ) {
_log.debug( "Registering readonly request: " + requestId );
}
incrementOrPut( _readOnlyRequests, requestId );
return true;
}
return false;
}
/**
* Registers the given requestURI as a modifying request, which can be seen as a blacklist for
* readonly requests. There's a limit on number and time for modifying requests beeing stored.
* @param requestId the request uri to track.
*/
public void modifyingRequest( final String requestId ) {
if ( _log.isDebugEnabled() ) {
_log.debug( "Registering modifying request: " + requestId );
}
incrementOrPut( _blacklist, requestId );
_readOnlyRequests.remove( requestId );
}
/**
* Determines, if the given requestURI is a readOnly request and not blacklisted as a modifying request.
* @param requestId the request uri to check
* @return <code>true</code> if the given request uri can be regarded as read only.
*/
public boolean isReadOnlyRequest( final String requestId ) {
if ( _log.isDebugEnabled() ) {
_log.debug( "Asked for readonly request: " + requestId + " ("+ _readOnlyRequests.containsKey( requestId ) +")" );
}
// TODO: add some threshold
return _readOnlyRequests.containsKey( requestId );
}
/**
* The readonly requests, ordered by last accessed time, from least-recently accessed to most-recently.
* @return a list of readonly requests.
*/
public List<String> getReadOnlyRequests() {
return _readOnlyRequests.getKeys();
}
/**
* The readonly requests, ordered by the frequency they got registered, from least-frequently to most-frequently.
* @return a list of readonly requests.
*/
public List<String> getReadOnlyRequestsByFrequency() {
return _readOnlyRequests.getKeysSortedByValue( ATOMLONG_COMP );
}
private void incrementOrPut( final LRUCache<String, AtomicLong> cache, final String requestURI ) {
final AtomicLong count = cache.get( requestURI );
if ( count != null ) {
count.incrementAndGet();
}
else {
cache.put( requestURI, new AtomicLong( 1 ) );
}
}
}