/* ############################################################################### # # # Copyright (C) 2011-2016 OpenMEAP, Inc. # # Credits to Jonathan Schang & Rob Thacher # # # # Released under the LGPLv3 # # # # OpenMEAP is free software: you can redistribute it and/or modify # # it under the terms of the GNU Lesser General Public License as published # # by the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # OpenMEAP is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU Lesser General Public License for more details. # # # # You should have received a copy of the GNU Lesser General Public License # # along with OpenMEAP. If not, see <http://www.gnu.org/licenses/>. # # # ############################################################################### */ package com.openmeap.cluster; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.openmeap.digest.DigestException; import com.openmeap.event.Event; import com.openmeap.event.ProcessingEvent; import com.openmeap.event.EventNotifier; import com.openmeap.model.ModelManager; import com.openmeap.model.dto.ClusterNode; import com.openmeap.model.dto.GlobalSettings; import com.openmeap.util.AuthTokenProvider; import com.openmeap.util.GenericRuntimeException; import com.openmeap.util.ThrowableList; import com.openmeap.http.HttpRequestExecuter; // TODO: separate this out into a ClusterNodesNotifierService and use delegate methods from the notifiers public abstract class AbstractClusterServiceMgmtNotifier<T> implements EventNotifier<T> { private Logger logger = LoggerFactory.getLogger(AbstractClusterServiceMgmtNotifier.class); /** * If null, then the notification requests are executed serially */ private ExecutorService executorService = null; private HttpRequestExecuter httpRequestExecuter = null; private ModelManager modelManager = null; private Long executorTimeout = null; protected void onBeforeNotify(final Event<T> event) {} protected void onAfterNotify(final Event<T> event) {} abstract protected void makeRequest(URL url, Event<T> message) throws ClusterNotificationException; public <E extends Event<T>> void notify(final E event, List<ProcessingEvent> events) throws ClusterNotificationException { final ThrowableList exceptions = new ThrowableList(); onBeforeNotify(event); final Map<String,Boolean> urlRequestCompleteStatus = new HashMap<String,Boolean>(); GlobalSettings globalSettings = modelManager.getGlobalSettings(); List<ClusterNode> clusterNodes = globalSettings.getClusterNodes(); for( ClusterNode thisNode : clusterNodes ) { URL thisUrl = null; try { thisUrl = new URL(thisNode.getServiceWebUrlPrefix()); } catch (MalformedURLException e) { logger.error("Could not create URL object from "+thisNode.getServiceWebUrlPrefix()+": {}",e); continue; } if( executorService!=null ) { logger.debug("Making request to {} using the executor.",thisUrl); executorService.execute(new Runnable() { URL url; public void run() { notifyMakeRequest(exceptions,url,urlRequestCompleteStatus,event); } Runnable setUrl(URL url) { this.url = url; return this; } }.setUrl(thisUrl) ); } else { logger.debug("Making request to {} serially.",thisUrl); notifyMakeRequest(exceptions,thisUrl,urlRequestCompleteStatus,event); } } // only block if they've configured a timeout // this set of if-else handles any exceptions in the list accumulated if( executorService!=null && executorTimeout!=null ) { try { // terminate, but make sure nothing is still waiting when we terminate if( ! executorService.awaitTermination(executorTimeout, TimeUnit.SECONDS) ) { executorService.shutdownNow(); List<String> waiting = new ArrayList<String>(); for( Map.Entry<String,Boolean> completed : urlRequestCompleteStatus.entrySet() ) { if( completed.getValue().equals(Boolean.FALSE) ) { waiting.add(completed.getKey()); } } logger.error("Blocking timed-out still waiting to notify: {}", StringUtils.join(waiting,", ")); throw new ClusterNotificationException( String.format("Blocking timed-out still waiting to notify: %s", StringUtils.join(waiting,", ")) ); } } catch( InterruptedException ie ) { throw new ClusterNotificationException("The notification thread was interrupted",ie); } } else if(exceptions.size()>0){ throw new ClusterNotificationException( String.format("The following exceptions were thrown: %s",exceptions.getMessages()) ); } onAfterNotify(event); } private <E extends Event<T>> void notifyMakeRequest(ThrowableList exceptions, URL thisUrl, Map<String,Boolean> urlRequestCompleteStatus, E event) { try { urlRequestCompleteStatus.put(thisUrl.toString(), Boolean.FALSE); makeRequest(thisUrl,event); urlRequestCompleteStatus.put(thisUrl.toString(), Boolean.TRUE); } catch( ClusterNotificationException e ) { logger.error("ClusterNode with url {} threw an exception : {}",thisUrl,e); exceptions.add(e); } } protected String newAuthToken() { try { return AuthTokenProvider.newAuthToken(modelManager.getGlobalSettings().getServiceManagementAuthSalt()); } catch(DigestException de) { throw new GenericRuntimeException(de); } } /** * The ModelServiceRefreshNotifier must have a request executer * @param httpExecuter */ public void setHttpRequestExecuter(HttpRequestExecuter httpExecuter) { httpRequestExecuter = httpExecuter; } public HttpRequestExecuter getHttpRequestExecuter() { return httpRequestExecuter; } /** * Set if you want an Executor to parallelize and background the requests * @param executor */ public void setExecutor(ExecutorService executor) { this.executorService = executor; } public ExecutorService getExecutor() { return executorService; } public void setModelManager(ModelManager config) { this.modelManager = config; } public ModelManager getModelManager() { return modelManager; } public void setExecutorTimeout(Long executorTimeout) { this.executorTimeout = executorTimeout; } public Long getExecutorTimeout() { return executorTimeout; } }