All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.vgregion.ldapservice.AsyncCachingLdapServiceWrapper Maven / Gradle / Ivy

/**
 * Copyright 2010 Västra Götalandsregionen
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of version 2.1 of the GNU Lesser General Public
 *   License as published by the Free Software Foundation.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the
 *   Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 *   Boston, MA 02111-1307  USA
 */

package se.vgregion.ldapservice;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.*;

/**
 * This implementation wraps an {@link LdapService} and makes the calls asynchronously (when possible), thus fetches the
 * result lazily. The returned object is a wrapper (when possible) implementation which uses concurrency to enable lazy
 * loading.
 *
 * @author Patrik Bergström
 * @see LdapService
 */
@Service
@SuppressWarnings("unchecked")
public class AsyncCachingLdapServiceWrapper implements LdapService {

    private static final CacheManager SINGLE_CACHE_MANAGER = CacheManager.create();

    private Ehcache cache;
    private LdapService ldapService;
    private static final int N_THREADS = 10;
    private ExecutorService executor = Executors.newFixedThreadPool(N_THREADS);
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture lastScheduledTask;

    /**
     * Constructor.
     *
     * @param ldapService ldapService
     */
    public AsyncCachingLdapServiceWrapper(LdapService ldapService) {
        final int hours = 48;
        final int minutes = 60;
        final int seconds = 60;
        long timeoutInSeconds = hours * minutes * seconds; // 48 hours
        // The timeout arguments mean that it's only the time from creation that matters; the idle time can
        // never be longer than the time since creation.
        String name = this.getClass() + "Cache_" + timeoutInSeconds;
        if (!SINGLE_CACHE_MANAGER.cacheExists(name)) {
            final int maxElementsInMemory = 500;
            this.cache = new Cache(name, maxElementsInMemory, false, false, timeoutInSeconds, timeoutInSeconds);
            SINGLE_CACHE_MANAGER.addCache(cache);
        } else {
            this.cache = SINGLE_CACHE_MANAGER.getCache(name);
        }

        this.ldapService = ldapService;
    }

    /**
     * Constructor.
     *
     * @param ldapService       ldapService
     * @param timeToLiveSeconds the time the cached elements should live (from creation)
     */
    public AsyncCachingLdapServiceWrapper(LdapService ldapService, long timeToLiveSeconds) {
        // The timeout arguments mean that it's only the time from creation that matters; the idle time can
        // never be longer than the time since creation.
        String name = this.getClass() + "Cache_" + timeToLiveSeconds;
        if (!SINGLE_CACHE_MANAGER.cacheExists(name)) {
            final int maxElementsInMemory = 500;
            cache = new Cache(name, maxElementsInMemory, false, false, timeToLiveSeconds, timeToLiveSeconds);
            SINGLE_CACHE_MANAGER.addCache(cache);
        } else {
            this.cache = SINGLE_CACHE_MANAGER.getCache(name);
        }

        this.ldapService = ldapService;
    }

    @Override
    public LdapUser[] search(final String base, final String filter) {
        Integer cacheKey = createCacheKey(base, filter);
        Element element = cache.get(cacheKey);
        if (element != null) {
            return (LdapUser[]) element.getValue();
        }

        // We cannot make a wrapper of an Array, so just delegate synchronously.
        LdapUser[] search = ldapService.search(base, filter);

        if (search != null) {
            cache.put(new Element(cacheKey, search));
        }

        return search;
    }

    private Integer createCacheKey(Object... args) {
        int callingLineNumber = new Exception().getStackTrace()[1].getLineNumber();
        final int prime = 7;
        int hash = callingLineNumber * prime;
        for (Object arg : args) {
            if (arg != null) {
                hash += arg.hashCode() * prime;
            }
        }

        return hash;
    }

