blob: 14421fb303bece63ba7a95db7ebca30642482512 [file] [log] [blame]
/*
* Copyright (c) 2020 Kentyou.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Kentyou - initial API and implementation
*/
package org.eclipse.sensinact.gateway.nthbnd.endpoint;
import org.eclipse.sensinact.gateway.core.security.AuthenticationToken;
import org.eclipse.sensinact.gateway.core.security.InvalidCredentialException;
import org.eclipse.sensinact.gateway.util.CastUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* A collection {@link NorthboundEndpoint}s with a limited lifetime,
* but 'reactivable'
*
* @author <a href="mailto:cmunilla@kentyou.com">Christophe Munilla</a>
*/
public final class NorthboundEndpoints {
/**
* Defines the lifetime end of an {@link NorthboundEndpoint}
*/
private final class Term {
Term previous;
Term next;
final String identifier;
final AtomicLong timeout;
Term(String identifier) {
this.identifier = identifier;
this.timeout = new AtomicLong(0);
}
final void reactivate() {
this.timeout.set(System.currentTimeMillis() + NorthboundEndpoints.this.lifetime);
}
final boolean expired() {
return System.currentTimeMillis() > this.timeout.get();
}
}
/**
* Handles the list of created {@link NorthboundEndpoint} according
* to their lifetime
*/
private final class Schedule implements Runnable {
private volatile boolean running;
private Map<String, Term> map = null;
private Term head = null;
private Term queue = null;
Schedule() {
this.map = new HashMap<String, Term>();
this.running = false;
}
void stop() {
this.running = false;
}
Term getTerm(String sessionIdentifier) {
Term term = null;
term = this.map.get(sessionIdentifier);
return term;
}
boolean update(String sessionIdentifier) {
Term term = this.remove(sessionIdentifier);
if (term != null) {
term.reactivate();
this.add(term);
return true;
}
return false;
}
Term remove(String sessionIdentifier) {
Term term = this.map.remove(sessionIdentifier);
if (term == null) {
return term;
}
if (term == this.head) {
this.head = term.next;
}
if (term == this.queue) {
this.queue = term.previous;
}
if (term.previous != null) {
term.previous.next = term.next;
}
if (term.next != null) {
term.next.previous = term.previous;
}
term.next = null;
term.previous = null;
return term;
}
Term add(Term term) {
Term old = null;
if (term == null || term.identifier == null) {
return old;
}
term.previous = this.queue;
if (this.queue != null) {
this.queue.next = term;
} else {
this.head = term;
}
this.queue = term;
old = this.map.put(term.identifier, term);
return old;
}
/**
* @inheritDoc
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
this.running = true;
while (this.running) {
synchronized (NorthboundEndpoints.this.lock) {
while (this.head != null && this.head.expired()) {
this.map.remove(this.head.identifier);
this.head = this.head.next;
if (this.head != null) {
this.head.previous = null;
}
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.interrupted();
this.running = false;
continue;
}
}
this.map.clear();
Term n = head;
Term t = null;
while (n != null) {
t = n.next;
n.previous = null;
n.next = null;
n = t;
t = null;
}
this.head = null;
this.queue = null;
}
}
public static final String NORTHBOUND_ENDPOINT_LIFETIME = "org.eclipse.sensinact.ntbnd.endpoint.lifetime";
private static final long DEFAULT_LIFETIME = 5 * 60 * 1000;
private final Object lock = new Object();
private Map<Term, NorthboundEndpoint> endpoints;
private NorthboundMediator mediator;
private Schedule schedule;
private final long lifetime;
/**
* Constructor
*
* @param mediator the {@link Mediator} allowing the
* NorthboundEndpoints to be instantiated to interact
* with the OSGi host environment
*/
public NorthboundEndpoints(NorthboundMediator mediator) {
Object lifetimeProp = mediator.getProperty(NORTHBOUND_ENDPOINT_LIFETIME);
Long l = null;
if (lifetimeProp == null || (l = CastUtils.castPrimitive(Long.class, lifetimeProp)) == null || l.longValue() < 1000) {
this.lifetime = DEFAULT_LIFETIME;
} else {
this.lifetime = l.longValue();
}
this.endpoints = Collections.<Term, NorthboundEndpoint>synchronizedMap(new WeakHashMap<Term, NorthboundEndpoint>());
this.schedule = new Schedule();
this.mediator = mediator;
new Thread(this.schedule).start();
}
/**
* Returns the {@link NorthboundEndpoint} for an anonymous
* access
*
* @return the {@link NorthboundEndpoint} for an anonymous
* access
* @throws InvalidCredentialException
*/
public NorthboundEndpoint getEndpoint() throws InvalidCredentialException {
NorthboundEndpoint endpoint = new NorthboundEndpoint(mediator, null);
Term term = new Term(endpoint.getSessionToken());
term.reactivate();
synchronized (this.lock) {
this.schedule.add(term);
}
this.endpoints.put(term, endpoint);
return endpoint;
}
/**
* Returns the already existing {@link NorthboundEndpoint}
* associated to the {@link Session} whose String identifier
* is wrapped by the {@link AuthenticationToken} instance
* passed as parameter. The lifetime of the {@link
* NorthboundEndpoint} is 'reactivated' before it is returned.
* if the {@link AuthenticationToken} argument is null or the
* {@link NorthboundEnpoint} does not already exist the method
* returns null.
*
* @param token the {@link AuthenticationToken} wrapping the
* String identifier of the {@link Session}
* @return the {@link NorthboundEndpoint} for the specified
* authentication material
* @throws InvalidCredentialException
*/
public NorthboundEndpoint getEndpoint(AuthenticationToken token) throws InvalidCredentialException {
if (token == null) {
return null;
}
String sessionIdentifier = token.getAuthenticationMaterial();
Term t = null;
synchronized (this.lock) {
if (this.schedule.update(sessionIdentifier)) {
t = this.schedule.getTerm(sessionIdentifier);
}
}
if(t!=null) {
return this.endpoints.get(t);
}
return null;
}
/**
* Adds the {@link NorthboundEndpoint} passed as parameter if
* its attached {@link Session}'s identifier is not null and
* if an existing {@link NorthboundEndpoint} linked to the
* same {@link Session} already exists.
*
* @param nothboundEndpoint the {@link NorthboundEndpoint} to be added
* @return the added {@link NorthboundEndpoint} or the existing
* one linked to the same {@link Session}
*/
public NorthboundEndpoint add(NorthboundEndpoint northboundEndpoint) {
String sessionToken = northboundEndpoint == null ? null : northboundEndpoint.getSessionToken();
if (sessionToken == null) {
return null;
}
//search for an already existing endpoint for the
//specified session identifier
Term t = null;
synchronized (this.lock) {
if (this.schedule.update(northboundEndpoint.getSessionToken())) {
t = this.schedule.getTerm(northboundEndpoint.getSessionToken());
}
}
if(t!=null) {
return this.endpoints.get(t);
}
t = new Term(sessionToken);
t.reactivate();
synchronized (this.lock) {
this.schedule.add(t);
}
this.endpoints.put(t, northboundEndpoint);
return northboundEndpoint;
}
/**
* Returns the defined timeout for the {@link NorthboundEndpoint}
* linked to the {@link Session} whose String identifier is
* passed as parameter
*
* @return the defined timeout for the {@link NorthboundEndpoint}
* of the specified {@link Session}
*/
public long getTimeout(String sessionToken) {
long timeout = -1;
Term t = null;
synchronized (this.lock) {
t = this.schedule.getTerm(sessionToken);
}
if (t != null) {
timeout = t.timeout.get();
}
return timeout;
}
/**
* Returns the lifetime long value that is assigned to
* newly created (or renewed) {@link NorthboundEndpoint}s
*
* @return the lifetime long value
*/
public long getLifetime() {
return this.lifetime;
}
/**
* Closes this {@link NorthboundEndpoint}s collection
*/
public void close() {
this.schedule.stop();
}
}