All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.discovery.oak.OakDiscoveryService Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.sling.discovery.oak;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.scheduler.Scheduler;
import org.apache.sling.discovery.DiscoveryService;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.PropertyProvider;
import org.apache.sling.discovery.TopologyEvent;
import org.apache.sling.discovery.TopologyEvent.Type;
import org.apache.sling.discovery.TopologyEventListener;
import org.apache.sling.discovery.base.commons.BaseDiscoveryService;
import org.apache.sling.discovery.base.commons.ClusterViewService;
import org.apache.sling.discovery.base.commons.DefaultTopologyView;
import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
import org.apache.sling.discovery.commons.providers.DefaultClusterView;
import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
import org.apache.sling.discovery.commons.providers.ViewStateManager;
import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
import org.apache.sling.discovery.commons.providers.spi.base.ClusterSyncHistory;
import org.apache.sling.discovery.commons.providers.spi.base.ClusterSyncServiceChain;
import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogClusterSyncService;
import org.apache.sling.discovery.commons.providers.spi.base.SyncTokenService;
import org.apache.sling.discovery.commons.providers.util.PropertyNameHelper;
import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
import org.apache.sling.discovery.oak.pinger.OakViewChecker;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This implementation of the cross-cluster service uses the view manager
 * implementation for detecting changes in a cluster and only supports one
 * cluster (of which this instance is part of).
 */
@Component(immediate = true, service = {DiscoveryService.class, OakDiscoveryService.class})
public class OakDiscoveryService extends BaseDiscoveryService {

    private final static Logger logger = LoggerFactory.getLogger(OakDiscoveryService.class);

    @Reference
    private SlingSettingsService settingsService;

    /**
     * All property providers.
     */
    private List providerInfos = new ArrayList();

    /**
     * lock object used for syncing bind/unbind and topology event sending
     **/
    private final Object lock = new Object();

    /**
     * whether this service is activated - necessary to avoid sending
     * events to discovery awares before activate is done
     **/
    private volatile boolean activated = false;

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Reference
    private Scheduler scheduler;

    @Reference
    private OakViewChecker oakViewChecker;

    @Reference
    private AnnouncementRegistry announcementRegistry;

    @Reference
    private ConnectorRegistry connectorRegistry;

    @Reference
    private ClusterViewService clusterViewService;

    @Reference
    private Config config;

    @Reference
    private IdMapService idMapService;

    @Reference
    private OakBacklogClusterSyncService oakBacklogClusterSyncService;

    @Reference
    private SyncTokenService syncTokenService;

    /**
     * the slingId of the local instance
     **/
    private String slingId;

    private ServiceRegistration mbeanRegistration;

    private ViewStateManager viewStateManager;

    private final ReentrantLock viewStateManagerLock = new ReentrantLock();

    private final List pendingListeners = new LinkedList();

    private TopologyEventListener changePropagationListener = new TopologyEventListener() {

        @Override
        public void handleTopologyEvent(TopologyEvent event) {
            OakViewChecker checker = oakViewChecker;
            if (activated && checker != null
                    && (event.getType() == Type.TOPOLOGY_CHANGED || event.getType() == Type.PROPERTIES_CHANGED)) {
                logger.debug("changePropagationListener.handleTopologyEvent: topology changed - propagate through connectors");
                checker.triggerAsyncConnectorPing();
            }
        }
    };

    public static OakDiscoveryService testConstructor(SlingSettingsService settingsService,
                                                      AnnouncementRegistry announcementRegistry,
                                                      ConnectorRegistry connectorRegistry,
                                                      ClusterViewService clusterViewService,
                                                      Config config,
                                                      OakViewChecker connectorPinger,
                                                      Scheduler scheduler,
                                                      IdMapService idMapService,
                                                      OakBacklogClusterSyncService oakBacklogClusterSyncService,
                                                      SyncTokenService syncTokenService,
                                                      ResourceResolverFactory factory) {
        OakDiscoveryService discoService = new OakDiscoveryService();
        discoService.settingsService = settingsService;
        discoService.announcementRegistry = announcementRegistry;
        discoService.connectorRegistry = connectorRegistry;
        discoService.clusterViewService = clusterViewService;
        discoService.config = config;
        discoService.oakViewChecker = connectorPinger;
        discoService.scheduler = scheduler;
        discoService.idMapService = idMapService;
        discoService.oakBacklogClusterSyncService = oakBacklogClusterSyncService;
        discoService.syncTokenService = syncTokenService;
        discoService.resourceResolverFactory = factory;
        return discoService;
    }

    @Override
    protected void handleIsolatedFromTopology() {
        if (oakViewChecker != null) {
            // SLING-5030 part 2: when we detect being isolated we should
            // step at the end of the leader-election queue and
            // that can be achieved by resetting the leaderElectionId
            // (which will in turn take effect on the next round of
            // voting, or also double-checked when the local instance votes)
            //
            //TODO:
            // Note that when the local instance doesn't notice
            // an 'ISOLATED_FROM_TOPOLOGY' case, then the leaderElectionId
            // will not be reset. Which means that it then could potentially
            // regain leadership.
            if (oakViewChecker.resetLeaderElectionId()) {
                logger.info("getTopology: reset leaderElectionId to force this instance to the end of the instance order (thus incl not to remain leader)");
            }
        }
    }

    /**
     * Activate this service
     */
    @Activate
    protected void activate(final BundleContext bundleContext) {
        logger.debug("OakDiscoveryService activating...");

        if (settingsService == null) {
            throw new IllegalStateException("settingsService not found");
        }
        if (oakViewChecker == null) {
            throw new IllegalStateException("heartbeatHandler not found");
        }

        slingId = settingsService.getSlingId();

        ClusterSyncService consistencyService;
        if (config.getSyncTokenEnabled()) {
            //TODO: ConsistencyHistory is implemented a little bit hacky ..
            ClusterSyncHistory consistencyHistory = new ClusterSyncHistory();
            oakBacklogClusterSyncService.setConsistencyHistory(consistencyHistory);
            syncTokenService.setConsistencyHistory(consistencyHistory);
            JoinerDelay joinerDelay = new JoinerDelay(config.getJoinerDelayMillis(), scheduler);
            consistencyService = new ClusterSyncServiceChain(oakBacklogClusterSyncService, syncTokenService, joinerDelay);
        } else {
            consistencyService = oakBacklogClusterSyncService;

        }
        viewStateManager = ViewStateManagerFactory.newViewStateManager(viewStateManagerLock, consistencyService);

        if (config.getMinEventDelay() > 0) {
            viewStateManager.installMinEventDelayHandler(this, scheduler, config.getMinEventDelay());
        }

        final String isolatedClusterId = UUID.randomUUID().toString();
        {
            // create a pre-voting/isolated topologyView which would be used until the first voting has finished.
            // this way for the single-instance case the clusterId can
            // remain the same between a getTopology() that is invoked before the first TOPOLOGY_INIT and afterwards
            DefaultClusterView isolatedCluster = new DefaultClusterView(isolatedClusterId);
            Map emptyProperties = new HashMap<>();
            DefaultInstanceDescription isolatedInstance =
                    new DefaultInstanceDescription(isolatedCluster, true, true, slingId, emptyProperties);
            Collection col = new ArrayList<>();
            col.add(isolatedInstance);
            final DefaultTopologyView topology = new DefaultTopologyView();
            topology.addInstances(col);
            topology.setNotCurrent();
            setOldView(topology);
        }
        setOldView((DefaultTopologyView) getTopology());
        getOldView().setNotCurrent();

        // make sure the first heartbeat is issued as soon as possible - which
        // is right after this service starts. since the two (discoveryservice
        // and heartbeatHandler need to know each other, the discoveryservice
        // is passed on to the heartbeatHandler in this initialize call).
        oakViewChecker.initialize(this);

        viewStateManagerLock.lock();
        try {
            viewStateManager.handleActivated();

            doUpdateProperties();

            DefaultTopologyView newView = (DefaultTopologyView) getTopology();
            if (newView.isCurrent()) {
                viewStateManager.handleNewView(newView);
            } else {
                // SLING-3750: just issue a log.info about the delaying
                logger.info("activate: this instance is in isolated mode and must yet finish voting before it can send out TOPOLOGY_INIT.");
            }
            activated = true;
            setOldView(newView);

            // in case bind got called before activate we now have pending listeners,
            // bind them to the viewstatemanager too
            for (TopologyEventListener listener : pendingListeners) {
                viewStateManager.bind(listener);
            }
            pendingListeners.clear();

            viewStateManager.bind(changePropagationListener);
        } finally {
            if (viewStateManagerLock != null) {
                viewStateManagerLock.unlock();
            }
        }

        URL[] topologyConnectorURLs = config.getTopologyConnectorURLs();
        if (topologyConnectorURLs != null) {
            for (int i = 0; i < topologyConnectorURLs.length; i++) {
                final URL aURL = topologyConnectorURLs[i];
                if (aURL != null) {
                    try {
                        logger.info("activate: registering outgoing topology connector to " + aURL);
                        connectorRegistry.registerOutgoingConnector(clusterViewService, aURL);
                    } catch (final Exception e) {
                        logger.info("activate: could not register url: " + aURL + " due to: " + e, e);
                    }
                }
            }
        }

        logger.debug("OakDiscoveryService activated.");
    }

    /**
     * Deactivate this service
     */
    @Deactivate
    protected void deactivate() {
        logger.debug("OakDiscoveryService deactivated.");
        viewStateManagerLock.lock();
        try {
            viewStateManager.unbind(changePropagationListener);

            viewStateManager.handleDeactivated();

            activated = false;
        } finally {
            if (viewStateManagerLock != null) {
                viewStateManagerLock.unlock();
            }
        }
        try {
            if (this.mbeanRegistration != null) {
                this.mbeanRegistration.unregister();
                this.mbeanRegistration = null;
            }
        } catch (Exception e) {
            logger.error("deactivate: Error on unregister: " + e, e);
        }
    }

    /**
     * bind a topology event listener
     */
    @Reference(name = "eventListeners",
            service = TopologyEventListener.class,
            cardinality = ReferenceCardinality.MULTIPLE,
            policy = ReferencePolicy.DYNAMIC,
            bind = "bindTopologyEventListener", unbind = "bindTopologyEventListener")
    protected void bindTopologyEventListener(final TopologyEventListener eventListener) {
        viewStateManagerLock.lock();
        try {
            if (!activated) {
                pendingListeners.add(eventListener);
            } else {
                viewStateManager.bind(eventListener);
            }
        } finally {
            if (viewStateManagerLock != null) {
                viewStateManagerLock.unlock();
            }
        }
    }

    /**
     * Unbind a topology event listener
     */
    protected void unbindTopologyEventListener(final TopologyEventListener eventListener) {
        viewStateManagerLock.lock();
        try {
            if (!activated) {
                pendingListeners.remove(eventListener);
            } else {
                viewStateManager.unbind(eventListener);
            }
        } finally {
            if (viewStateManagerLock != null) {
                viewStateManagerLock.unlock();
            }
        }
    }

    /**
     * Bind a new property provider.
     */
    @Reference(name = "providerInfos",
            service = PropertyProvider.class,
            cardinality = ReferenceCardinality.MULTIPLE,
            policy = ReferencePolicy.DYNAMIC,
            bind = "bindPropertyProvider", unbind = "unbindPropertyProvider", updated = "updatedPropertyProvider")
    protected void bindPropertyProvider(final PropertyProvider propertyProvider, final Map props) {
        logger.debug("bindPropertyProvider: Binding PropertyProvider {}", propertyProvider);

        synchronized (lock) {
            try {
                this.bindPropertyProviderInteral(propertyProvider, props);
            } catch (RuntimeException re) {
                // SLING-10204 : catch and instead log less noisy as this can legitimately happen
                logger.warn("bindPropertyProvider: got RuntimeException (enable debug logging to see stacktrace) : " + re);
                logger.debug("bindPropertyProvider: RuntimeException stacktrace", re);
            }
        }
    }

    /**
     * Bind a new property provider.
     */
    private void bindPropertyProviderInteral(final PropertyProvider propertyProvider, final Map props) {
        final ProviderInfo info = new ProviderInfo(propertyProvider, props);
        this.providerInfos.add(info);
        Collections.sort(this.providerInfos);
        if (activated) {
            this.doUpdateProperties();
        }
        checkForTopologyChange();
    }

    /**
     * Update a property provider.
     */
    protected void updatedPropertyProvider(final PropertyProvider propertyProvider, final Map props) {
        logger.debug("bindPropertyProvider: Updating PropertyProvider {}", propertyProvider);

        synchronized (lock) {
            try {
                this.unbindPropertyProviderInternal(propertyProvider, props, false);
                this.bindPropertyProviderInteral(propertyProvider, props);
            } catch (RuntimeException re) {
                // SLING-10204 : catch and instead log less noisy as this can legitimately happen
                logger.warn("updatePropertyProvider: got RuntimeException (enable debug logging to see stacktrace) : " + re);
                logger.debug("updatePropertyProvider: RuntimeException stacktrace", re);
            }
        }
    }

    /**
     * Unbind a property provider
     */
    protected void unbindPropertyProvider(final PropertyProvider propertyProvider, final Map props) {
        logger.debug("unbindPropertyProvider: Releasing PropertyProvider {}", propertyProvider);
        synchronized (lock) {
            try {
                this.unbindPropertyProviderInternal(propertyProvider, props, true);
            } catch (RuntimeException re) {
                // SLING-10204 : catch and instead log less noisy as this can legitimately happen
                logger.warn("unbindPropertyProvider: got RuntimeException (enable debug logging to see stacktrace) : " + re);
                logger.debug("unbindPropertyProvider: RuntimeException stacktrace", re);
            }
        }
    }

    /**
     * Unbind a property provider
     */
    private void unbindPropertyProviderInternal(
            final PropertyProvider propertyProvider,
            final Map props, final boolean update) {

        final ProviderInfo info = new ProviderInfo(propertyProvider, props);
        if (this.providerInfos.remove(info) && update) {
            if (activated) {
                this.doUpdateProperties();
            }
            this.checkForTopologyChange();
        }
    }

    /**
     * Update the properties by inquiring the PropertyProvider's current values.
     * 

* This method is invoked regularly by the heartbeatHandler. * The properties are stored in the repository under Config.getClusterInstancesPath() * and announced in the topology. *

* * @see Config#getClusterInstancesPath() */ private void doUpdateProperties() { // SLING-5382 : the caller must ensure that this method // is not invoked after deactivation or before activation. // so this method doesn't have to do any further synchronization. // what we do nevertheless is a paranoia way of checking if // all variables are available and do a NOOP if that's not the case. final ResourceResolverFactory rrf = resourceResolverFactory; final Config c = config; final String sid = slingId; if (rrf == null || c == null || sid == null) { // cannot update the properties then.. logger.debug("doUpdateProperties: too early to update the properties. " + "resourceResolverFactory ({}), config ({}) or slingId ({}) not yet set.", new Object[]{rrf, c, sid}); return; } else { logger.debug("doUpdateProperties: updating properties now.."); } final Map newProps = new HashMap<>(); for (final ProviderInfo info : this.providerInfos) { info.refreshProperties(); newProps.putAll(info.properties); } ResourceResolver resourceResolver = null; try { resourceResolver = rrf.getServiceResourceResolver(null); Resource myInstance = ResourceHelper .getOrCreateResource(resourceResolver, c.getClusterInstancesPath() + "/" + sid + "/properties"); // SLING-2879 - revert/refresh resourceResolver here to work // around a potential issue with jackrabbit in a clustered environment resourceResolver.revert(); resourceResolver.refresh(); final ModifiableValueMap myInstanceMap = myInstance.adaptTo(ModifiableValueMap.class); final Set keys = new HashSet(myInstanceMap.keySet()); for (final String key : keys) { if (newProps.containsKey(key)) { // perfect continue; } else if (key.indexOf(":") != -1) { // ignore continue; } else { // remove myInstanceMap.remove(key); } } boolean anyChanges = false; for (final Entry entry : newProps.entrySet()) { Object existingValue = myInstanceMap.get(entry.getKey()); if (entry.getValue().equals(existingValue)) { // SLING-3389: dont rewrite the properties if nothing changed! if (logger.isDebugEnabled()) { logger.debug("doUpdateProperties: unchanged: {}={}", entry.getKey(), entry.getValue()); } continue; } if (logger.isDebugEnabled()) { logger.debug("doUpdateProperties: changed: {}={}", entry.getKey(), entry.getValue()); } anyChanges = true; myInstanceMap.put(entry.getKey(), entry.getValue()); } if (anyChanges) { resourceResolver.commit(); } } catch (LoginException e) { logger.error("handleEvent: could not log in administratively: " + e, e); throw new RuntimeException("Could not log in to repository (" + e + ")", e); } catch (PersistenceException e) { logger.error("handleEvent: got a PersistenceException: " + e, e); throw new RuntimeException( "Exception while talking to repository (" + e + ")", e); } finally { if (resourceResolver != null) { resourceResolver.close(); } } logger.debug("doUpdateProperties: updating properties done."); } /** * Update the properties and sent a topology event if applicable */ public void updateProperties() { synchronized (lock) { if (!activated) { logger.debug("updateProperties: not yet activated, not calling doUpdateProperties this time."); } else { logger.debug("updateProperties: calling doUpdateProperties."); doUpdateProperties(); } logger.debug("updateProperties: calling handlePotentialTopologyChange."); checkForTopologyChange(); logger.debug("updateProperties: done."); } } /** * Internal class caching some provider infos like service id and ranking. */ private final static class ProviderInfo implements Comparable { public final PropertyProvider provider; public final Object propertyProperties; public final int ranking; public final long serviceId; public final Map properties = new HashMap(); public ProviderInfo(final PropertyProvider provider, final Map serviceProps) { this.provider = provider; this.propertyProperties = serviceProps.get(PropertyProvider.PROPERTY_PROPERTIES); final Object sr = serviceProps.get(Constants.SERVICE_RANKING); if (sr == null || !(sr instanceof Integer)) { this.ranking = 0; } else { this.ranking = (Integer) sr; } this.serviceId = (Long) serviceProps.get(Constants.SERVICE_ID); refreshProperties(); } public void refreshProperties() { properties.clear(); if (this.propertyProperties instanceof String) { final String val = provider.getProperty((String) this.propertyProperties); if (val != null) { putPropertyIfValid((String) this.propertyProperties, val); } } else if (this.propertyProperties instanceof String[]) { for (final String name : (String[]) this.propertyProperties) { final String val = provider.getProperty(name); if (val != null) { putPropertyIfValid(name, val); } } } } /** * SLING-2883 : put property only if valid **/ private void putPropertyIfValid(final String name, final String val) { if (PropertyNameHelper.isValidPropertyName(name)) { this.properties.put(name, val); } } /** * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(final ProviderInfo o) { // Sort by rank in ascending order. if (this.ranking < o.ranking) { return -1; // lower rank } else if (this.ranking > o.ranking) { return 1; // higher rank } // If ranks are equal, then sort by service id in descending order. return (this.serviceId < o.serviceId) ? 1 : -1; } @Override public boolean equals(final Object obj) { if (obj instanceof ProviderInfo) { return ((ProviderInfo) obj).serviceId == this.serviceId; } return false; } @Override public int hashCode() { return provider.hashCode(); } } /** * Check the current topology for any potential change */ public void checkForTopologyChange() { viewStateManagerLock.lock(); try { if (!activated) { logger.debug("checkForTopologyChange: not yet activated, ignoring"); return; } DefaultTopologyView t = (DefaultTopologyView) getTopology(); if (t.isCurrent()) { // if we have a valid view, let the viewStateManager do the // comparison and sending of an event, if necessary viewStateManager.handleNewView(t); setOldView(t); } else { // if we don't have a view, then we might have to send // a CHANGING event, let that be decided by the viewStateManager as well viewStateManager.handleChanging(); } } finally { if (viewStateManagerLock != null) { viewStateManagerLock.unlock(); } } } /** * Handle the fact that the topology has started to change - inform the listeners asap */ public void handleTopologyChanging() { logger.debug("handleTopologyChanging: invoking viewStateManager.handlechanging"); viewStateManager.handleChanging(); } @Override protected ClusterViewService getClusterViewService() { return clusterViewService; } @Override protected AnnouncementRegistry getAnnouncementRegistry() { return announcementRegistry; } /** * for testing only * * @return */ public ViewStateManager getViewStateManager() { return viewStateManager; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy