Java tutorial
package io.joynr.dispatching.subscription; /* * #%L * %% * Copyright (C) 2011 - 2016 BMW Car IT GmbH * %% * 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. * #L% */ import static io.joynr.runtime.JoynrInjectionConstants.JOYNR_SCHEDULER_CLEANUP; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import com.google.common.collect.Maps; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import io.joynr.dispatching.Dispatcher; import io.joynr.exceptions.JoynrRuntimeException; import io.joynr.messaging.MessagingQos; import io.joynr.proxy.Future; import io.joynr.proxy.invocation.AttributeSubscribeInvocation; import io.joynr.proxy.invocation.BroadcastSubscribeInvocation; import io.joynr.pubsub.HeartbeatSubscriptionInformation; import io.joynr.pubsub.SubscriptionQos; import io.joynr.pubsub.subscription.AttributeSubscriptionListener; import io.joynr.pubsub.subscription.BroadcastSubscriptionListener; import joynr.BroadcastSubscriptionRequest; import joynr.SubscriptionReply; import joynr.SubscriptionRequest; import joynr.SubscriptionStop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class SubscriptionManagerImpl implements SubscriptionManager { private ConcurrentMap<String, AttributeSubscriptionListener<?>> subscriptionListenerDirectory; private ConcurrentMap<String, BroadcastSubscriptionListener> broadcastSubscriptionListenerDirectory; private ConcurrentMap<String, Future<String>> subscriptionFutureMap; private ConcurrentMap<String, Class<?>> subscriptionTypes; private ConcurrentMap<String, Class<?>[]> subscriptionBroadcastTypes; private ConcurrentMap<String, PubSubState> subscriptionStates; private ConcurrentMap<String, MissedPublicationTimer> missedPublicationTimers; private ConcurrentMap<String, ScheduledFuture<?>> subscriptionEndFutures; // These futures will be needed if a // subscription // should be updated with a new end time private static final Logger logger = LoggerFactory.getLogger(SubscriptionManagerImpl.class); private ScheduledExecutorService cleanupScheduler; private Dispatcher dispatcher; @Inject public SubscriptionManagerImpl(@Named(JOYNR_SCHEDULER_CLEANUP) ScheduledExecutorService cleanupScheduler, Dispatcher dispatcher) { super(); this.cleanupScheduler = cleanupScheduler; this.dispatcher = dispatcher; this.subscriptionListenerDirectory = Maps.newConcurrentMap(); this.broadcastSubscriptionListenerDirectory = Maps.newConcurrentMap(); this.subscriptionStates = Maps.newConcurrentMap(); this.missedPublicationTimers = Maps.newConcurrentMap(); this.subscriptionEndFutures = Maps.newConcurrentMap(); this.subscriptionTypes = Maps.newConcurrentMap(); this.subscriptionBroadcastTypes = Maps.newConcurrentMap(); this.subscriptionFutureMap = Maps.newConcurrentMap(); } // CHECKSTYLE IGNORE ParameterNumber FOR NEXT 1 LINES public SubscriptionManagerImpl( ConcurrentMap<String, AttributeSubscriptionListener<?>> attributeSubscriptionDirectory, ConcurrentMap<String, BroadcastSubscriptionListener> broadcastSubscriptionDirectory, ConcurrentMap<String, PubSubState> subscriptionStates, ConcurrentMap<String, MissedPublicationTimer> missedPublicationTimers, ConcurrentMap<String, ScheduledFuture<?>> subscriptionEndFutures, ConcurrentMap<String, Class<?>> subscriptionAttributeTypes, ConcurrentMap<String, Class<?>[]> subscriptionBroadcastTypes, ConcurrentMap<String, Future<String>> subscriptionFutureMap, ScheduledExecutorService cleanupScheduler, Dispatcher dispatcher) { super(); this.subscriptionListenerDirectory = attributeSubscriptionDirectory; this.broadcastSubscriptionListenerDirectory = broadcastSubscriptionDirectory; this.subscriptionStates = subscriptionStates; this.missedPublicationTimers = missedPublicationTimers; this.subscriptionEndFutures = subscriptionEndFutures; this.subscriptionTypes = subscriptionAttributeTypes; this.subscriptionBroadcastTypes = subscriptionBroadcastTypes; this.cleanupScheduler = cleanupScheduler; this.dispatcher = dispatcher; this.subscriptionFutureMap = subscriptionFutureMap; } private void cancelExistingSubscriptionEndRunnable(String subscriptionId) { ScheduledFuture<?> scheduledFuture = subscriptionEndFutures.get(subscriptionId); if (scheduledFuture != null) { scheduledFuture.cancel(false); } } private void registerSubscription(final SubscriptionQos qos, String subscriptionId) { cancelExistingSubscriptionEndRunnable(subscriptionId); PubSubState subState = new PubSubState(); subState.updateTimeOfLastPublication(); subscriptionStates.put(subscriptionId, subState); long expiryDate = qos.getExpiryDateMs(); logger.info("subscription: {} expiryDate: " + (expiryDate == SubscriptionQos.NO_EXPIRY_DATE ? "never" : expiryDate - System.currentTimeMillis()), subscriptionId); if (expiryDate != SubscriptionQos.NO_EXPIRY_DATE) { SubscriptionEndRunnable endRunnable = new SubscriptionEndRunnable(subscriptionId); ScheduledFuture<?> subscriptionEndFuture = cleanupScheduler.schedule(endRunnable, expiryDate, TimeUnit.MILLISECONDS); subscriptionEndFutures.put(subscriptionId, subscriptionEndFuture); } } @Override public void registerAttributeSubscription(String fromParticipantId, Set<String> toParticipantIds, AttributeSubscribeInvocation request) { if (!request.hasSubscriptionId()) { request.setSubscriptionId(UUID.randomUUID().toString()); } SubscriptionQos qos = request.getQos(); registerSubscription(qos, request.getSubscriptionId()); logger.info("Attribute subscription registered with Id: " + request.getSubscriptionId()); subscriptionTypes.put(request.getSubscriptionId(), request.getAttributeTypeReference()); subscriptionListenerDirectory.put(request.getSubscriptionId(), request.getAttributeSubscriptionListener()); subscriptionFutureMap.put(request.getSubscriptionId(), request.getFuture()); if (qos instanceof HeartbeatSubscriptionInformation) { HeartbeatSubscriptionInformation heartbeat = (HeartbeatSubscriptionInformation) qos; // alerts only if alert after interval > 0 if (heartbeat.getAlertAfterIntervalMs() > 0) { logger.info("Will notify if updates are missed."); missedPublicationTimers.put(request.getSubscriptionId(), new MissedPublicationTimer(qos.getExpiryDateMs(), heartbeat.getPeriodMs(), heartbeat.getAlertAfterIntervalMs(), request.getAttributeSubscriptionListener(), subscriptionStates.get(request.getSubscriptionId()), request.getSubscriptionId())); } } SubscriptionRequest requestObject = new SubscriptionRequest(request.getSubscriptionId(), request.getAttributeName(), request.getQos()); MessagingQos messagingQos = new MessagingQos(); if (qos.getExpiryDateMs() == SubscriptionQos.NO_EXPIRY_DATE) { messagingQos.setTtl_ms(SubscriptionQos.INFINITE_SUBSCRIPTION); } else { messagingQos.setTtl_ms(qos.getExpiryDateMs() - System.currentTimeMillis()); } // TODO pass the future to the messageSender and set the error state when exceptions are thrown dispatcher.sendSubscriptionRequest(fromParticipantId, toParticipantIds, requestObject, messagingQos, false); } @Override public void registerBroadcastSubscription(String fromParticipantId, Set<String> toParticipantIds, BroadcastSubscribeInvocation request) { if (!request.hasSubscriptionId()) { request.setSubscriptionId(UUID.randomUUID().toString()); } String subscriptionId = request.getSubscriptionId(); subscriptionFutureMap.put(subscriptionId, request.getFuture()); registerSubscription(request.getQos(), subscriptionId); logger.info("Attribute subscription registered with Id: " + subscriptionId); subscriptionBroadcastTypes.put(subscriptionId, request.getOutParameterTypes()); broadcastSubscriptionListenerDirectory.put(subscriptionId, request.getBroadcastSubscriptionListener()); SubscriptionRequest requestObject = new BroadcastSubscriptionRequest(request.getSubscriptionId(), request.getBroadcastName(), request.getFilterParameters(), request.getQos()); MessagingQos messagingQos = new MessagingQos(); SubscriptionQos qos = requestObject.getQos(); if (qos.getExpiryDateMs() == SubscriptionQos.NO_EXPIRY_DATE) { messagingQos.setTtl_ms(SubscriptionQos.INFINITE_SUBSCRIPTION); } else { messagingQos.setTtl_ms(qos.getExpiryDateMs() - System.currentTimeMillis()); } dispatcher.sendSubscriptionRequest(fromParticipantId, toParticipantIds, requestObject, messagingQos, true); } @Override public void unregisterSubscription(String fromParticipantId, Set<String> toParticipantIds, String subscriptionId, MessagingQos qosSettings) { PubSubState subscriptionState = subscriptionStates.get(subscriptionId); if (subscriptionState != null) { logger.info("Called unregister / unsubscribe on subscription id= " + subscriptionId); removeSubscription(subscriptionId); } else { logger.info("Called unregister on a non/no longer existent subscription, used id= " + subscriptionId); } SubscriptionStop subscriptionStop = new SubscriptionStop(subscriptionId); dispatcher.sendSubscriptionStop(fromParticipantId, toParticipantIds, subscriptionStop, new MessagingQos(qosSettings)); } @Override public void handleBroadcastPublication(String subscriptionId, Object[] broadcastValues) { BroadcastSubscriptionListener broadcastSubscriptionListener = getBroadcastSubscriptionListener( subscriptionId); try { Class<?>[] broadcastTypes = getParameterTypesForBroadcastPublication(broadcastValues); Method receive = broadcastSubscriptionListener.getClass().getDeclaredMethod("onReceive", broadcastTypes); if (!receive.isAccessible()) { receive.setAccessible(true); } receive.invoke(broadcastSubscriptionListener, broadcastValues); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | SecurityException e) { logger.error("Broadcast publication could not be processed", e); } } @Override public <T> void handleAttributePublication(String subscriptionId, T attributeValue) { touchSubscriptionState(subscriptionId); AttributeSubscriptionListener<T> listener = getSubscriptionListener(subscriptionId); if (listener == null) { logger.error("No subscription listener found for incoming publication!"); } else { listener.onReceive(attributeValue); } } @Override public <T> void handleAttributePublicationError(String subscriptionId, JoynrRuntimeException error) { touchSubscriptionState(subscriptionId); AttributeSubscriptionListener<T> listener = getSubscriptionListener(subscriptionId); if (listener == null) { logger.error("No subscription listener found for incoming publication!"); } else { listener.onError(error); } } @Override public void handleSubscriptionReply(final SubscriptionReply subscriptionReply) { String subscriptionId = subscriptionReply.getSubscriptionId(); if (subscriptionReply.getError() == null) { if (subscriptionFutureMap.containsKey(subscriptionId)) { subscriptionFutureMap.remove(subscriptionId).onSuccess(subscriptionId); } if (subscriptionListenerDirectory.containsKey(subscriptionId)) { subscriptionListenerDirectory.get(subscriptionId).onSubscribed(subscriptionId); } else if (broadcastSubscriptionListenerDirectory.containsKey(subscriptionId)) { broadcastSubscriptionListenerDirectory.get(subscriptionId).onSubscribed(subscriptionId); } else { logger.warn( "No subscription listener found for incoming subscription reply for subscription ID {}!", subscriptionId); } } else { logger.debug("Handling subscription reply with error: {}", subscriptionReply.getError()); if (subscriptionFutureMap.containsKey(subscriptionId)) { subscriptionFutureMap.remove(subscriptionId).onFailure(subscriptionReply.getError()); } if (subscriptionListenerDirectory.containsKey(subscriptionId)) { subscriptionListenerDirectory.remove(subscriptionId).onError(subscriptionReply.getError()); } else if (broadcastSubscriptionListenerDirectory.containsKey(subscriptionId)) { broadcastSubscriptionListenerDirectory.remove(subscriptionId).onError(subscriptionReply.getError()); } else { logger.warn( "No subscription listener found for incoming subscription reply for subscription ID {}! Error message: {}", subscriptionId, subscriptionReply.getError().getMessage()); } subscriptionTypes.remove(subscriptionId); } } @Override public void touchSubscriptionState(final String subscriptionId) { logger.info("Touching subscription state for id=" + subscriptionId); if (!subscriptionStates.containsKey(subscriptionId)) { logger.debug("No subscription state found for id: " + subscriptionId); return; } PubSubState subscriptionState = subscriptionStates.get(subscriptionId); subscriptionState.updateTimeOfLastPublication(); } @SuppressWarnings("unchecked") @Override public <T> AttributeSubscriptionListener<T> getSubscriptionListener(final String subscriptionId) { if (!subscriptionStates.containsKey(subscriptionId) || !subscriptionListenerDirectory.containsKey(subscriptionId)) { logger.error("Received publication for not existing subscription callback with id=" + subscriptionId); } return (AttributeSubscriptionListener<T>) subscriptionListenerDirectory.get(subscriptionId); } @Override public BroadcastSubscriptionListener getBroadcastSubscriptionListener(String subscriptionId) { if (!subscriptionStates.containsKey(subscriptionId) || !broadcastSubscriptionListenerDirectory.containsKey(subscriptionId)) { logger.error("Received publication for not existing subscription callback with id=" + subscriptionId); } return broadcastSubscriptionListenerDirectory.get(subscriptionId); } @Override public boolean isBroadcast(String subscriptionId) { return broadcastSubscriptionListenerDirectory.containsKey(subscriptionId); } @Override public Class<?> getAttributeType(final String subscriptionId) { return subscriptionTypes.get(subscriptionId); } @Override public Class<?>[] getBroadcastOutParameterTypes(String subscriptionId) { return subscriptionBroadcastTypes.get(subscriptionId); } protected void removeSubscription(String subscriptionId) { if (missedPublicationTimers.containsKey(subscriptionId)) { missedPublicationTimers.get(subscriptionId).cancel(); missedPublicationTimers.remove(subscriptionId); } ScheduledFuture<?> future = subscriptionEndFutures.remove(subscriptionId); if (future != null) { future.cancel(true); } subscriptionStates.remove(subscriptionId); subscriptionListenerDirectory.remove(subscriptionId); subscriptionTypes.remove(subscriptionId); } private Class<?>[] getParameterTypesForBroadcastPublication(Object[] broadcastValues) { List<Class<?>> parameterTypes = new ArrayList<Class<?>>(broadcastValues.length); for (int i = 0; i < broadcastValues.length; i++) { parameterTypes.add(broadcastValues[i].getClass()); } return parameterTypes.toArray(new Class<?>[parameterTypes.size()]); } class SubscriptionEndRunnable implements Runnable { private String subscriptionId; public SubscriptionEndRunnable(String subscriptionId) { super(); this.subscriptionId = subscriptionId; } @Override public void run() { // subscription expired, stop the missed publication timer and remove it from the maps removeSubscription(subscriptionId); } } }