/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.view.rest; import java.net.URI; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.Instant; import com.opengamma.engine.resource.EngineResourceManager; import com.opengamma.engine.resource.EngineResourceReference; import com.opengamma.id.UniqueId; import com.opengamma.id.UniqueIdentifiable; import com.opengamma.transport.jaxrs.FudgeRest; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.rest.AbstractDataResource; /** * RESTful resource for a {@link EngineResourceManager} * * @param <T> the type of resource */ public abstract class DataEngineResourceManagerResource<T extends UniqueIdentifiable> extends AbstractDataResource { private static final Logger s_logger = LoggerFactory.getLogger(DataEngineResourceManagerResource.class); /** * The time after which unused references may be automatically released. */ public static final long REFERENCE_LEASE_MILLIS = 5000; private final URI _baseUri; private final EngineResourceManager<? extends T> _manager; private final AtomicLong _nextReferenceId = new AtomicLong(); private final Map<Long, DataEngineResourceReferenceResource<T>> _activeReferences = new ConcurrentHashMap<Long, DataEngineResourceReferenceResource<T>>(); protected DataEngineResourceManagerResource(URI baseUri, EngineResourceManager<? extends T> manager) { _baseUri = baseUri; _manager = manager; } @POST @Consumes(FudgeRest.MEDIA) public Response createReference(UniqueId uniqueId) { EngineResourceReference<? extends T> reference = _manager.createReference(uniqueId); if (reference == null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } URI referenceUri = manageReference(reference); return responseCreated(referenceUri); } @Path("{referenceId}") public DataEngineResourceReferenceResource<T> getReference(@PathParam("referenceId") long referenceId) { DataEngineResourceReferenceResource<T> referenceResource = _activeReferences.get(referenceId); if (referenceResource == null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } return referenceResource; } /*package*/ void referenceReleased(long uniqueId) { _activeReferences.remove(uniqueId); } public static URI uriReference(URI baseUri, long referenceId) { return UriBuilder.fromUri(baseUri).segment(Long.toString(referenceId)).build(); } private URI getBaseUri() { return _baseUri; } /*package*/ URI manageReference(EngineResourceReference<? extends T> reference) { long referenceId = _nextReferenceId.getAndIncrement(); DataEngineResourceReferenceResource<T> referenceResource = createReferenceResource(referenceId, reference); _activeReferences.put(referenceId, referenceResource); return uriReference(getBaseUri(), referenceId); } protected abstract DataEngineResourceReferenceResource<T> createReferenceResource(long referenceId, EngineResourceReference<? extends T> reference); /** * Releases and discards any references that have not received a heartbeat since the given time. * * @param oldestHeartbeatTime the oldest permitted heartbeat time, not null */ public void releaseExpiredReferences(final Instant oldestHeartbeatTime) { ArgumentChecker.notNull(oldestHeartbeatTime, "oldestHeartbeatTime"); final Iterator<Map.Entry<Long, DataEngineResourceReferenceResource<T>>> it = _activeReferences.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<Long, DataEngineResourceReferenceResource<T>> entry = it.next(); DataEngineResourceReferenceResource<T> referenceResource = entry.getValue(); if (referenceResource.getLastHeartbeat().isBefore(oldestHeartbeatTime)) { // Notifies the manager which removes it from the map s_logger.warn("Releasing reference {} which has not received a heartbeat since {}, which exceeds the oldest allowed heartbeat of {}", new Object[] {entry.getKey(), referenceResource.getLastHeartbeat(), oldestHeartbeatTime}); referenceResource.release(); } } } public ReleaseExpiredReferencesRunnable createReleaseExpiredReferencesTask() { return new ReleaseExpiredReferencesRunnable(); } /** * Runnable to release expired references. */ class ReleaseExpiredReferencesRunnable implements Runnable { @Override public void run() { releaseExpiredReferences(Instant.now().minusMillis(REFERENCE_LEASE_MILLIS)); } /** * Sets the scheduler. * @param scheduler the scheduler, not null */ public void setScheduler(final ScheduledExecutorService scheduler) { scheduler.scheduleWithFixedDelay(this, REFERENCE_LEASE_MILLIS, REFERENCE_LEASE_MILLIS, TimeUnit.MILLISECONDS); } } }