org.codice.ddf.catalog.subscriptionstore.common.CachedSubscription.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.catalog.subscriptionstore.common.CachedSubscription.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program 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. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.catalog.subscriptionstore.common;

import static java.lang.String.format;
import static org.apache.commons.lang.Validate.notNull;

import ddf.catalog.event.Subscription;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Optional;
import org.codice.ddf.catalog.subscriptionstore.internal.SubscriptionFactory;
import org.codice.ddf.catalog.subscriptionstore.internal.SubscriptionRegistrationException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A data object for storing subscription information in a cache.
 *
 * <p>The data stored is a mix of persisted and locally cached information. This includes the
 * original {@link Subscription} either passed from the client or deserialized from stored data, and
 * the relevant {@link SubscriptionMetadata}.
 *
 * <p>The existence of a {@link CachedSubscription} object <i>between</i> subscription CRUD
 * operations implies that:
 *
 * <ol>
 *   <li>The {@code SubscriptionMetadata} in this object is also in the {@code PersistentStore}.
 *   <li>The {@code Subscription} in this object is registered as a service with OSGi <b>only if</b>
 *       {@link #isNotRegistered()} is false.
 * </ol>
 *
 * {@code CachedSubscription} objects are not meant to be reused. They have a 1-to-1 correspondence
 * with a {@code SubscriptionMetadata} object in the backing store and are used to track that
 * subscription's lifecycle in OSGi.
 *
 * <p>Given the dynamic nature of Karaf and the need to handle {@link
 * javax.cache.event.CacheEntryEvent}s, there may be a brief period during system startup where
 * cached subscriptions exist in the cache but are not registered. This problem should rectify
 * itself as additional {@link SubscriptionFactory} instances are registered by subscription
 * endpoints.
 *
 * <p>Currently, no guarantees are made regarding the state of this object <i>during</i> a
 * subscription CRUD operation. Only one container operation may be active at a time.
 *
 * @see org.codice.ddf.catalog.subscriptionstore.SubscriptionContainerImpl
 */
public class CachedSubscription {
    private static final Logger LOGGER = LoggerFactory.getLogger(CachedSubscription.class);

    private static final String SUBSCRIPTION_ID_OSGI = "subscription-id";

    private static final String EVENT_ENDPOINT = "event-endpoint";

    private static final Integer INITIAL_CAPACITY = 4;

    private final SubscriptionMetadata metadata;

    private transient Subscription subscription;

    private transient ServiceRegistration registration;

    public CachedSubscription(SubscriptionMetadata metadata) {
        notNull(metadata, "Subscription metadata cannot be null");
        this.metadata = metadata;

        this.subscription = null;
        this.registration = null;
    }

    public SubscriptionMetadata getMetadata() {
        return metadata;
    }

    public Optional<Subscription> getSubscription() {
        return Optional.ofNullable(subscription);
    }

    public boolean isType(String type) {
        return metadata.getTypeName().equals(type);
    }

    public boolean isNotType(String type) {
        return !isType(type);
    }

    public synchronized boolean isNotRegistered() {
        return registration == null;
    }

    /**
     * Attempt to construct a {@link Subscription} using the provided factory. If the resultant
     * subscription is valid, register it as this cached subscription's OSGi service.
     *
     * @param factory subscription factory corresponding to this cached subscription's type.
     * @throws SubscriptionRegistrationException if the factory can't operate on this cached
     *     subscription.
     */
    public void registerSubscription(SubscriptionFactory factory) {
        if (isNotType(factory.getTypeName())) {
            throw new SubscriptionRegistrationException(
                    format("Factory type mismatch for subscription type [%s]", metadata.getTypeName()));
        }

        LOGGER.debug("Regenerating subscription [ {} | {} | {} ]", metadata.getId(), metadata.getTypeName(),
                metadata.getCallbackAddress());

        Subscription sub = factory.createSubscription(metadata);
        if (sub == null) {
            throw new SubscriptionRegistrationException("Unable to regenerate subscription");
        }
        registerSubscription(sub);
    }

    /**
     * Register the provided {@link Subscription} as this cached subscription's OSGi service.
     *
     * @param sub the subscription to register.
     * @throws SubscriptionRegistrationException if registration failed.
     */
    public synchronized void registerSubscription(Subscription sub) {
        if (registration != null) {
            throw new SubscriptionRegistrationException("Subscription already registered");
        }

        LOGGER.debug("Registering service [{}]", metadata.getId());

        Dictionary<String, String> properties = new Hashtable<>(INITIAL_CAPACITY);
        properties.put(SUBSCRIPTION_ID_OSGI, metadata.getId());
        properties.put(EVENT_ENDPOINT, metadata.getCallbackAddress());

        registration = getBundleContext().registerService(Subscription.class.getName(), sub, properties);

        if (registration != null) {
            LOGGER.debug("Subscription [ {} | {} | {} ] registered with bundle ID = {}", metadata.getId(),
                    metadata.getTypeName(), metadata.getCallbackAddress(),
                    registration.getReference().getBundle().getBundleId());
            subscription = sub;
        } else {
            throw new SubscriptionRegistrationException(
                    format("Subscription registration attempt for id [%s] of type [%s] failed", metadata.getId(),
                            metadata.getTypeName()));
        }
    }

    /**
     * Removes this cached subscriptions OSGi service from the service registry. Calling this method
     * on a cached subscription that {@link #isNotRegistered()} will throw an exception.
     *
     * @throws SubscriptionRegistrationException if this cached subscription did not have a service
     *     registered.
     */
    public synchronized void unregisterSubscription() {
        LOGGER.debug("Removing service [{}]", metadata.getId());
        if (registration == null) {
            throw new SubscriptionRegistrationException(format(
                    "Subscription [%s] had no registration and could not be unregistered", metadata.getId()));
        }
        registration.unregister();
        registration = null;
    }

    protected BundleContext getBundleContext() {
        return FrameworkUtil.getBundle(CachedSubscription.class).getBundleContext();
    }
}