com.astamuse.asta4d.web.util.timeout.DefaultSessionAwareExpirableDataManager.java Source code

Java tutorial

Introduction

Here is the source code for com.astamuse.asta4d.web.util.timeout.DefaultSessionAwareExpirableDataManager.java

Source

/*
 * Copyright 2012 astamuse company,Ltd.
 * 
 * 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 com.astamuse.asta4d.web.util.timeout;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;

import com.astamuse.asta4d.util.IdGenerator;
import com.astamuse.asta4d.web.WebApplicationContext;

public class DefaultSessionAwareExpirableDataManager implements ExpirableDataManager {

    private static final String SessionCheckIdKey = DefaultSessionAwareExpirableDataManager.class
            + "#SessionCheckIdKey";

    private Map<String, DataHolder> dataMap = null;

    private AtomicInteger dataCounter = null;

    private ScheduledExecutorService service = null;

    // 3 minutes
    private long expirationCheckPeriodInMilliseconds = 3 * 60 * 1000;

    private int maxDataSize = 10_000;

    private boolean sessionAware = true;

    private long spinTimeInMilliseconds = 1000;

    // 5 times spinning
    private long maxSpinTimeInMilliseconds = 1000 * 5;

    private String checkThreadName = this.getClass().getSimpleName() + "-check-thread";

    public DefaultSessionAwareExpirableDataManager() {
        dataMap = createThreadSafeDataMap();
        dataCounter = new AtomicInteger();
    }

    public void setExpirationCheckPeriodInMilliseconds(long expirationCheckPeriodInMilliseconds) {
        this.expirationCheckPeriodInMilliseconds = expirationCheckPeriodInMilliseconds;
    }

    public void setMaxDataSize(int maxDataSize) {
        this.maxDataSize = maxDataSize;
    }

    public void setSessionAware(boolean sessionAware) {
        this.sessionAware = sessionAware;
    }

    public void setSpinTimeInMilliseconds(long spinTimeInMilliseconds) {
        this.spinTimeInMilliseconds = spinTimeInMilliseconds;
    }

    public void setMaxSpinTimeInMilliseconds(long maxSpinTimeInMilliseconds) {
        this.maxSpinTimeInMilliseconds = maxSpinTimeInMilliseconds;
    }

    protected Map<String, DataHolder> createThreadSafeDataMap() {
        return new ConcurrentHashMap<>();
    }

    protected void decreaseCount() {
        dataCounter.decrementAndGet();
    }

    protected void addCount(int delta) {
        dataCounter.addAndGet(delta);
    }

    protected void increaseCount() {
        dataCounter.incrementAndGet();
    }

    protected int getCurrentCount() {
        return dataCounter.get();
    }

    @Override
    public void start() {
        service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, checkThreadName);
            }
        });

        // start check thread
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                List<Entry<String, DataHolder>> entries = new ArrayList<>(dataMap.entrySet());
                long currentTime = System.currentTimeMillis();
                int removedCounter = 0;
                Object existing;
                for (Entry<String, DataHolder> entry : entries) {
                    if (entry.getValue().isExpired(currentTime)) {
                        existing = dataMap.remove(entry.getKey());
                        if (existing != null) {// we removed it successfully
                            removedCounter++;
                        }
                    }
                }
                if (removedCounter > 0) {
                    addCount(-removedCounter);
                }
            }
        }, expirationCheckPeriodInMilliseconds, expirationCheckPeriodInMilliseconds, TimeUnit.MILLISECONDS);
    }

    @Override
    public void stop() {
        // release all resources
        service.shutdownNow();
        dataCounter = null;
        dataMap = null;
    }

    @SuppressWarnings("unchecked")
    public <T> T get(String dataId, boolean remove) {
        DataHolder holder;
        if (remove) {
            holder = dataMap.remove(dataId);
            if (holder != null) {
                decreaseCount();
                if (holder.isExpired(System.currentTimeMillis())) {
                    holder = null;
                }
            }
        } else {
            holder = dataMap.get(dataId);
            if (holder != null) {
                if (holder.isExpired(System.currentTimeMillis())) {
                    holder = dataMap.remove(dataId);
                    if (holder != null) {
                        decreaseCount();
                        holder = null;
                    }
                }
            }
        }
        if (holder == null) {
            return null;
        } else {
            if (StringUtils.equals(retrieveSessionCheckId(false), holder.sessionId)) {
                return (T) holder.getData();
            } else {
                return null;
            }
        }
    }

    public void put(String dataId, Object data, long expireMilliSeconds) {
        checkSize();
        Object existing = dataMap.put(dataId,
                new DataHolder(data, expireMilliSeconds, retrieveSessionCheckId(true)));
        if (existing == null) {
            increaseCount();
        }
    }

    protected void checkSize() {
        if (getCurrentCount() >= maxDataSize) {
            try {
                long spinTimeTotal = 0;
                while (getCurrentCount() >= maxDataSize) {
                    if (spinTimeTotal >= maxSpinTimeInMilliseconds) {
                        String msg = "There are too many data in %s and we could not get empty space after waiting for %d milliseconds."
                                + " The configured max size is %d and perhaps you should increase the value.";
                        msg = String.format(msg, this.getClass().getName(), spinTimeTotal, maxDataSize);
                        throw new TooManyDataException(msg);
                    }
                    Thread.sleep(spinTimeInMilliseconds);
                    spinTimeTotal += spinTimeInMilliseconds;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * NOTE: Because there is no way to retrieve the session id via WebApplicationContext, thus we use a independent check id which is
     * different from the http request session id to check whether current request client is the same client from previous request.
     * 
     * @param create
     * @return
     */
    protected String retrieveSessionCheckId(boolean create) {
        String sessionCheckId = null;
        if (sessionAware) {
            WebApplicationContext context = WebApplicationContext.getCurrentThreadWebApplicationContext();
            sessionCheckId = context.getData(WebApplicationContext.SCOPE_SESSION, SessionCheckIdKey);
            if (sessionCheckId == null && create) {
                sessionCheckId = IdGenerator.createId();
                context.setData(WebApplicationContext.SCOPE_SESSION, SessionCheckIdKey, sessionCheckId);
            }
        }
        return sessionCheckId;
    }

    @Override
    protected void finalize() throws Throwable {
        stop();
        super.finalize();
    }

    protected static class DataHolder implements Serializable {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        private final Object data;
        private final long creationTime;
        private final long expireMilliSeconds;
        private final String sessionId;

        private DataHolder(Object data, long expireMilliSeconds, String sessionId) {
            this.sessionId = sessionId;
            this.data = data;
            this.expireMilliSeconds = expireMilliSeconds;
            this.creationTime = System.currentTimeMillis();
        }

        private Object getData() {
            return data;
        }

        private boolean isExpired(long currentTime) {
            return (currentTime - creationTime) > expireMilliSeconds;
        }
    }
}