Package org.apache.sling.discovery.impl

Source Code of org.apache.sling.discovery.impl.DiscoveryServiceImpl$ProviderInfo

/*
* 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.impl;

import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
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.ClusterView;
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.TopologyView;
import org.apache.sling.discovery.impl.cluster.ClusterViewService;
import org.apache.sling.discovery.impl.common.heartbeat.HeartbeatHandler;
import org.apache.sling.discovery.impl.common.resource.ResourceHelper;
import org.apache.sling.discovery.impl.topology.TopologyViewImpl;
import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
import org.apache.sling.discovery.impl.topology.connector.ConnectorRegistry;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.framework.Constants;
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(value = { DiscoveryService.class, DiscoveryServiceImpl.class })
public class DiscoveryServiceImpl implements DiscoveryService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Reference
    private SlingSettingsService settingsService;

    @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, referenceInterface = TopologyEventListener.class)
    private TopologyEventListener[] eventListeners = new TopologyEventListener[0];

    /**
     * All property providers.
     */
    @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, referenceInterface = PropertyProvider.class, updated = "updatedPropertyProvider")
    private List<ProviderInfo> providerInfos = new ArrayList<ProviderInfo>();

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

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

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Reference
    private Scheduler scheduler;

    @Reference
    private HeartbeatHandler heartbeatHandler;

    @Reference
    private AnnouncementRegistry announcementRegistry;

    @Reference
    private ConnectorRegistry connectorRegistry;

    @Reference
    private ClusterViewService clusterViewService;

    @Reference
    private Config config;

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

    /** the old view previously valid and sent to the TopologyEventListeners **/
    private TopologyViewImpl oldView;

    /** whether or not there is a delayed event sending pending **/
    private boolean delayedEventPending = false;

    /**
     * Activate this service
     */
    @Activate
    protected void activate() {
        logger.debug("DiscoveryServiceImpl activating...");

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

        slingId = settingsService.getSlingId();

        oldView = (TopologyViewImpl) getTopology();

        // 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).
        heartbeatHandler.initialize(this,
                clusterViewService.getIsolatedClusterViewId());

        final TopologyEventListener[] registeredServices;
        synchronized (lock) {
            registeredServices = this.eventListeners;
            doUpdateProperties();

            TopologyViewImpl newView = (TopologyViewImpl) getTopology();
            TopologyEvent event = new TopologyEvent(Type.TOPOLOGY_INIT, null,
                    newView);
            for (final TopologyEventListener da : registeredServices) {
                sendTopologyEvent(da, event);
            }
            activated = true;
            oldView = newView;
        }

        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("DiscoveryServiceImpl activated.");
    }

    private void sendTopologyEvent(final TopologyEventListener da, final TopologyEvent event) {
      if (logger.isDebugEnabled()) {
        logger.debug("sendTopologyEvent: sending topologyEvent {}, to {}", event, da);
      }
        try{
            da.handleTopologyEvent(event);
        } catch(final Exception e) {
            logger.warn("sendTopologyEvent: handler threw exception. handler: "+da+", exception: "+e, e);
        }
    }

    /**
     * Deactivate this service
     */
    @Deactivate
    protected void deactivate() {
        logger.debug("DiscoveryServiceImpl deactivated.");
        synchronized (lock) {
            activated = false;
        }
    }

    /**
     * bind a topology event listener
     */
    protected void bindTopologyEventListener(final TopologyEventListener eventListener) {

        logger.debug("bindTopologyEventListener: Binding TopologyEventListener {}",
                eventListener);

        boolean activated = false;
        synchronized (lock) {
            final List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>(
                    Arrays.asList(eventListeners));
            currentList.add(eventListener);
            this.eventListeners = currentList
                    .toArray(new TopologyEventListener[currentList.size()]);
            activated = this.activated;
        }

        if (activated) {
            sendTopologyEvent(eventListener, new TopologyEvent(
                    Type.TOPOLOGY_INIT, null, getTopology()));
        }
    }

    /**
     * Unbind a topology event listener
     */
    protected void unbindTopologyEventListener(final TopologyEventListener eventListener) {

        logger.debug("unbindTopologyEventListener: Releasing TopologyEventListener {}",
                eventListener);

        synchronized (lock) {
            final List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>(
                    Arrays.asList(eventListeners));
            currentList.remove(eventListener);
            this.eventListeners = currentList
                    .toArray(new TopologyEventListener[currentList.size()]);
        }
    }

    /**
     * Bind a new property provider.
     */
    protected void bindPropertyProvider(final PropertyProvider propertyProvider,
                                        final Map<String, Object> props) {
        logger.debug("bindPropertyProvider: Binding PropertyProvider {}",
                propertyProvider);

        synchronized (lock) {
            this.bindPropertyProviderInteral(propertyProvider, props);
        }
    }

    /**
     * Bind a new property provider.
     */
    private void bindPropertyProviderInteral(final PropertyProvider propertyProvider,
            final Map<String, Object> props) {
        final ProviderInfo info = new ProviderInfo(propertyProvider, props);
        this.providerInfos.add(info);
        Collections.sort(this.providerInfos);
        this.doUpdateProperties();
        handlePotentialTopologyChange();
    }

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

        synchronized (lock) {
           this.unbindPropertyProviderInternal(propertyProvider, props, false);
           this.bindPropertyProviderInteral(propertyProvider, props);
        }
    }

    /**
     * Unbind a property provider
     */
    protected void unbindPropertyProvider(final PropertyProvider propertyProvider,
                                          final Map<String, Object> props) {
        logger.debug("unbindPropertyProvider: Releasing PropertyProvider {}",
                propertyProvider);
        synchronized (lock) {
            this.unbindPropertyProviderInternal(propertyProvider, props, true);
        }
    }

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

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

    /**
     * Update the properties by inquiring the PropertyProvider's current values.
     * <p>
     * This method is invoked regularly by the heartbeatHandler.
     * The properties are stored in the repository under Config.getClusterInstancesPath()
     * and announced in the topology.
     * <p>
     * @see Config#getClusterInstancesPath()
     */
    private void doUpdateProperties() {
        if (resourceResolverFactory == null) {
            // cannot update the properties then..
            logger.debug("doUpdateProperties: too early to update the properties. resourceResolverFactory not yet set.");
            return;
        } else {
            logger.debug("doUpdateProperties: updating properties now..");
        }

        final Map<String, String> newProps = new HashMap<String, String>();
        for (final ProviderInfo info : this.providerInfos) {
            info.refreshProperties();
            newProps.putAll(info.properties);
        }

        ResourceResolver resourceResolver = null;
        try {
            resourceResolver = resourceResolverFactory
                    .getAdministrativeResourceResolver(null);

            Resource myInstance = ResourceHelper
                    .getOrCreateResource(
                            resourceResolver,
                            config.getClusterInstancesPath()
                                    + "/" + slingId + "/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<String> keys = new HashSet<String>(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<String, String> 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.");
    }

    /**
     * @see DiscoveryService#getTopology()
     */
    public TopologyView getTopology() {
        if (clusterViewService == null) {
            throw new IllegalStateException(
                    "DiscoveryService not yet initialized with IClusterViewService");
        }
        // create a new topology view
        final TopologyViewImpl topology = new TopologyViewImpl();

        final ClusterView localClusterView = clusterViewService.getClusterView();

        final List<InstanceDescription> localInstances = localClusterView.getInstances();
        topology.addInstances(localInstances);

        Collection<InstanceDescription> attachedInstances = announcementRegistry
                .listInstances(localClusterView);
        topology.addInstances(attachedInstances);

        // TODO: isCurrent() might be wrong!!!

        return topology;
    }

    /**
     * Update the properties and sent a topology event if applicable
     */
    public void updateProperties() {
        synchronized (lock) {
            logger.debug("updateProperties: calling doUpdateProperties.");
            doUpdateProperties();
            logger.debug("updateProperties: calling handlePotentialTopologyChange.");
            handlePotentialTopologyChange();
            logger.debug("updateProperties: done.");
        }
    }

    /**
     * Internal handle method which checks if anything in the topology has
     * changed and informs the TopologyEventListeners if such a change occurred.
     * <p>
     * All changes should go through this method. This method keeps track of
     * oldView/newView as well.
     */
    private void handlePotentialTopologyChange() {
        if (!activated) {
            // ignore this call then - an early call to issue
            // a topologyevent before even activated
            logger.debug("handlePotentialTopologyChange: ignoring early change before activate finished.");
            return;
        }
        if (delayedEventPending) {
            logger.debug("handlePotentialTopologyChange: ignoring potential change since a delayed event is pending.");
            return;
        }
        if (oldView == null) {
            throw new IllegalStateException("oldView must not be null");
        }
        TopologyViewImpl newView = (TopologyViewImpl) getTopology();
        TopologyViewImpl oldView = this.oldView;

        Type difference = newView.compareTopology(oldView);
        if (difference == null) {
            // then dont send any event then
            logger.debug("handlePotentialTopologyChange: identical views. not informing listeners");
            return;
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("handlePotentialTopologyChange: difference: {}, oldView={}, newView={}",
                        new Object[] {difference, oldView, newView});
            }
        }

        oldView.markOld();
        if (difference!=Type.TOPOLOGY_CHANGED) {
            for (final TopologyEventListener da : eventListeners) {
                sendTopologyEvent(da, new TopologyEvent(difference, oldView,
                        newView));
            }
        } else { // TOPOLOGY_CHANGED

          // send a TOPOLOGY_CHANGING first
            for (final TopologyEventListener da : eventListeners) {
                sendTopologyEvent(da, new TopologyEvent(Type.TOPOLOGY_CHANGING, oldView,
                        null));
            }

          if (config.getMinEventDelay()>0) {
                // then delay the sending of the next event
                logger.debug("handlePotentialTopologyChange: delaying event sending to avoid event flooding");

                if (runAfter(config.getMinEventDelay() /*seconds*/ , new Runnable() {

                    public void run() {
                        synchronized(lock) {
                          delayedEventPending = false;
                          logger.debug("handlePotentialTopologyChange: sending delayed event now");
                          if (!activated) {
                            logger.debug("handlePotentialTopologyChange: no longer activated. not sending delayed event");
                            return;
                          }
                            final TopologyViewImpl newView = (TopologyViewImpl) getTopology();
                            // irrespective of the difference, send the latest topology
                            // via a topology_changed event (since we already sent a changing)
                            for (final TopologyEventListener da : eventListeners) {
                                sendTopologyEvent(da, new TopologyEvent(Type.TOPOLOGY_CHANGED,
                                        DiscoveryServiceImpl.this.oldView, newView));
                            }
                            DiscoveryServiceImpl.this.oldView = newView;
                        }
                        if (heartbeatHandler!=null) {
                            // trigger a heartbeat 'now' to pass it on to the topology asap
                            heartbeatHandler.triggerHeartbeat();
                        }
                    }
                })) {
                  delayedEventPending = true;
                    logger.debug("handlePotentialTopologyChange: delaying of event triggered.");
                    return;
                } else {
                  logger.debug("handlePotentialTopologyChange: delaying did not work for some reason.");
                }
          }

          // otherwise, send the TOPOLOGY_CHANGED now
            for (final TopologyEventListener da : eventListeners) {
                sendTopologyEvent(da, new TopologyEvent(Type.TOPOLOGY_CHANGED, oldView,
                        newView));
            }
        }

        this.oldView = newView;
        if (heartbeatHandler!=null) {
            // trigger a heartbeat 'now' to pass it on to the topology asap
            heartbeatHandler.triggerHeartbeat();
        }
    }

    /**
     * run the runnable after the indicated number of seconds, once.
     * @return true if the scheduling of the runnable worked, false otherwise
     */
    private boolean runAfter(int seconds, final Runnable runnable) {
        final Scheduler theScheduler = scheduler;
      if (theScheduler == null) {
        logger.info("runAfter: no scheduler set");
        return false;
      }
      logger.debug("runAfter: trying with scheduler.fireJob");
      final Date date = new Date(System.currentTimeMillis() + seconds * 1000);
    try {
        theScheduler.fireJobAt(null, runnable, null, date);
      return true;
    } catch (Exception e) {
      logger.info("runAfter: could not schedule a job: "+e);
      return false;
    }
    }

    /**
     * Internal class caching some provider infos like service id and ranking.
     */
    private final static class ProviderInfo implements Comparable<ProviderInfo> {

        public final PropertyProvider provider;
        public final Object propertyProperties;
        public final int ranking;
        public final long serviceId;
        public final Map<String, String> properties = new HashMap<String, String>();

        public ProviderInfo(final PropertyProvider provider,
                final Map<String, Object> 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 (ResourceHelper.isValidPropertyName(name)) {
        this.properties.put(name, val);
      }
    }

    /**
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        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();
        }
    }

    /**
     * Handle the fact that the topology has likely changed
     */
    public void handleTopologyChanged() {
        logger.debug("handleTopologyChanged: calling handlePotentialTopologyChange.");
        synchronized ( this.lock ) {
            handlePotentialTopologyChange();
        }
    }

    /** SLING-2901 : send a TOPOLOGY_CHANGING event and shutdown the service thereafter **/
  public void forcedShutdown() {
    synchronized(lock) {
          if (!activated) {
              logger.error("forcedShutdown: ignoring forced shutdown. Service is not activated.");
              return;
          }
          if (oldView == null) {
              logger.error("forcedShutdown: ignoring forced shutdown. No oldView available.");
              return;
          }
          logger.error("forcedShutdown: sending TOPOLOGY_CHANGING to all listeners");
            for (final TopologyEventListener da : eventListeners) {
                sendTopologyEvent(da, new TopologyEvent(Type.TOPOLOGY_CHANGING, oldView,
                        null));
            }
          logger.error("forcedShutdown: deactivating DiscoveryService.");
          // to make sure no further event is sent after this, flag this service as deactivated
            activated = false;
    }
  }

}
TOP

Related Classes of org.apache.sling.discovery.impl.DiscoveryServiceImpl$ProviderInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.