All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.bedrock.runtime.AbstractAssembly Maven / Gradle / Ivy

Go to download

Interfaces, classes and resources to construct, inspect and manage runtime processes.

There is a newer version: 7.0.5
Show newest version
/*
 * File: AbstractAssembly.java
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * The contents of this file are subject to the terms and conditions of 
 * the Common Development and Distribution License 1.0 (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License by consulting the LICENSE.txt file
 * distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
 *
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file LICENSE.txt.
 *
 * MODIFICATIONS:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 */

package com.oracle.bedrock.runtime;

import com.oracle.bedrock.Option;
import com.oracle.bedrock.OptionsByType;
import com.oracle.bedrock.annotations.Internal;
import com.oracle.bedrock.deferred.DeferredPredicate;
import com.oracle.bedrock.runtime.options.Discriminator;
import com.oracle.bedrock.runtime.options.DisplayName;
import com.oracle.bedrock.runtime.options.StabilityPredicate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.oracle.bedrock.deferred.DeferredHelper.ensure;
import static com.oracle.bedrock.deferred.DeferredHelper.eventually;
import static com.oracle.bedrock.predicate.Predicates.is;

/**
 * A base implementation of an {@link Assembly}.
 * 

* Copyright (c) 2014. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates. * * @author Brian Oliver * * @param the type of {@link Application} that belongs to the {@link Assembly}. */ @Internal public abstract class AbstractAssembly implements Assembly, ApplicationListener, ApplicationStream { /** * The {@link Application}s that belong to the {@link Assembly}. */ protected CopyOnWriteArrayList applications; /** * Is the {@link Assembly} closed? */ protected AtomicBoolean isClosed; /** * The next value to use for discriminating {@link Application}s by their {@link DisplayName}. *

* These values are used for creating {@link Discriminator}s when expanding an {@link Assembly}. */ protected ConcurrentHashMap discriminators; /** * The {@link OptionsByType} used when constructing the {@link Assembly}. *

* These are used as the default options where {@link OptionsByType} are required. */ protected OptionsByType optionsByType; /** * Constructs an {@link AbstractAssembly} with the specified {@link OptionsByType}. * * @param optionsByType the {@link OptionsByType} for the {@link Assembly} */ public AbstractAssembly(OptionsByType optionsByType) { this.applications = new CopyOnWriteArrayList<>(); this.isClosed = new AtomicBoolean(false); this.discriminators = new ConcurrentHashMap<>(); this.optionsByType = OptionsByType.of(optionsByType); } /** * Determine if the {@link Assembly} is closed. * * @return true if the {@link Assembly} is closed */ public boolean isClosed() { return isClosed.get(); } /** * Obtains an {@link Application} in this {@link Assembly} matching the given name * (as a regular expression). * * If no such {@link Application} exists in the {@link Assembly}, null * will be returned. If multiple {@link Application}s in the {@link Assembly} * matching the given name, an arbitrary {@link Application} matching the specified * name will be returned * * @param pattern a regular expression for the name of the application * * @return the application in this {@link Assembly} matching the given name or null * if no application has matched with the given name */ public A get(String pattern) { // when not found, search for the application using the pattern for (A application : applications) { if (application.getName().matches(pattern)) { return application; } } return null; } /** * Obtains an {@link Application} in this {@link Assembly} matching the given {@link Predicate}. * * If no such {@link Application} exists in the {@link Assembly}, {@code null} * will be returned. If multiple {@link Application}s in the {@link Assembly} * matching the given {@link Predicate}, an arbitrary {@link Application} matching * the specified {@link Predicate} will be returned. * * @param predicate a {@link Predicate} to use to match an {@link Application} * * @return the application in this {@link Assembly} matching the given name or {@code null} * if no application has matched with the given {@link Predicate} */ public A get(Predicate predicate) { // when not found, search for the application using the pattern for (A application : applications) { if (predicate.test(application)) { return application; } } return null; } /** * Obtains an {@link Application} in this {@link Assembly}. * * If no {@link Application} exists in the {@link Assembly}, {@code null} * will be returned. If multiple {@link Application}s in the {@link Assembly} * an arbitrary {@link Application} matching will be returned. * * @return the application in this {@link Assembly} or {@code null} * if there are no no applications */ public A getAny() { return applications.stream().findFirst().orElse(null); } /** * Obtains the {@link Application}s in this {@link Assembly} where by their * {@link Application#getName()} matches the specified pattern (as a regex) * or starts with the specified pattern. * * @param pattern the pattern to match with {@link Application#getName()}s * @return an {@link Iterable} over the {@link Application}s matching or * starting with the specified pattern */ public Iterable getAll(String pattern) { LinkedList list = new LinkedList<>(); for (A application : applications) { String name = application.getName(); if (name.startsWith(pattern) || name.matches(pattern)) { list.add(application); } } return list; } /** * Obtains the {@link Application}s in this {@link Assembly} * satifying the specified {@link Predicate}. * * @param predicate the {@link Predicate} to test {@link Application}s * @return an {@link Iterable} over the {@link Application}s satisfying the * {@link Predicate} */ public Iterable getAll(Predicate predicate) { LinkedList list = new LinkedList<>(); for (A application : applications) { if (predicate.test(application)) { list.add(application); } } return list; } /** * Adds the specified {@link Application} to the {@link Assembly}. * * @param application the {@link Application} to add * * @throws IllegalStateException when the {@link Assembly} {@link #isClosed} */ @SuppressWarnings("StatementWithEmptyBody") public void add(A application) { if (isClosed()) { throw new IllegalStateException("Can't add [" + application + "] as the " + this.getClass().getName() + " is closed"); } else if (get(application.getName()) != null) { // do nothing when an application of the same name is already in the assembly // (with the same name) } else { // ensure the assembly is a feature of the application so that it can be called back for lifecycle events application.add(Assembly.class, this); // add the application to the assembly applications.add(application); // notify the assembly implementation that it has expanded onExpanded(Collections.singletonList(application), application.getOptions()); } } /** * Removes the specified {@link Application} to the {@link Assembly}. * * @param application the {@link Application} to remove * * @return true if the specified {@link Application} was removed * false otherwise * * @throws IllegalStateException when the {@link Assembly} {@link #isClosed} */ public boolean remove(A application) { if (isClosed()) { throw new IllegalStateException("Can't remove [" + application + "] as the " + this.getClass().getName() + " is closed"); } else { // ensure the assembly is no longer a feature so that won't be called back for lifecycle events application.remove(Assembly.class); // remove the application from the assembly return applications.remove(application); } } /** * Expands the number of {@link Application}s in the {@link Assembly} by launching and adding the specified number * of {@link Application}s on the provided {@link Platform} using the zero or more provided {@link Option}s. * * @param count the number of instances of the {@link Application} that should be launched and added * to the {@link Assembly} * @param platform the {@link Platform} on which to launch the {@link Application}s * @param applicationClass the class of {@link Application} * @param options the {@link Option}s to use for launching the {@link Application}s * * @see Platform#launch(String, Option...) * @see AssemblyBuilder#include(int, Class, Option...) */ public void expand(int count, Platform platform, Class applicationClass, Option... options) { expand(count, Infrastructure.of(platform), applicationClass, options); } /** * Expands the number of {@link Application}s in the {@link Assembly} by launching and adding the specified number * of {@link Application}s on the provided {@link Infrastructure} using the zero or more provided {@link Option}s. * * @param count the number of instances of the {@link Application} that should be launched and added * to the {@link Assembly} * @param infrastructure the {@link Infrastructure} on which to launch the {@link Application}s * @param applicationClass the class of {@link Application} * @param options the {@link Option}s to use for launching the {@link Application}s * * @see Platform#launch(String, Option...) * @see AssemblyBuilder#include(int, Class, Option...) */ public void expand(int count, Infrastructure infrastructure, Class applicationClass, Option... options) { // we keep track of the new applications that are launched ArrayList launchedApplications = new ArrayList<>(); // determine the common expandingOptions OptionsByType expandingOptions = OptionsByType.of(optionsByType).addAll(options); for (int i = 0; i < count; i++) { // establish the launch options for the next application OptionsByType launchOptions = OptionsByType.of(expandingOptions); // include a discriminator for the application about to be launched // (if it has a DisplayName, doesn't have a Discriminator and there are more than one to launch) DisplayName displayName = launchOptions.getOrDefault(DisplayName.class, null); if (displayName != null &&!launchOptions.contains(Discriminator.class)) { // acquire the discriminator counter for the application DisplayName AtomicInteger counter = discriminators.computeIfAbsent(displayName, name -> new AtomicInteger(0)); // create a discriminator for the application launchOptions.addIfAbsent(Discriminator.of(counter.incrementAndGet())); } // attempt to launch the application try { // acquire the platform from the infrastructure based on the launch options Platform platform = infrastructure.getPlatform(launchOptions.asArray()); // launch the application A application = platform.launch(applicationClass, launchOptions.asArray()); // remember the application // (so we can add it to the assembly once they are all launched) launchedApplications.add(application); } catch (Throwable throwable) { // ensure all recently launched applications are shutdown to prevent applications staying around for (A application : launchedApplications) { try { application.close(); } catch (Throwable t) { // we ignore any issues when the application fails to close } } throw new RuntimeException("Failed to launch one of the desired " + applicationClass.getSimpleName() + "(s) out of " + count + " requested. " + "Automatically closed " + launchedApplications.size() + " that were successfully created. The options provided where " + launchOptions, throwable); } } // include the launched applications in the assembly for (A application : launchedApplications) { // ensure the assembly is a feature of the application so that it can be called back for lifecycle events application.add(Assembly.class, this); // add the application to the assembly applications.add(application); } // notify the assembly that it has expanded with the launched applications onExpanded(launchedApplications, expandingOptions); } /** * Called when the {@link Assembly} is first created and after the * {@link #expand(int, Platform, Class, Option...)}, {@link #add(Application)} and * {@link #clone(int, Option...)} methods have increased the number of {@link Application}s * in the {@link Assembly}. * * @param applications the newly added {@link Application}s to the {@link Assembly} * @param optionsByType the {@link OptionsByType} used for expanding */ @SuppressWarnings("unused") protected void onExpanded(List applications, OptionsByType optionsByType) { // notify the assembly of the change onChanged(optionsByType); } /** * Called prior to the {@link Assembly} closing and relaunching the specified {@link Application} * using the provided {@link OptionsByType}. When called the {@link Application} has yet * to be closed. * * @param application the {@link Application} to be relaunched * @param optionsByType the {@link OptionsByType} provided for relaunching */ protected void onRelaunching(A application, OptionsByType optionsByType) { // by default there's nothing to do prior to relaunching an application } /** * Called prior to the {@link Assembly} relaunching the now closed {@link Application} on the * specified {@link Platform}, allowing an implementation to override and customize the provided * {@link OptionsByType}, arguments and properties. * * @param platform the {@link Platform} * @param optionsByType the {@link OptionsByType} */ @SuppressWarnings("unused") protected void onRelaunching(Platform platform, OptionsByType optionsByType) { // notify the assembly of the change onChanged(optionsByType); } /** * Called after the {@link Assembly} has relaunched the {@link Application} using the * provided {@link OptionsByType}. * * @param original the original (now closed) {@link Application} * @param restarted the restarted {@link Application} * @param optionsByType the launch {@link OptionsByType} */ protected void onRelaunched(A original, A restarted, OptionsByType optionsByType) { // notify the assembly of the change onChanged(optionsByType); } /** * Called after the {@link Assembly} {@link Application}s have been added or removed. * * @param optionsByType the {@link OptionsByType} used for the change */ @SuppressWarnings("unchecked") protected void onChanged(OptionsByType optionsByType) { // ensure the stability of the assembly after it was changed StabilityPredicate> stabilityPredicate = optionsByType.getOrDefault(StabilityPredicate.class, null); if (stabilityPredicate != null) { DeferredPredicate deferredPredicate = new DeferredPredicate<>(this, stabilityPredicate.get()); ensure(eventually(deferredPredicate), is(true), optionsByType.asArray()); } } /** * Restarts the specified {@link Application}s using the provide {@link Option}s. * * @param applications the {@link Application}s to relaunch * @param options the {@link Option}s for restarting */ @SuppressWarnings("unchecked") protected void relaunch(List applications, Option... options) { // close and relaunch each application one at a time applications.forEach( application -> { // only relaunch if the application is in the assembly if (remove(application)) { // obtain some information about the application before closing it Platform platform = application.getPlatform(); OptionsByType applicationOptions = application.getOptions(); // establish the launch options // (based on the application and specified options) OptionsByType launchOptions = OptionsByType.of(applicationOptions).addAll(options); // notify the assembly that the application is about to be relaunched onRelaunching(application, launchOptions); // close the application (using the options) application.close(options); application.waitFor(); // notify the assembly we're about to relaunch an application onRelaunching(platform, launchOptions); // we'll create the same class of application Class applicationClass = (Class) application.getClass(); // (re) launch the application A relaunchedApplication = platform.launch(applicationClass, launchOptions.asArray()); // notify the assembly that the application was restarted onRelaunched(application, relaunchedApplication, launchOptions); // add the application to the assembly // (this will notify the assembly of the new application) add(relaunchedApplication); } }); } /** * Creates the specified number of "clones" of the specified {@link Application}s using the * provided override {@link Option}s. * * @param applications the {@link Application}s to clone * @param count the number of clones of each application * @param options the override {@link Option}s */ @SuppressWarnings("unchecked") protected void clone(List applications, int count, Option... options) { OptionsByType optionsByType = OptionsByType.of(options); boolean definesCustomDiscriminator = optionsByType.contains(Discriminator.class); // clone each application the desired amount applications.forEach( application -> { // obtain some information about the application Platform platform = application.getPlatform(); OptionsByType launchOptions = OptionsByType.of(application.getOptions()).addAll(options); // remove the existing Discriminator when a custom one hasn't been provided if (!definesCustomDiscriminator) { launchOptions.remove(Discriminator.class); } // we'll create the same class of application Class applicationClass = (Class) application.getClass(); // expand the assembly by the desired amount expand(count, platform, applicationClass, launchOptions.asArray()); }); } @Override public Iterator iterator() { return applications.iterator(); } @Override public void close() { this.close(new Option[] { }); } @Override public void close(Option... options) { if (isClosed.compareAndSet(false, true)) { for (A application : applications) { if (application != null) { // ensure the assembly is no longer a feature so that won't be called back for lifecycle events application.remove(Assembly.class); try { application.close(options); } catch (Exception e) { // skip: we always ignore } } } // now remove the applications applications.clear(); } } @Override public void onClosing(A application, OptionsByType optionsByType) { if (!isClosed()) { // when we're not in the process of closing the cluster we must remove // the application as it's been closed explicitly applications.remove(application); } } @Override public void onClosed(A application, OptionsByType optionsByType) { // SKIP: nothing to do when an application is closed } @Override public void onLaunched(A application) { // SKIP: nothing to do when an application is launched } /** * Obtains an {@link ApplicationStream} for the current {@link Application}s * in the {@link Assembly}. * * @return an {@link ApplicationStream} */ public ApplicationStream stream() { return new StreamAdapter(applications.stream()); } @Override public void forEach(Consumer action) { stream().forEach(action); } @Override public long count() { return applications.size(); } @Override public ApplicationStream filter(java.util.function.Predicate predicate) { return stream().filter(predicate); } @Override public boolean allMatch(java.util.function.Predicate predicate) { return stream().allMatch(predicate); } @Override public boolean anyMatch(java.util.function.Predicate predicate) { return stream().anyMatch(predicate); } @Override public boolean noneMatch(java.util.function.Predicate predicate) { return stream().noneMatch(predicate); } @Override public ApplicationStream limit(int maximum) { return stream().limit(maximum); } @Override public ApplicationStream peek(Consumer consumer) { return stream().peek(consumer); } @Override public R collect(Collector collector) { return stream().collect(collector); } @Override public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { return stream().collect(supplier, accumulator, combiner); } @Override public Optional findAny() { return stream().findAny(); } @Override public Optional findFirst() { return stream().findFirst(); } @Override public ApplicationStream unordered() { return stream().unordered(); } @Override public void relaunch(Option... options) { stream().relaunch(options); } @Override public void clone(int count, Option... options) { stream().clone(count, options); } /** * Return an {@link ApplicationStream} that wraps a stream of the {@link Application applications}. * * @param stream the stream of the {@link Application applications} * * @return an {@link ApplicationStream} that wraps the stream of the {@link Application applications} */ protected ApplicationStream streamOf(Stream stream) { return new StreamAdapter(stream); } /** * An internal implementation of an {@link ApplicationStream} that * adapts a regular {@link Stream} of {@link Application}s into an * {@link ApplicationStream}. This allows the {@link ApplicationStream} * to interact with the underlying {@link Assembly} from which it was * produced. */ protected class StreamAdapter implements ApplicationStream { /** * The {@link Stream} of {@link Application}s to adapt into an {@link ApplicationStream}. */ private final Stream stream; /** * Constructs a {@link StreamAdapter}. * * @param stream the stream of {@link Application applications} */ protected StreamAdapter(Stream stream) { this.stream = stream; } @Override public void close(Option... options) { stream.forEach(a -> a.close(options)); } @Override public void forEach(Consumer consumer) { stream.forEach(consumer); } @Override public long count() { return stream.count(); } @Override public ApplicationStream filter(Predicate predicate) { return new StreamAdapter(stream.filter(predicate)); } @Override public boolean allMatch(Predicate predicate) { return stream.allMatch(predicate); } @Override public boolean anyMatch(Predicate predicate) { return stream.anyMatch(predicate); } @Override public boolean noneMatch(Predicate predicate) { return stream.noneMatch(predicate); } @Override public ApplicationStream limit(int maximum) { return new StreamAdapter(stream.limit(maximum)); } @Override public ApplicationStream peek(Consumer consumer) { return new StreamAdapter(stream.peek(consumer)); } @Override public R collect(Collector collector) { return stream.collect(collector); } @Override public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { return stream.collect(supplier, accumulator, combiner); } @Override public Optional findAny() { return stream.findAny(); } @Override public Optional findFirst() { return stream.findFirst(); } @Override public ApplicationStream unordered() { // collect all of the applications in this stream List applications = stream.collect(Collectors.toList()); // shuffle them! Collections.shuffle(applications); // return a new stream based on the shuffled applications return new StreamAdapter(applications.stream()); } @Override public void relaunch(Option... options) { // collect the applications we need to relaunch List applications = stream.collect(Collectors.toList()); // have the assembly relaunch these applications with the provided options AbstractAssembly.this.relaunch(applications, options); } @Override public void clone(int count, Option... options) { // collect the applications we need to clone List applications = stream.collect(Collectors.toList()); // have the assembly clone these applications with the provided options AbstractAssembly.this.clone(applications, count, options); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy