/******************************************************************************* * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved * * Licensed 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.cloudifysource.utilitydomain.admin; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import net.jini.core.discovery.LookupLocator; import org.apache.commons.lang.StringUtils; import org.openspaces.admin.Admin; import org.openspaces.admin.AdminFactory; import org.openspaces.admin.esm.ElasticServiceManager; import org.openspaces.admin.pu.ProcessingUnit; import org.openspaces.admin.space.Space; import org.openspaces.security.AdminFilter; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; /** * Wraps the {@link Admin} object in order to monitor the object usage and close it after it is no longer in use. * This is intended to minimize memory and network utilization by unused {@link Admin} objects. * * @author noak * @since 2.7.1 */ public class TimedAdmin { private static Logger logger = Logger.getLogger(TimedAdmin.class.getName()); // TODO noak: make configurable private static final long MAX_IDLE_TIME_MILLIS = 120 * 1000; // defaults to 120 seconds private static final long POLLING_INTERVAL_MILLIS = 10 * 1000; // defaults to 10 seconds private long lastUsed = System.currentTimeMillis(); private Admin admin; private boolean discoverUnmanagedSpaces; boolean running; private int statisticsHistorySize = Admin.DEFAULT_HISTORY_SIZE; private String groups; private String locators; private Class[] discoveryServices; private AdminFilter adminFilter; private ExecutorService executor; public void setDiscoveryServices(final Class[] discoveryServices) { this.discoveryServices = discoveryServices; } public void setStatisticsHistorySize(int statisticsHistorySize) { this.statisticsHistorySize = statisticsHistorySize; } public String[] getAdminGroups() { if (admin != null) { return admin.getGroups(); } return null; } public void setGroups(final String groups) { this.groups = groups; } public LookupLocator[] getAdminLocators() { if (admin != null) { return admin.getLocators(); } return null; } public void setLocators(final String locators) { this.locators = locators; } public void setAdminFilter(final AdminFilter adminFilter) { this.adminFilter = adminFilter; } public void discoverUnmanagedSpaces() { this.discoverUnmanagedSpaces = true; } /*********** * Creates an admin instance if required. * A timing thread is also created to monitor the admin expity time and terminate it if needed. */ private synchronized void initAdmin() { logger.finest("getting admin object"); if (admin == null) { createAdmin(); } else { logger.finest("Using a cached Admin object"); } updateTimestamp(); } private void createAdmin() { logger.finest("Creating a new Admin object..."); final AdminFactory factory = new AdminFactory(); factory.useDaemonThreads(true); if (StringUtils.isNotBlank(groups)) { factory.addGroups(groups); } if (StringUtils.isNotBlank(locators)) { factory.addLocators(locators); } if (adminFilter != null) { factory.adminFilter(adminFilter); } if (discoveryServices != null) { factory.setDiscoveryServices(discoveryServices); } if (discoverUnmanagedSpaces) { factory.discoverUnmanagedSpaces(); } this.admin = factory.createAdmin(); this.admin.setStatisticsHistorySize(statisticsHistorySize); logger.finest("Created new Admin Object with groups: " + Arrays.toString(this.admin.getGroups()) + " and Locators: " + Arrays.toString(this.admin.getLocators())); updateTimestamp(); startTimingThread(); } /** * Creates and starts a thread that monitors the admin object usage - if the object was not used for longer than * the maximum idle time, the object is closed and nullified. */ private synchronized void startTimingThread() { // create daemon threads, so the timing thread won't keep the process alive executor = Executors.newSingleThreadExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = Executors.defaultThreadFactory().newThread(runnable); thread.setDaemon(true); thread.setName("AdminTimingThread"); return thread; } }); executor.execute(new Runnable() { @Override public void run() { running = true; while (running) { try { if (admin != null && (lastUsed + MAX_IDLE_TIME_MILLIS < System.currentTimeMillis())) { logger.fine("Closing expired admin object"); admin.close(); admin = null; running = false; } Thread.sleep(POLLING_INTERVAL_MILLIS); } catch (final InterruptedException e) { // ignore } } } }); executor.shutdown(); } /** * Waits until a space by the specified name is found, or the timeout is reached. * @param spaceName The name of the requested space * @param timeout The timeout length * @param timeunit The timeout length time unit * @return The space, if found in the given time frame; null otherwise */ public Space waitForSpace(final String spaceName, final long timeout, final TimeUnit timeunit) { validateTimeout(timeout, timeunit, "waiting for space instance"); initAdmin(); return admin.getSpaces().waitFor(spaceName, timeout, timeunit); } /** * Returns a space based on its name. * @param spaceName The name of the requested space * @return The space if found; null otherwise */ public Space getSpaceByName(final String spaceName) { initAdmin(); return admin.getSpaces().getSpaceByName(spaceName); } /** * Waits until a processing unit by the specified name is found, or the timeout is reached. * @param puName The name of the requested space * @param timeout The timeout length * @param timeunit The timeout length time unit * @return The processing unit, if found in the given time frame; null otherwise */ public ProcessingUnit waitForPU(final String puName, final long timeout, final TimeUnit timeunit) { validateTimeout(timeout, timeunit, "waiting for PU"); initAdmin(); return admin.getProcessingUnits().waitFor(puName, timeout, timeunit); } /** * Waits until at least the provided number of Processing Unit Instances are found, or the timeout is reached. * @param pu The processing unit object * @param numberOfProcessingUnitInstances The required number of instances * @param timeout the timeout length * @param timeunit The timeout length time unit * @return true if the numbers of PUIs found is equal or more than required, false otherwise */ public boolean waitForPUI(final ProcessingUnit pu, int numberOfPUInstances, long timeout, TimeUnit timeunit) { validateTimeout(timeout, timeunit, "waiting for PU instance"); initAdmin(); return pu.waitFor(numberOfPUInstances, timeout, timeunit); } /** * Waits until all lookup services are found, or the timeout is reached. * @param numberOfLookupServices The number of requested lookup services * @param timeout The timeout length * @param timeunit The timeout length time unit * @return True if all lookup services were found; false otherwise */ public boolean waitForLookupServices(int numberOfLookupServices, long timeout, TimeUnit timeunit) { validateTimeout(timeout, timeunit, "waiting for lookup service"); initAdmin(); return admin.getLookupServices().waitFor(numberOfLookupServices, timeout, timeunit); } /** * Waits until an {@link ElasticServiceManager} is found, or the timeout is reached. * @return The ElasticServiceManager if found in the given time frame; null otherwise */ public ElasticServiceManager waitForElasticServiceManager() { initAdmin(); return admin.getElasticServiceManagers().waitForAtLeastOne(); } /** * Closes the admin object and stops the timing thread. */ public void close() { logger.finest("Closing the admin object and stopping the timing thread"); if (admin != null) { admin.close(); admin = null; } // this should cause the timing thread to end running = false; } /** * Returns the state of the underlying admin object: if it's set - return true, otherwise return false. * @return If the admin is set (not null) - return true, otherwise return false */ public boolean isAdminObjectAlive() { if (admin == null) { return false; } else { return true; } } private synchronized void updateTimestamp() { lastUsed = System.currentTimeMillis(); } /** * Validates the given action timeout is not shorter than the admin timeout, and issues a warning if it is. */ private void validateTimeout(final long timeout, final TimeUnit timeunit, final String actionDescription) { if (timeunit.toMillis(timeout) >= MAX_IDLE_TIME_MILLIS) { logger.warning("Admin object might expire prematurely! The specified timeout for " + actionDescription + " was set to " + timeout + " " + timeunit.toString() + " while the admin timeout is " + MAX_IDLE_TIME_MILLIS/1000 + " seconds"); } } }