com.b2international.snowowl.snomed.reasoner.server.classification.CollectingService.java Source code

Java tutorial

Introduction

Here is the source code for com.b2international.snowowl.snomed.reasoner.server.classification.CollectingService.java

Source

/*
 * Copyright 2011-2018 B2i Healthcare Pte Ltd, http://b2i.sg
 * 
 * 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.b2international.snowowl.snomed.reasoner.server.classification;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;

import com.b2international.snowowl.core.AbstractDisposableService;
import com.b2international.snowowl.core.api.IBranchPath;
import com.b2international.snowowl.core.api.SnowowlRuntimeException;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Iterables;
import com.google.common.collect.MapMaker;

/**
 * Represents an abstract service manager which:
 * <ul>
 * <li>Keeps frequently used instances keyed by {@link IBranchPath} for a certain amount of time
 * <li>Limits the total number of available instances
 * <li>Provides a service instance pool where instances can be borrowed and returned
 * </ul>
 * @param <S> the service instance type
 * @param <P> the initialization parameter type
 */
public abstract class CollectingService<S, P> extends AbstractDisposableService {

    private class CollectingTimerTask extends TimerTask {

        private final IBranchPath branchPath;

        public CollectingTimerTask(final IBranchPath branchPath) {
            this.branchPath = branchPath;
        }

        @Override
        public void run() {
            if (isDisposed()) {
                cancel();
            } else {
                evict(branchPath);
            }
        }
    }

    // Keep unreferenced services around for 15 minutes
    private static final long KEEPALIVE_MILLIS = TimeUnit.MINUTES.toMillis(15L);

    private static final Supplier<Timer> CLEANUP_TIMER = Suppliers.memoize(new Supplier<Timer>() {
        @Override
        public Timer get() {
            return new Timer("Service cleanup timer", true);
        }
    });

    private final ConcurrentMap<IBranchPath, CollectingServiceReference<S>> sharedServiceCache;

    private final BlockingQueue<CollectingServiceReference<S>> serviceReferenceQueue;

    protected CollectingService(final int maximumServiceCount) {
        this(maximumServiceCount, KEEPALIVE_MILLIS);
    }

    protected CollectingService(final int maximumServiceCount, final long keepaliveMillis) {
        this.serviceReferenceQueue = initQueue(maximumServiceCount);
        this.sharedServiceCache = new MapMaker().makeMap();
    }

    private Timer getCleanupTimer() {
        return CLEANUP_TIMER.get();
    }

    protected abstract Logger getLogger();

    protected abstract S createService(final IBranchPath branchPath, final boolean shared, final P serviceParams)
            throws Exception;

    protected abstract boolean matchesParams(final S service, final P serviceParams);

    protected abstract void retireService(final S service);

    private ArrayBlockingQueue<CollectingServiceReference<S>> initQueue(final int maximumServiceCount) {
        final ArrayBlockingQueue<CollectingServiceReference<S>> result = new ArrayBlockingQueue<CollectingServiceReference<S>>(
                maximumServiceCount, true);

        for (int i = 0; i < maximumServiceCount; i++) {
            try {
                result.put(new CollectingServiceReference<S>());
            } catch (final InterruptedException e) {
                throw new SnowowlRuntimeException("Interrupted while trying to populate service reference queue.",
                        e);
            }
        }

        return result;
    }

    protected final CollectingServiceReference<S> getSharedServiceReferenceIfExists(final IBranchPath branchPath) {
        return sharedServiceCache.get(branchPath);
    }

    public final CollectingServiceReference<S> takeServiceReference(final IBranchPath branchPath,
            final boolean shared, final P serviceParams) throws Exception {

        CollectingServiceReference<S> sharedServiceReference = null;

        if (shared) {
            sharedServiceReference = takeSharedServiceReference(branchPath, serviceParams);
        }

        if (null != sharedServiceReference) {
            return sharedServiceReference;
        } else {
            return takeRegularServiceReference(branchPath, shared, serviceParams);
        }
    }

    private CollectingServiceReference<S> takeSharedServiceReferenceIfExists(final IBranchPath branchPath) {
        return sharedServiceCache.remove(branchPath);
    }

    private CollectingServiceReference<S> takeSharedServiceReference(final IBranchPath branchPath,
            final P serviceParams) throws Exception {
        final CollectingServiceReference<S> removedReference = takeSharedServiceReferenceIfExists(branchPath);
        if (null != removedReference) {
            removedReference.cancelCollectingTask();

            if (!matchesParams(removedReference.getService(), serviceParams)) {
                retireService(removedReference.getService());
                removedReference.init(branchPath, true, createService(branchPath, true, serviceParams));
            }
        }

        return removedReference;
    }

    private CollectingServiceReference<S> takeRegularServiceReference(final IBranchPath branchPath,
            final boolean shared, final P serviceParams) throws Exception {
        final CollectingServiceReference<S> serviceReference = getUnusedServiceReference();
        serviceReference.init(branchPath, shared, createService(branchPath, shared, serviceParams));
        return serviceReference;
    }

    private CollectingServiceReference<S> getUnusedServiceReference() throws InterruptedException {

        // TODO: after some tries, we may want to give up
        while (true) {

            final CollectingServiceReference<S> serviceReference = serviceReferenceQueue.poll(1, TimeUnit.SECONDS);

            if (null != serviceReference) {
                return serviceReference;
            }

            if (!sharedServiceCache.isEmpty()) {
                final IBranchPath evictedBranchPath = Iterables.getFirst(sharedServiceCache.keySet(), null);
                evict(evictedBranchPath);
            }
        }
    }

    public final void retireServiceReference(final CollectingServiceReference<S> serviceReference)
            throws InterruptedException {

        if (!serviceReference.isShared()) {
            retireRegularServiceReference(serviceReference);
        } else {
            retireSharedServiceReference(serviceReference);
        }
    }

    private void retireSharedServiceReference(final CollectingServiceReference<S> serviceReference) {
        sharedServiceCache.put(serviceReference.getBranchPath(), serviceReference);
        final TimerTask collectingTask = createSharedCollectingTimerTask(serviceReference.getBranchPath());
        serviceReference.scheduleTaskOnTimer(getCleanupTimer(), collectingTask, KEEPALIVE_MILLIS);
    }

    private TimerTask createSharedCollectingTimerTask(final IBranchPath branchPath) {
        return new CollectingTimerTask(branchPath);
    }

    private void retireRegularServiceReference(final CollectingServiceReference<S> serviceReference)
            throws InterruptedException {
        retireService(serviceReference.getService());
        serviceReference.init(null, false, null);
        serviceReferenceQueue.put(serviceReference);
    }

    protected final boolean hasAvailableServiceReferences() {
        return !serviceReferenceQueue.isEmpty();
    }

    protected void evictAll() {
        for (final IBranchPath branchPath : sharedServiceCache.keySet()) {
            evict(branchPath);
        }
    }

    private void evict(final IBranchPath branchPath) {
        final CollectingServiceReference<S> referenceToRetire = takeSharedServiceReferenceIfExists(branchPath);

        if (null != referenceToRetire) {
            try {
                retireRegularServiceReference(referenceToRetire);
            } catch (final InterruptedException e) {
                getLogger().error("Interrupted while trying to retire an unused shared service reference.", e);
            }
        }
    }

    @Override
    protected void onDispose() {
        getCleanupTimer().cancel();
        evictAll();
        super.onDispose();
    }
}