    @Override
    public LdapUser[] search(String s, String s1, String[] strings) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LdapUser getLdapUser(String s, String s1) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LdapUser getLdapUser(String s, String s1, String[] strings) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Properties getProperties() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addLdapUser(String s, HashMap stringStringHashMap) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean modifyLdapUser(LdapUser ldapUser, HashMap stringStringHashMap) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean deleteLdapUser(LdapUser ldapUser) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LdapUser getLdapUserByUid(String s, String s1) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LdapUser getLdapUserByUid(final String userId) {
        Integer cacheKey = createCacheKey(userId);
        Element element = cache.get(cacheKey);
        if (element != null) {
            return (LdapUser) element.getValue();
        }

        Callable callable = new Callable() {
            @Override
            public LdapUser call() throws Exception {
                return ldapService.getLdapUserByUid(userId);
            }
        };

        Future futureLdapUser = executor.submit(callable);

        AsyncLdapUserWrapper ldapUser = new AsyncLdapUserWrapper(futureLdapUser, cacheKey);

        cache.put(new Element(cacheKey, ldapUser));

        // Cleanup null objects since we don't want to cache them.
        if (lastScheduledTask == null || lastScheduledTask.isDone()) {
            // Otherwise it is unnecessary to pile up tasks
            final int delay = 5;
            lastScheduledTask = scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    final List keys = cache.getKeys();
                    for (Object key : keys) {
                        final Element element1 = cache.get(key);
                        if (element1 == null || element1.getObjectValue() == null
                                || ((LdapUser)element1.getValue()).getDn() == null) {
                            cache.remove(key);
                        }
                    }
                }
            }, delay, TimeUnit.SECONDS);
        }

        return ldapUser;
    }

    Ehcache getCache() {
        return cache;
    }

    static class AsyncLdapUserWrapper implements LdapUser, Serializable {
        private final Logger LOGGER = LoggerFactory.getLogger(AsyncLdapUserWrapper.class);
        private static final long serialVersionUID = -1123850060733039675L;

        private transient Future futureLdapUser;
        private Integer cacheKey;

        /**
         * Constructor.
         *
         * @param futureLdapUser futureLdapUser
         * @param cacheKey       cacheKey
         */
        public AsyncLdapUserWrapper(Future futureLdapUser, Integer cacheKey) {
            this.futureLdapUser = futureLdapUser;
            this.cacheKey = cacheKey;
        }

        @Override
        public String getDn() {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return null;
                }
                return ldapUser.getDn();
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public String getAttributeValue(String s) {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return null;
                }
                return ldapUser.getAttributeValue(s);
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public String[] getAttributeValues(String s) {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return null;
                }
                return ldapUser.getAttributeValues(s);
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public Map> getAttributes() {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return null;
                }
                return ldapUser.getAttributes();
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public void clearAttribute(String s) {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return;
                }
                ldapUser.clearAttribute(s);
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public void setAttributeValue(String s, Object o) {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return;
                }
                ldapUser.setAttributeValue(s, o);
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public void addAttributeValue(String s, Object o) {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return;
                }
                ldapUser.addAttributeValue(s, o);
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        @Override
        public void setAttributeValue(String s, Object[] objects) {
            try {
                LdapUser ldapUser = futureLdapUser.get();
                if (ldapUser == null) {
                    return;
                }
                ldapUser.setAttributeValue(s, objects);
            } catch (InterruptedException e) {
                throw new LdapUserRetrievalException(e);
            } catch (ExecutionException e) {
                throw new LdapUserRetrievalException(e);
            }
        }

        private void writeObject(java.io.ObjectOutputStream out)
                throws IOException {
            out.defaultWriteObject();
            try {
                out.writeObject(futureLdapUser.get());
            } catch (InterruptedException e) {
                LOGGER.error(e.getMessage(), e);
            } catch (ExecutionException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }

        private void readObject(java.io.ObjectInputStream in)
                throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            futureLdapUser = new AsyncResult((LdapUser) in.readObject());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy