Coverage Report - org.kuali.rice.ksb.impl.bus.ServiceBusImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ServiceBusImpl
0%
0/250
0%
0/122
3.516
ServiceBusImpl$1
0%
0/6
N/A
3.516
 
 1  
 package org.kuali.rice.ksb.impl.bus;
 2  
 
 3  
 import java.util.ArrayList;
 4  
 import java.util.Collections;
 5  
 import java.util.HashMap;
 6  
 import java.util.HashSet;
 7  
 import java.util.Iterator;
 8  
 import java.util.List;
 9  
 import java.util.Map;
 10  
 import java.util.Random;
 11  
 import java.util.Set;
 12  
 import java.util.concurrent.ScheduledFuture;
 13  
 import java.util.concurrent.TimeUnit;
 14  
 
 15  
 import javax.xml.namespace.QName;
 16  
 
 17  
 import org.apache.commons.lang.StringUtils;
 18  
 import org.apache.log4j.Logger;
 19  
 import org.kuali.rice.core.api.config.property.Config;
 20  
 import org.kuali.rice.core.api.config.property.ConfigContext;
 21  
 import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
 22  
 import org.kuali.rice.ksb.api.bus.Endpoint;
 23  
 import org.kuali.rice.ksb.api.bus.ServiceBus;
 24  
 import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
 25  
 import org.kuali.rice.ksb.api.bus.ServiceDefinition;
 26  
 import org.kuali.rice.ksb.api.registry.RemoveAndPublishResult;
 27  
 import org.kuali.rice.ksb.api.registry.ServiceEndpoint;
 28  
 import org.kuali.rice.ksb.api.registry.ServiceInfo;
 29  
 import org.kuali.rice.ksb.api.registry.ServiceRegistry;
 30  
 import org.kuali.rice.ksb.impl.registry.diff.CompleteServiceDiff;
 31  
 import org.kuali.rice.ksb.impl.registry.diff.LocalServicesDiff;
 32  
 import org.kuali.rice.ksb.impl.registry.diff.RemoteServicesDiff;
 33  
 import org.kuali.rice.ksb.impl.registry.diff.ServiceRegistryDiffCalculator;
 34  
 import org.kuali.rice.ksb.messaging.serviceexporters.ServiceExportManager;
 35  
 import org.kuali.rice.ksb.messaging.threadpool.KSBScheduledPool;
 36  
 import org.springframework.beans.factory.DisposableBean;
 37  
 import org.springframework.beans.factory.InitializingBean;
 38  
 
 39  0
 public class ServiceBusImpl extends BaseLifecycle implements ServiceBus, InitializingBean, DisposableBean {
 40  
         
 41  0
         private static final Logger LOG = Logger.getLogger(ServiceBusImpl.class);
 42  
         
 43  0
         private final Object serviceLock = new Object();
 44  0
         private final Object synchronizeLock = new Object();
 45  0
         private final Random randomNumber = new Random();
 46  
         
 47  
         // injected values
 48  
         private String instanceId;
 49  
         private ServiceRegistry serviceRegistry;
 50  
         private ServiceRegistryDiffCalculator diffCalculator;
 51  
         private ServiceExportManager serviceExportManager;
 52  
         private KSBScheduledPool scheduledPool;
 53  
         
 54  
         private ScheduledFuture<?> registrySyncFuture;
 55  
         
 56  
         /**
 57  
          * Contains endpoints for services which were published by this client application.
 58  
          */
 59  
         private final Map<QName, LocalService> localServices;
 60  
         
 61  
         /**
 62  
          * Contains endpoints for services which exist remotely.  This list may not be
 63  
          * entirely complete as entries get lazily loaded into it as services are requested.
 64  
          */
 65  
         private final Map<QName, Set<RemoteService>> clientRegistryCache;
 66  
                 
 67  0
         public ServiceBusImpl() {
 68  0
                 this.localServices = new HashMap<QName, LocalService>();
 69  0
                 this.clientRegistryCache = new HashMap<QName, Set<RemoteService>>();
 70  0
         }
 71  
         
 72  
         @Override
 73  
         public void afterPropertiesSet() throws Exception {
 74  0
                 if (StringUtils.isBlank(instanceId)) {
 75  0
                         throw new IllegalStateException("a valid instanceId was not injected");
 76  
                 }
 77  0
                 if (serviceRegistry == null) {
 78  0
                         throw new IllegalStateException("serviceRegistry was not injected");
 79  
                 }
 80  0
                 if (diffCalculator == null) {
 81  0
                         throw new IllegalStateException("diffCalculator was not injected");
 82  
                 }
 83  0
                 if (scheduledPool == null) {
 84  0
                         throw new IllegalStateException("scheduledPool was not injected");
 85  
                 }
 86  0
         }
 87  
         
 88  
         @Override
 89  
         public void start() throws Exception {
 90  0
                 startSynchronizationThread();
 91  0
                 super.start();
 92  0
         }
 93  
                 
 94  
         protected boolean isDevMode() {
 95  0
                 return ConfigContext.getCurrentContextConfig().getDevMode();
 96  
         }
 97  
 
 98  
         protected void startSynchronizationThread() {
 99  0
                 synchronized (synchronizeLock) {
 100  0
                         LOG.info("Starting Service Bus synchronization thread...");
 101  0
                         if (!isDevMode()) {
 102  0
                                 int refreshRate = ConfigContext.getCurrentContextConfig().getRefreshRate();
 103  0
                                 Runnable runnable = new Runnable() {
 104  
                                         public void run() {
 105  
                                                 try {
 106  0
                                                         synchronize();
 107  0
                                                 } catch (Throwable t) {
 108  0
                                                         LOG.error("Failed to execute background service bus synchronization.", t);
 109  0
                                                 }
 110  0
                                         }
 111  
                                 };
 112  0
                                 this.registrySyncFuture = scheduledPool.scheduleWithFixedDelay(runnable, 30, refreshRate, TimeUnit.SECONDS);
 113  
                         }
 114  0
                         LOG.info("...Service Bus synchronization thread successfully started.");
 115  0
                 }
 116  0
         }
 117  
         
 118  
         @Override
 119  
         public void destroy() throws Exception {
 120  0
                 LOG.info("Stopping the Service Bus...");
 121  0
                 stopSynchronizationThread();
 122  0
                 serviceRegistry.takeInstanceOffline(getInstanceId());
 123  0
                 LOG.info("...Service Bus successfully stopped.");
 124  0
         }
 125  
         
 126  
         protected void stopSynchronizationThread() {
 127  0
                 synchronized (synchronizeLock) {
 128  
                         // remove services from the bus
 129  0
                         if (this.registrySyncFuture != null) {
 130  0
                                 if (!this.registrySyncFuture.cancel(false)) {
 131  0
                                         LOG.warn("Failed to cancel registry sychronization.");
 132  
                                 }
 133  0
                                 this.registrySyncFuture = null;
 134  
                         }
 135  0
                 }
 136  0
         }
 137  
 
 138  
         @Override
 139  
         public String getInstanceId() {
 140  0
                 return this.instanceId;
 141  
         }
 142  
         
 143  
         public void setInstanceId(String instanceId) {
 144  0
                 this.instanceId = instanceId;
 145  0
         }
 146  
         
 147  
         @Override
 148  
         public List<Endpoint> getEndpoints(QName serviceName) {
 149  0
                 if (serviceName == null) {
 150  0
                         throw new IllegalArgumentException("serviceName cannot be null");
 151  
                 }
 152  0
                 List<Endpoint> endpoints = new ArrayList<Endpoint>();
 153  0
                 synchronized (serviceLock) {
 154  0
                         endpoints.addAll(getRemoteEndpoints(serviceName));
 155  0
                         Endpoint localEndpoint = getLocalEndpoint(serviceName);
 156  0
                         if (localEndpoint != null) {
 157  0
                                 for (Iterator<Endpoint> iterator = endpoints.iterator(); iterator.hasNext();) {
 158  0
                                         Endpoint endpoint = (Endpoint) iterator.next();
 159  0
                                         if (localEndpoint.getServiceConfiguration().equals(endpoint.getServiceConfiguration())) {
 160  0
                                                 iterator.remove();
 161  0
                                                 break;
 162  
                                         }
 163  0
                                 }
 164  
                                 // add at first position, just because we like the local endpoint the best, it's our friend ;)
 165  0
                                 endpoints.add(0, localEndpoint);
 166  
                         }
 167  0
                 }
 168  0
                 return Collections.unmodifiableList(endpoints);
 169  
         }
 170  
         
 171  
         @Override
 172  
         public List<Endpoint> getRemoteEndpoints(QName serviceName) {
 173  0
                 if (serviceName == null) {
 174  0
                         throw new IllegalArgumentException("serviceName cannot be null");
 175  
                 }
 176  0
                 List<Endpoint> endpoints = new ArrayList<Endpoint>();
 177  0
                 synchronized (serviceLock) {
 178  0
                         Set<RemoteService> remoteServices = clientRegistryCache.get(serviceName);
 179  0
                         if (remoteServices != null) {
 180  0
                                 for (RemoteService remoteService : remoteServices) {
 181  0
                                         endpoints.add(remoteService.getEndpoint());
 182  
                                 }
 183  
                         }
 184  0
                 }
 185  0
                 return Collections.unmodifiableList(endpoints);
 186  
         }
 187  
 
 188  
         @Override
 189  
         public Endpoint getLocalEndpoint(QName serviceName) {
 190  0
                 if (serviceName == null) {
 191  0
                         throw new IllegalArgumentException("serviceName cannot be null");
 192  
                 }
 193  0
                 synchronized (serviceLock) {
 194  0
                         LocalService localService = localServices.get(serviceName);
 195  0
                         if (localService != null) {
 196  0
                                 return localService.getEndpoint();
 197  
                         }
 198  0
                         return null;
 199  0
                 }
 200  
         }
 201  
 
 202  
         @Override
 203  
         public Map<QName, Endpoint> getLocalEndpoints() {
 204  0
                 Map<QName, Endpoint> localEndpoints = new HashMap<QName, Endpoint>();
 205  0
                 synchronized (serviceLock) {
 206  0
                         for (QName localServiceName : localServices.keySet()) {
 207  0
                                 LocalService localService = localServices.get(localServiceName);
 208  0
                                 localEndpoints.put(localServiceName, localService.getEndpoint());
 209  0
                         }
 210  0
                 }
 211  0
                 return Collections.unmodifiableMap(localEndpoints);
 212  
         }
 213  
 
 214  
         @Override
 215  
         public List<Endpoint> getAllEndpoints() {
 216  0
                 List<Endpoint> allEndpoints = new ArrayList<Endpoint>();
 217  0
                 synchronized (serviceLock) {
 218  0
                         for (QName serviceName : this.localServices.keySet()) {
 219  0
                                 allEndpoints.add(this.localServices.get(serviceName).getEndpoint());
 220  
                         }
 221  0
                         for (QName serviceName : this.clientRegistryCache.keySet()) {
 222  0
                                 Set<RemoteService> remoteServices = clientRegistryCache.get(serviceName);
 223  0
                                 for (RemoteService remoteService : remoteServices) {
 224  0
                                         allEndpoints.add(remoteService.getEndpoint());
 225  
                                 }
 226  0
                         }
 227  0
                 }
 228  0
                 return Collections.unmodifiableList(allEndpoints);
 229  
         }
 230  
 
 231  
         @Override
 232  
         public Endpoint getEndpoint(QName serviceName) {
 233  0
                 if (serviceName == null) {
 234  0
                         throw new IllegalArgumentException("serviceName cannot be null");
 235  
                 }
 236  0
                 Endpoint availableEndpoint = null;
 237  0
                 synchronized (serviceLock) {
 238  
                         // look at local services first
 239  0
                         availableEndpoint = getLocalEndpoint(serviceName);
 240  0
                         if (availableEndpoint == null) {
 241  
                                  // TODO - would be better to return an Endpoint that contained an internal proxy to all the services so fail-over would be easier to implement!
 242  0
                                 Set<RemoteService> remoteServices = clientRegistryCache.get(serviceName);
 243  0
                                 if (remoteServices != null && !remoteServices.isEmpty()) {
 244  
                                         // TODO - this should also probably check the current status of the service?
 245  0
                                         RemoteService[] remoteServiceArray = remoteServices.toArray(new RemoteService[0]);
 246  0
                                         RemoteService availableRemoteService = remoteServiceArray[this.randomNumber.nextInt(remoteServiceArray.length)];
 247  0
                                         availableEndpoint = availableRemoteService.getEndpoint();
 248  
                                 }
 249  
                         }
 250  0
                 }
 251  0
                 return availableEndpoint;
 252  
         }
 253  
         
 254  
         @Override
 255  
         public Endpoint getConfiguredEndpoint(ServiceConfiguration serviceConfiguration) {
 256  0
                 if (serviceConfiguration == null) {
 257  0
                         throw new IllegalArgumentException("serviceConfiguration cannot be null");
 258  
                 }
 259  0
                 synchronized (serviceLock) {
 260  0
                         Endpoint localEndpoint = getLocalEndpoint(serviceConfiguration.getServiceName());
 261  0
                         if (localEndpoint != null && localEndpoint.getServiceConfiguration().equals(serviceConfiguration)) {
 262  0
                                 return localEndpoint;
 263  
                         }
 264  0
                         List<Endpoint> remoteEndpoints = getRemoteEndpoints(serviceConfiguration.getServiceName());
 265  0
                         for (Endpoint remoteEndpoint : remoteEndpoints) {
 266  0
                                 if (remoteEndpoint.getServiceConfiguration().equals(serviceConfiguration)) {
 267  0
                                         return remoteEndpoint;
 268  
                                 }
 269  
                         }
 270  0
                 }
 271  0
                 return null;
 272  
         }
 273  
 
 274  
         
 275  
         @Override
 276  
         public Object getService(QName serviceName) {
 277  0
                 Endpoint availableEndpoint = getEndpoint(serviceName);
 278  0
                 if (availableEndpoint == null) {
 279  0
                         return null;
 280  
                 }
 281  0
                 return availableEndpoint.getService();
 282  
         }
 283  
 
 284  
         @Override
 285  
         public ServiceConfiguration publishService(ServiceDefinition serviceDefinition, boolean synchronize) {
 286  0
                 if (serviceDefinition == null) {
 287  0
                         throw new IllegalArgumentException("serviceDefinition cannot be null");
 288  
                 }
 289  0
                 LocalService localService = new LocalService(getInstanceId(), serviceDefinition);
 290  0
                 synchronized (serviceLock) {
 291  0
                         serviceExportManager.exportService(serviceDefinition);
 292  0
                         localServices.put(serviceDefinition.getServiceName(), localService);
 293  0
                 }
 294  0
                 if (synchronize) {
 295  0
                         synchronize();
 296  
                 }
 297  0
                 return localService.getEndpoint().getServiceConfiguration();
 298  
         }
 299  
 
 300  
         @Override
 301  
         public List<ServiceConfiguration> publishServices(List<ServiceDefinition> serviceDefinitions, boolean synchronize) {
 302  0
                 if (serviceDefinitions == null) {
 303  0
                         throw new IllegalArgumentException("serviceDefinitions list cannot be null");
 304  
                 }
 305  0
                 List<ServiceConfiguration> serviceConfigurations = new ArrayList<ServiceConfiguration>();
 306  0
                 synchronized (serviceLock) {
 307  0
                         for (ServiceDefinition serviceDefinition : serviceDefinitions) {
 308  0
                                 ServiceConfiguration serviceConfiguration = publishService(serviceDefinition, false);
 309  0
                                 serviceConfigurations.add(serviceConfiguration);
 310  0
                         }
 311  0
                 }
 312  0
                 if (synchronize) {
 313  0
                         synchronize();
 314  
                 }
 315  0
                 return Collections.unmodifiableList(serviceConfigurations);
 316  
         }
 317  
 
 318  
         @Override
 319  
         public boolean removeService(QName serviceName, boolean synchronize) {
 320  0
                 if (serviceName == null) {
 321  0
                         throw new IllegalArgumentException("serviceName cannot be null");
 322  
                 }
 323  0
                 boolean serviceRemoved = false;
 324  0
                 synchronized (serviceLock) {
 325  0
                         LocalService localService = localServices.remove(serviceName);
 326  0
                         serviceRemoved = localService != null;
 327  0
                         serviceExportManager.removeService(serviceName);
 328  0
                 }
 329  0
                 if (serviceRemoved && synchronize) {
 330  0
                         synchronize();
 331  
                 }
 332  0
                 return serviceRemoved;
 333  
         }
 334  
 
 335  
         @Override
 336  
         public List<Boolean> removeServices(List<QName> serviceNames, boolean synchronize) {
 337  0
                 if (serviceNames == null) {
 338  0
                         throw new IllegalArgumentException("serviceNames cannot be null");
 339  
                 }
 340  0
                 boolean serviceRemoved = false;
 341  0
                 List<Boolean> servicesRemoved = new ArrayList<Boolean>();
 342  0
                 synchronized (serviceLock) {
 343  0
                         for (QName serviceName : serviceNames) {
 344  0
                                 serviceExportManager.removeService(serviceName);
 345  0
                                 LocalService localService = localServices.remove(serviceName);
 346  0
                                 if (localService != null) {
 347  0
                                         servicesRemoved.add(Boolean.TRUE);
 348  0
                                         serviceRemoved = true;
 349  
                                 } else {
 350  0
                                         servicesRemoved.add(Boolean.FALSE);
 351  
                                 }
 352  0
                         }
 353  0
                 }
 354  0
                 if (serviceRemoved && synchronize) {
 355  0
                         synchronize();
 356  
                 }
 357  0
                 return servicesRemoved;
 358  
         }
 359  
 
 360  
         @Override
 361  
         public void synchronize() {
 362  0
                 if (!isDevMode() && isStarted()) {
 363  0
                         synchronized (synchronizeLock) {
 364  
                                 List<LocalService> localServicesList;
 365  
                                 List<RemoteService> clientRegistryCacheList;
 366  0
                                 synchronized (serviceLock) {
 367  
                                         // first, flatten the lists
 368  0
                                         localServicesList = new ArrayList<LocalService>(this.localServices.values());
 369  0
                                         clientRegistryCacheList = new ArrayList<RemoteService>();
 370  0
                                         for (Set<RemoteService> remoteServices : this.clientRegistryCache.values()) {
 371  0
                                                 clientRegistryCacheList.addAll(remoteServices);
 372  
                                         }
 373  0
                                 }
 374  0
                                 CompleteServiceDiff serviceDiff = diffCalculator.diffServices(getInstanceId(), localServicesList, clientRegistryCacheList);
 375  
                         
 376  0
                                 RemoteServicesDiff remoteServicesDiff = serviceDiff.getRemoteServicesDiff();
 377  0
                                 processRemoteServiceDiff(remoteServicesDiff);
 378  
                         
 379  0
                                 LocalServicesDiff localServicesDiff = serviceDiff.getLocalServicesDiff();
 380  0
                                 processLocalServiceDiff(localServicesDiff);
 381  0
                         }
 382  
                 }
 383  0
         }
 384  
                 
 385  
         protected void processRemoteServiceDiff(RemoteServicesDiff remoteServicesDiff) {
 386  
                 // note that since there is a gap between when the original services are acquired, the diff, and this subsequent critical section
 387  
                 // the list of local and client registry services could have changed, so that needs to be considered in the remaining code
 388  0
                 synchronized (serviceLock) {
 389  
                         // first, let's update what we know about the remote services
 390  0
                         List<RemoteService> removedServices = remoteServicesDiff.getRemovedServices();
 391  0
                         for (RemoteService removedRemoteService : removedServices) {
 392  0
                                 Set<RemoteService> remoteServiceSet = this.clientRegistryCache.get(removedRemoteService.getServiceName());
 393  0
                                 if (remoteServiceSet != null) {
 394  0
                                         boolean wasRemoved = remoteServiceSet.remove(removedRemoteService);
 395  0
                                         if (!wasRemoved) {
 396  0
                                                 LOG.warn("Failed to remove remoteService during synchronization: " + removedRemoteService);
 397  
                                         }
 398  
                                 }
 399  0
                         }
 400  0
                         List<ServiceInfo> newServices = remoteServicesDiff.getNewServices();
 401  0
                         for (ServiceInfo newService : newServices) {
 402  0
                                 Set<RemoteService> remoteServiceSet = clientRegistryCache.get(newService.getServiceName());
 403  0
                                 if (remoteServiceSet == null) {
 404  0
                                         remoteServiceSet = new HashSet<RemoteService>();
 405  0
                                         clientRegistryCache.put(newService.getServiceName(), remoteServiceSet);
 406  
                                 }
 407  0
                                 remoteServiceSet.add(new RemoteService(newService, this.serviceRegistry));
 408  0
                         }
 409  0
                 }
 410  0
         }
 411  
         
 412  
         protected void processLocalServiceDiff(LocalServicesDiff localServicesDiff) {
 413  0
                 List<String> removeServiceEndpointIds = new ArrayList<String>();
 414  0
                 List<ServiceEndpoint> publishServiceEndpoints = new ArrayList<ServiceEndpoint>();
 415  0
                 for (ServiceInfo serviceToRemove : localServicesDiff.getServicesToRemoveFromRegistry()) {
 416  0
                         removeServiceEndpointIds.add(serviceToRemove.getServiceId());
 417  
                 }
 418  0
                 for (LocalService localService : localServicesDiff.getLocalServicesToPublish()) {
 419  0
                         publishServiceEndpoints.add(localService.getServiceEndpoint());
 420  
                 }
 421  0
                 for (LocalService localService : localServicesDiff.getLocalServicesToUpdate().keySet()) {
 422  0
                         ServiceInfo registryServiceInfo = localServicesDiff.getLocalServicesToUpdate().get(localService);
 423  0
                         publishServiceEndpoints.add(rebuildServiceEndpointForUpdate(localService.getServiceEndpoint(), registryServiceInfo));
 424  0
                 }
 425  0
                 boolean batchMode = ConfigContext.getCurrentContextConfig().getBooleanProperty(Config.BATCH_MODE, false);
 426  0
                 if (!batchMode && (!removeServiceEndpointIds.isEmpty() || !publishServiceEndpoints.isEmpty())) {
 427  0
                         RemoveAndPublishResult result = this.serviceRegistry.removeAndPublish(removeServiceEndpointIds, publishServiceEndpoints);
 428  
                         // now update the ServiceEndpoints for our local services so we can get the proper id for them
 429  0
                         if (!result.getServicesPublished().isEmpty()) {
 430  0
                                 synchronized (serviceLock) {
 431  0
                                         for (ServiceEndpoint publishedService : result.getServicesPublished()) {
 432  0
                                                 rebuildLocalServiceEndpointAfterPublishing(publishedService);
 433  
                                         }
 434  0
                                 }
 435  
                         }
 436  
                 }
 437  0
         }
 438  
         
 439  
         protected ServiceEndpoint rebuildServiceEndpointForUpdate(ServiceEndpoint originalEndpoint, ServiceInfo registryServiceInfo) {
 440  0
                 ServiceEndpoint.Builder builder = ServiceEndpoint.Builder.create(originalEndpoint);
 441  0
                 builder.getInfo().setServiceId(registryServiceInfo.getServiceId());
 442  0
                 builder.getInfo().setServiceDescriptorId(registryServiceInfo.getServiceDescriptorId());
 443  0
                 builder.getInfo().setVersionNumber(registryServiceInfo.getVersionNumber());
 444  0
                 builder.getDescriptor().setId(registryServiceInfo.getServiceDescriptorId());
 445  0
                 return builder.build();
 446  
         }
 447  
         
 448  
         protected void rebuildLocalServiceEndpointAfterPublishing(ServiceEndpoint publishedService) {
 449  
                 // verify the service is still published
 450  0
                 QName serviceName = publishedService.getInfo().getServiceName();
 451  0
                 if (localServices.containsKey(serviceName)) {
 452  0
                         LocalService newLocalService = new LocalService(localServices.get(serviceName), publishedService);
 453  0
                         localServices.put(serviceName, newLocalService);
 454  
                 }
 455  0
         }
 456  
 
 457  
         public void setServiceRegistry(ServiceRegistry serviceRegistry) {
 458  0
                 this.serviceRegistry = serviceRegistry;
 459  0
         }
 460  
         
 461  
         public void setDiffCalculator(ServiceRegistryDiffCalculator diffCalculator) {
 462  0
                 this.diffCalculator = diffCalculator;
 463  0
         }
 464  
         
 465  
         public void setServiceExportManager(ServiceExportManager serviceExportManager) {
 466  0
                 this.serviceExportManager = serviceExportManager;
 467  0
         }
 468  
         
 469  
         public void setScheduledPool(KSBScheduledPool scheduledPool) {
 470  0
                 this.scheduledPool = scheduledPool;
 471  0
         }
 472  
         
 473  
 }