001/**
002 * Copyright 2005-2017 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.ksb.impl.bus.diff;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import javax.xml.namespace.QName;
024
025import org.apache.commons.collections.MapUtils;
026import org.apache.log4j.Logger;
027import org.kuali.rice.ksb.api.registry.ServiceInfo;
028import org.kuali.rice.ksb.api.registry.ServiceRegistry;
029import org.kuali.rice.ksb.impl.bus.LocalService;
030import org.kuali.rice.ksb.impl.bus.RemoteService;
031
032/**
033 * Default implementation of the {@link ServiceRegistryDiffCalculator} which calculates
034 * differences between client service bus state and service registry state.
035 * 
036 * @author Kuali Rice Team (rice.collab@kuali.org)
037 *
038 */
039public class ServiceRegistryDiffCalculatorImpl implements ServiceRegistryDiffCalculator {
040
041        private static final Logger LOG = Logger.getLogger(ServiceRegistryDiffCalculatorImpl.class);
042        
043        private ServiceRegistry serviceRegistry;
044        
045        public void setServiceRegistry(ServiceRegistry serviceRegistry) {
046                this.serviceRegistry = serviceRegistry;
047        }
048        
049        @Override
050        public CompleteServiceDiff diffServices(String instanceId, List<LocalService> localServices, List<RemoteService> clientRegistryCache) {
051        List<ServiceInfo> allRegistryServices = serviceRegistry.getAllOnlineServices();
052        List<ServiceInfo> allRegistryServicesForInstance = serviceRegistry.getAllServicesForInstance(instanceId);
053                LocalServicesDiff localServicesDiff = calculateLocalServicesDiff(allRegistryServicesForInstance, instanceId, localServices);
054                RemoteServicesDiff remoteServicesDiff = calculateRemoteServicesDiff(allRegistryServices, clientRegistryCache);
055                return new CompleteServiceDiff(localServicesDiff, remoteServicesDiff);
056        }
057
058        protected LocalServicesDiff calculateLocalServicesDiff(List<ServiceInfo> allRegistryServicesForInstance, String instanceId, List<LocalService> localServices) {
059                
060                List<ServiceInfo> servicesToRemoveFromRegistry = new ArrayList<ServiceInfo>();
061                List<LocalService> localServicesToPublish = new ArrayList<LocalService>();
062                Map<LocalService, ServiceInfo> localServicesToUpdate = new HashMap<LocalService, ServiceInfo>();
063                
064                Map<QName, LocalService> localServiceIndex = indexLocalServices(instanceId, localServices);
065                for (ServiceInfo serviceInfo : allRegistryServicesForInstance) {
066                        // first validate that the service has a valid instance id
067                        if (!instanceId.equals(serviceInfo.getInstanceId())) {
068                StringBuffer errorMessage = new StringBuffer("ServiceInfo given for local service diff does not have a valid instance id.  Should have been '" + instanceId + "' but was '" + serviceInfo.getInstanceId() + "'");
069                if (serviceInfo.getInstanceId() == null) {
070                    errorMessage.append(" Null instanceIds can be the result of multiple asm.jars or none in the classpath.");
071                }
072                                throw new IllegalArgumentException(errorMessage.toString());
073                        }
074                        LocalService localService = localServiceIndex.get(serviceInfo.getServiceName());
075                        if (localService == null) {
076                                // this means the registry has the service but there is no local service, it has been unregistered
077                                servicesToRemoveFromRegistry.add(serviceInfo);
078                        } else {
079                                // if the LocalService is not null, that means that it exists but it may have changed, or this may be the first time the service
080                                // is being published upon startup in which case it's service id will be null
081                                if (!localService.getServiceEndpoint().getInfo().equals(serviceInfo)) {
082                                        // if the service infos don't match, that means we need to re-publish our current copy of the local service
083                                        localServicesToUpdate.put(localService, serviceInfo);
084                                }
085                                // whether or not it matches, remove it from the index
086                                localServiceIndex.remove(serviceInfo.getServiceName());
087                        }
088                }
089                // what's left in the localServiceIndex will be services that weren't in the registry at all, they need to be published
090                localServicesToPublish.addAll(localServiceIndex.values());
091                
092                if (LOG.isDebugEnabled()) {
093                        LOG.info("For instance '" + instanceId + "', found " + servicesToRemoveFromRegistry.size() + " services to remove from registry, "+
094                                localServicesToPublish.size() + " local services to publish");
095                }
096                
097                return new LocalServicesDiff(servicesToRemoveFromRegistry, localServicesToPublish, localServicesToUpdate);
098                                
099        }
100        
101        private Map<QName, LocalService> indexLocalServices(String instanceId, List<LocalService> localServices) {
102                Map<QName, LocalService> localServiceIndex = new HashMap<QName, LocalService>(localServices.size());
103                for (LocalService localService : localServices) {
104                        String localServiceInstanceId = localService.getServiceEndpoint().getInfo().getInstanceId(); 
105                        if (!instanceId.equals(localServiceInstanceId)) {
106                                throw new IllegalStateException("Instance id of local service (" + localServiceInstanceId + ") does not match instance id given to the diff calculator (" + instanceId + ")");
107                        }
108                        localServiceIndex.put(localService.getServiceName(), localService);
109                }
110                return localServiceIndex;
111        }
112                
113        protected RemoteServicesDiff calculateRemoteServicesDiff(List<ServiceInfo> allRegistryServices, List<RemoteService> clientRegistryCache) {
114
115        Map<String, ServiceInfo> indexedRegistryServices = indexRegistryServices(allRegistryServices);
116                Map<String, ServiceInfo> servicesToAddToClientRegistryCache = new HashMap<String, ServiceInfo>(indexedRegistryServices);
117                List<RemoteService> servicesToRemoveFromClientRegistryCache = new ArrayList<RemoteService>();
118
119                for (RemoteService remoteService : clientRegistryCache) {
120                        ServiceInfo indexedRegistryService = indexedRegistryServices.get(remoteService.getServiceInfo().getServiceId());
121                        if (indexedRegistryService == null) {
122                                servicesToRemoveFromClientRegistryCache.add(remoteService);
123                        } else {
124                                if (!remoteService.getServiceInfo().getChecksum().equals(indexedRegistryService.getChecksum())) {
125                                        servicesToRemoveFromClientRegistryCache.add(remoteService);
126                                } else {
127                                        servicesToAddToClientRegistryCache.remove(remoteService.getServiceInfo().getServiceId());
128                                }
129                        }
130                }
131                
132                if (LOG.isDebugEnabled()) {
133                        LOG.debug("For instance found " + servicesToRemoveFromClientRegistryCache.size() + " services to remove from client registry cache, "+
134                                servicesToAddToClientRegistryCache.size() + " services to add to client registry cache");
135                }
136                
137                return new RemoteServicesDiff(new ArrayList<ServiceInfo>(servicesToAddToClientRegistryCache.values()),
138                servicesToRemoveFromClientRegistryCache);
139        }
140        
141        private Map<String, ServiceInfo> indexRegistryServices(List<ServiceInfo> allRegistryServices) {
142                Map<String, ServiceInfo> indexedRegistryServices = new HashMap<String, ServiceInfo>(allRegistryServices.size());
143                for (ServiceInfo serviceInfo : allRegistryServices) {
144                        indexedRegistryServices.put(serviceInfo.getServiceId(), serviceInfo);
145                }
146                return indexedRegistryServices;
147        }
148
149}