Refactor BootstrapRegistry support
Refactor `BootstrapRegistry` support following initial prototype work with the Spring Cloud team. This update splits the `BootstrapRegistry` API into `BootstrapRegistry`, `BootstrapContext` and `ConfigurableBootstrapContext` interfaces and moves it to the same package as `SpringApplication`. A new `Bootstrapper` interface has been introduced that can be added to the `SpringApplication` to customize the `BootstrapRegistry` before it's used. Closes gh-23326pull/23391/head
parent
27095d9043
commit
1ae1436211
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* A simple bootstrap context that is available during startup and {@link Environment}
|
||||
* post-processing up to the point that the {@link ApplicationContext} is prepared.
|
||||
* <p>
|
||||
* Provides lazy access to singletons that may be expensive to create, or need to be
|
||||
* shared before the {@link ApplicationContext} is available.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public interface BootstrapContext {
|
||||
|
||||
/**
|
||||
* Return an instance from the context, creating it if it hasn't been accessed
|
||||
* previously.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @return the instance managed by the context
|
||||
* @throws IllegalStateException if the type has not been registered
|
||||
*/
|
||||
<T> T get(Class<T> type) throws IllegalStateException;
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
/**
|
||||
* {@link ApplicationEvent} published by a {@link BootstrapContext} when it's closed.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
* @see BootstrapRegistry#addCloseListener(org.springframework.context.ApplicationListener)
|
||||
*/
|
||||
public class BootstrapContextClosedEvent extends ApplicationEvent {
|
||||
|
||||
private final ConfigurableApplicationContext applicationContext;
|
||||
|
||||
BootstrapContextClosedEvent(BootstrapContext source, ConfigurableApplicationContext applicationContext) {
|
||||
super(source);
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link BootstrapContext} that was closed.
|
||||
* @return the bootstrap context
|
||||
*/
|
||||
public BootstrapContext getBootstrapContext() {
|
||||
return (BootstrapContext) this.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prepared application context.
|
||||
* @return the application context
|
||||
*/
|
||||
public ConfigurableApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.undertow.servlet.api.InstanceFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* A simple object registry that is available during startup and {@link Environment}
|
||||
* post-processing up to the point that the {@link ApplicationContext} is prepared.
|
||||
* <p>
|
||||
* Can be used to register instances that may be expensive to create, or need to be shared
|
||||
* before the {@link ApplicationContext} is available.
|
||||
* <p>
|
||||
* The registry uses {@link Class} as a key, meaning that only a single instance of a
|
||||
* given type can be stored.
|
||||
* <p>
|
||||
* The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener
|
||||
* that can perform actions when {@link BootstrapContext} has been closed and the
|
||||
* {@link ApplicationContext} is fully prepared. For example, an instance may choose to
|
||||
* register itself as a regular Spring bean so that it is available for the application to
|
||||
* use.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
* @see BootstrapContext
|
||||
* @see ConfigurableBootstrapContext
|
||||
*/
|
||||
public interface BootstrapRegistry {
|
||||
|
||||
/**
|
||||
* Register a specific type with the registry. If the specified type has already been
|
||||
* registered, but not get obtained, it will be replaced.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @param instanceSupplier the instance supplier
|
||||
*/
|
||||
<T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);
|
||||
|
||||
/**
|
||||
* Register a specific type with the registry if one is not already present.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @param instanceSupplier the instance supplier
|
||||
*/
|
||||
<T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier);
|
||||
|
||||
/**
|
||||
* Return if a registration exists for the given type.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @return {@code true} if the type has already been registered
|
||||
*/
|
||||
<T> boolean isRegistered(Class<T> type);
|
||||
|
||||
/**
|
||||
* Return any existing {@link InstanceFactory} for the given type.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @return the registered {@link InstanceSupplier} or {@code null}
|
||||
*/
|
||||
<T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);
|
||||
|
||||
/**
|
||||
* Add an {@link ApplicationListener} that will be called with a
|
||||
* {@link BootstrapContextClosedEvent} when the {@link BootstrapContext} is closed and
|
||||
* the {@link ApplicationContext} has been prepared.
|
||||
* @param listener the listener to add
|
||||
*/
|
||||
void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);
|
||||
|
||||
/**
|
||||
* Supplier used to provide the actual instance the first time it is accessed.
|
||||
*
|
||||
* @param <T> the instance type
|
||||
*/
|
||||
public interface InstanceSupplier<T> {
|
||||
|
||||
/**
|
||||
* Factory method used to create the instance when needed.
|
||||
* @param context the {@link BootstrapContext} which may be used to obtain other
|
||||
* bootstrap instances.
|
||||
* @return the instance
|
||||
*/
|
||||
T get(BootstrapContext context);
|
||||
|
||||
/**
|
||||
* Factory method that can be used to create a {@link InstanceFactory} for a given
|
||||
* instance.
|
||||
* @param <T> the instance type
|
||||
* @param instance the instance
|
||||
* @return a new {@link InstanceFactory}
|
||||
*/
|
||||
static <T> InstanceSupplier<T> of(T instance) {
|
||||
return (registry) -> instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that can be used to create a {@link InstanceFactory} from a
|
||||
* {@link Supplier}.
|
||||
* @param <T> the instance type
|
||||
* @param supplier the supplier that will provide the instance
|
||||
* @return a new {@link InstanceFactory}
|
||||
*/
|
||||
static <T> InstanceSupplier<T> from(Supplier<T> supplier) {
|
||||
return (registry) -> (supplier != null) ? supplier.get() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to initialize a {@link BootstrapRegistry} before it
|
||||
* is used.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
* @see SpringApplication#addBootstrapper(Bootstrapper)
|
||||
* @see BootstrapRegistry
|
||||
*/
|
||||
public interface Bootstrapper {
|
||||
|
||||
/**
|
||||
* Initialize the given {@link BootstrapRegistry} with any required registrations.
|
||||
* @param registry the registry to initialize
|
||||
*/
|
||||
void intitialize(BootstrapRegistry registry);
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
/**
|
||||
* A {@link BootstrapContext} that also provides configuration methods via the
|
||||
* {@link BootstrapRegistry} interface.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
* @see BootstrapRegistry
|
||||
* @see BootstrapContext
|
||||
* @see DefaultBootstrapContext
|
||||
*/
|
||||
public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext {
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.event.ApplicationEventMulticaster;
|
||||
import org.springframework.context.event.SimpleApplicationEventMulticaster;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default {@link ConfigurableBootstrapContext} implementation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
|
||||
|
||||
private final Map<Class<?>, InstanceSupplier<?>> instanceSuppliers = new HashMap<>();
|
||||
|
||||
private final Map<Class<?>, Object> instances = new HashMap<>();
|
||||
|
||||
private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster();
|
||||
|
||||
@Override
|
||||
public <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier) {
|
||||
register(type, instanceSupplier, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier) {
|
||||
register(type, instanceSupplier, false);
|
||||
}
|
||||
|
||||
private <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier, boolean replaceExisting) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
Assert.notNull(instanceSupplier, "InstanceSupplier must not be null");
|
||||
synchronized (this.instanceSuppliers) {
|
||||
boolean alreadyRegistered = this.instanceSuppliers.containsKey(type);
|
||||
if (replaceExisting || !alreadyRegistered) {
|
||||
Assert.state(!this.instances.containsKey(type), () -> type.getName() + " has already been created");
|
||||
this.instanceSuppliers.put(type, instanceSupplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean isRegistered(Class<T> type) {
|
||||
synchronized (this.instanceSuppliers) {
|
||||
return this.instanceSuppliers.containsKey(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type) {
|
||||
synchronized (this.instanceSuppliers) {
|
||||
return (InstanceSupplier<T>) this.instanceSuppliers.get(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener) {
|
||||
this.events.addApplicationListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(Class<T> type) throws IllegalStateException {
|
||||
synchronized (this.instanceSuppliers) {
|
||||
InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
|
||||
Assert.state(instanceSupplier != null, () -> type.getName() + " has not been registered");
|
||||
return (T) this.instances.computeIfAbsent(type, (key) -> instanceSupplier.get(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be called when {@link BootstrapContext} is closed and the
|
||||
* {@link ApplicationContext} is prepared.
|
||||
* @param applicationContext the prepared context
|
||||
*/
|
||||
public void close(ConfigurableApplicationContext applicationContext) {
|
||||
this.events.multicastEvent(new BootstrapContextClosedEvent(this, applicationContext));
|
||||
}
|
||||
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot.env;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* A simple object registry that is available during {@link Environment} post-processing
|
||||
* up to the point that the {@link ApplicationContext} is prepared. The registry can be
|
||||
* used to store objects that may be expensive to create, or need to be shared by
|
||||
* different {@link EnvironmentPostProcessor EnvironmentPostProcessors}.
|
||||
* <p>
|
||||
* The registry uses the object type as a key, meaning that only a single instance of a
|
||||
* given class can be stored.
|
||||
* <p>
|
||||
* Registered instances may optionally use
|
||||
* {@link Registration#onApplicationContextPrepared(BiConsumer)
|
||||
* onApplicationContextPrepared(...)} to perform an action when the
|
||||
* {@link ApplicationContext} is {@link ApplicationPreparedEvent prepared}. For example,
|
||||
* an instance may choose to register itself as a regular Spring bean so that it is
|
||||
* available for the application to use.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
* @see EnvironmentPostProcessor
|
||||
*/
|
||||
public interface BootstrapRegistry {
|
||||
|
||||
/**
|
||||
* Get an instance from the registry, creating one if it does not already exist.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @param instanceSupplier a supplier used to create the instance if it doesn't
|
||||
* already exist
|
||||
* @return the registered instance
|
||||
*/
|
||||
<T> T get(Class<T> type, Supplier<T> instanceSupplier);
|
||||
|
||||
/**
|
||||
* Get an instance from the registry, creating one if it does not already exist.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @param instanceSupplier a supplier used to create the instance if it doesn't
|
||||
* already exist
|
||||
* @param onApplicationContextPreparedAction the action that should be called when the
|
||||
* application context is prepared. This action is ignored if the registration already
|
||||
* exists.
|
||||
* @return the registered instance
|
||||
*/
|
||||
<T> T get(Class<T> type, Supplier<T> instanceSupplier,
|
||||
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction);
|
||||
|
||||
/**
|
||||
* Register an instance with the registry and return a {@link Registration} that can
|
||||
* be used to provide further configuration. This method will replace any existing
|
||||
* registration.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @param instanceSupplier a supplier used to create the instance if it doesn't
|
||||
* already exist
|
||||
* @return an instance registration
|
||||
*/
|
||||
<T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier);
|
||||
|
||||
/**
|
||||
* Return if a registration exists for the given type.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @return {@code true} if the type has already been registered
|
||||
*/
|
||||
<T> boolean isRegistered(Class<T> type);
|
||||
|
||||
/**
|
||||
* Return any existing {@link Registration} for the given type.
|
||||
* @param <T> the instance type
|
||||
* @param type the instance type
|
||||
* @return the existing registration or {@code null}
|
||||
*/
|
||||
<T> Registration<T> getRegistration(Class<T> type);
|
||||
|
||||
/**
|
||||
* A single registration contained in the registry.
|
||||
*
|
||||
* @param <T> the instance type
|
||||
*/
|
||||
interface Registration<T> {
|
||||
|
||||
/**
|
||||
* Get or crearte the registered object instance.
|
||||
* @return the object instance
|
||||
*/
|
||||
T get();
|
||||
|
||||
/**
|
||||
* Add an action that should run when the {@link ApplicationContext} has been
|
||||
* prepared.
|
||||
* @param action the action to run
|
||||
*/
|
||||
void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot.env;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link BootstrapRegistry}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class DefaultBootstrapRegisty implements BootstrapRegistry {
|
||||
|
||||
private final Map<Class<?>, DefaultRegistration<?>> registrations = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public <T> T get(Class<T> type, Supplier<T> instanceSupplier) {
|
||||
return get(type, instanceSupplier, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(Class<T> type, Supplier<T> instanceSupplier,
|
||||
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction) {
|
||||
Registration<T> registration = getRegistration(type);
|
||||
if (registration != null) {
|
||||
return registration.get();
|
||||
}
|
||||
registration = register(type, instanceSupplier);
|
||||
registration.onApplicationContextPrepared(onApplicationContextPreparedAction);
|
||||
return registration.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier) {
|
||||
DefaultRegistration<T> registration = new DefaultRegistration<>(instanceSupplier);
|
||||
this.registrations.put(type, registration);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean isRegistered(Class<T> type) {
|
||||
return getRegistration(type) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Registration<T> getRegistration(Class<T> type) {
|
||||
return (Registration<T>) this.registrations.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be called when the {@link ApplicationContext} is prepared.
|
||||
* @param applicationContext the prepared context
|
||||
*/
|
||||
public void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
|
||||
this.registrations.values()
|
||||
.forEach((registration) -> registration.applicationContextPrepared(applicationContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the registry to reclaim memory.
|
||||
*/
|
||||
public void clear() {
|
||||
this.registrations.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link Registration}.
|
||||
*/
|
||||
private static class DefaultRegistration<T> implements Registration<T> {
|
||||
|
||||
private Supplier<T> instanceSupplier;
|
||||
|
||||
private volatile T instance;
|
||||
|
||||
private List<BiConsumer<ConfigurableApplicationContext, T>> applicationContextPreparedActions = new ArrayList<>();
|
||||
|
||||
DefaultRegistration(Supplier<T> instanceSupplier) {
|
||||
this.instanceSupplier = instanceSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
T instance = this.instance;
|
||||
if (instance == null) {
|
||||
synchronized (this.instanceSupplier) {
|
||||
instance = this.instanceSupplier.get();
|
||||
this.instance = instance;
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action) {
|
||||
if (action != null) {
|
||||
this.applicationContextPreparedActions.add(action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when the {@link ApplicationContext} is prepared.
|
||||
* @param applicationContext the prepared context
|
||||
*/
|
||||
void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
|
||||
this.applicationContextPreparedActions.forEach((consumer) -> consumer.accept(applicationContext, get()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultBootstrapContext}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DefaultBootstrapContextTests {
|
||||
|
||||
private DefaultBootstrapContext context = new DefaultBootstrapContext();
|
||||
|
||||
private AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
private StaticApplicationContext applicationContext = new StaticApplicationContext();
|
||||
|
||||
@Test
|
||||
void registerWhenTypeIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.context.register(null, InstanceSupplier.of(1)))
|
||||
.withMessage("Type must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenRegistrationIsNullThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.context.register(Integer.class, null))
|
||||
.withMessage("InstanceSupplier must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenNotAlreadyRegisteredRegistersInstance() {
|
||||
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
|
||||
assertThat(this.context.get(Integer.class)).isEqualTo(0);
|
||||
assertThat(this.context.get(Integer.class)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenAlreadyRegisteredRegistersReplacedInstance() {
|
||||
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
|
||||
this.context.register(Integer.class, InstanceSupplier.of(100));
|
||||
assertThat(this.context.get(Integer.class)).isEqualTo(100);
|
||||
assertThat(this.context.get(Integer.class)).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenAlreadyCreatedThrowsException() {
|
||||
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
|
||||
this.context.get(Integer.class);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.context.register(Integer.class, InstanceSupplier.of(100)))
|
||||
.withMessage("java.lang.Integer has already been created");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithDependencyRegistersInstance() {
|
||||
this.context.register(Integer.class, InstanceSupplier.of(100));
|
||||
this.context.register(String.class, this::integerAsString);
|
||||
assertThat(this.context.get(String.class)).isEqualTo("100");
|
||||
}
|
||||
|
||||
private String integerAsString(BootstrapContext context) {
|
||||
return String.valueOf(context.get(Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerIfAbsentWhenAbsentRegisters() {
|
||||
this.context.registerIfAbsent(Long.class, InstanceSupplier.of(100L));
|
||||
assertThat(this.context.get(Long.class)).isEqualTo(100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerIfAbsentWhenPresentDoesNotRegister() {
|
||||
this.context.registerIfAbsent(Long.class, InstanceSupplier.of(1L));
|
||||
this.context.registerIfAbsent(Long.class, InstanceSupplier.of(100L));
|
||||
assertThat(this.context.get(Long.class)).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredWhenNotRegisteredReturnsFalse() {
|
||||
this.context.register(Number.class, InstanceSupplier.of(1));
|
||||
assertThat(this.context.isRegistered(Long.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredWhenRegisteredReturnsTrue() {
|
||||
this.context.register(Number.class, InstanceSupplier.of(1));
|
||||
assertThat(this.context.isRegistered(Number.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRegisteredInstanceSupplierWhenNotRegisteredReturnsNull() {
|
||||
this.context.register(Number.class, InstanceSupplier.of(1));
|
||||
assertThat(this.context.getRegisteredInstanceSupplier(Long.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRegisteredInstanceSupplierWhenRegisteredReturnsRegistration() {
|
||||
InstanceSupplier<Number> instanceSupplier = InstanceSupplier.of(1);
|
||||
this.context.register(Number.class, instanceSupplier);
|
||||
assertThat(this.context.getRegisteredInstanceSupplier(Number.class)).isSameAs(instanceSupplier);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenNoRegistrationThrowsIllegalStateException() {
|
||||
this.context.register(Number.class, InstanceSupplier.of(1));
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.context.get(Long.class))
|
||||
.withMessageContaining("has not been registered");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenRegisteredAsNullReturnsNull() {
|
||||
this.context.register(Number.class, InstanceSupplier.of(null));
|
||||
assertThat(this.context.get(Number.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCreatesOnlyOneInstance() {
|
||||
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
|
||||
assertThat(this.context.get(Integer.class)).isEqualTo(0);
|
||||
assertThat(this.context.get(Integer.class)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeMulticastsEventToListeners() {
|
||||
TestCloseListener listener = new TestCloseListener();
|
||||
this.context.addCloseListener(listener);
|
||||
assertThat(listener).wasNotCalled();
|
||||
this.context.close(this.applicationContext);
|
||||
assertThat(listener).wasCalledOnlyOnce().hasBootstrapContextSameAs(this.context)
|
||||
.hasApplicationContextSameAs(this.applicationContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCloseListenerIgnoresMultipleCallsWithSameListener() {
|
||||
TestCloseListener listener = new TestCloseListener();
|
||||
this.context.addCloseListener(listener);
|
||||
this.context.addCloseListener(listener);
|
||||
this.context.close(this.applicationContext);
|
||||
assertThat(listener).wasCalledOnlyOnce();
|
||||
}
|
||||
|
||||
private static class TestCloseListener
|
||||
implements ApplicationListener<BootstrapContextClosedEvent>, AssertProvider<CloseListenerAssert> {
|
||||
|
||||
private int called;
|
||||
|
||||
private BootstrapContext bootstrapContext;
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(BootstrapContextClosedEvent event) {
|
||||
this.called++;
|
||||
this.bootstrapContext = event.getBootstrapContext();
|
||||
this.applicationContext = event.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseListenerAssert assertThat() {
|
||||
return new CloseListenerAssert(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CloseListenerAssert extends AbstractAssert<CloseListenerAssert, TestCloseListener> {
|
||||
|
||||
CloseListenerAssert(TestCloseListener actual) {
|
||||
super(actual, CloseListenerAssert.class);
|
||||
}
|
||||
|
||||
CloseListenerAssert wasCalledOnlyOnce() {
|
||||
assertThat(this.actual.called).as("action calls").isEqualTo(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
CloseListenerAssert wasNotCalled() {
|
||||
assertThat(this.actual.called).as("action calls").isEqualTo(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
CloseListenerAssert hasBootstrapContextSameAs(BootstrapContext bootstrapContext) {
|
||||
assertThat(this.actual.bootstrapContext).isSameAs(bootstrapContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
CloseListenerAssert hasApplicationContextSameAs(ApplicationContext applicationContext) {
|
||||
assertThat(this.actual.applicationContext).isSameAs(applicationContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot.env;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.env.BootstrapRegistry.Registration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link DefaultBootstrapRegisty}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DefaultBootstrapRegistyTests {
|
||||
|
||||
private DefaultBootstrapRegisty registy = new DefaultBootstrapRegisty();
|
||||
|
||||
private AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
private StaticApplicationContext context = new StaticApplicationContext();
|
||||
|
||||
@Test
|
||||
void getWhenNotRegisteredCreateInstance() {
|
||||
Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(result).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenAlreadyRegisteredReturnsExisting() {
|
||||
this.registy.get(Integer.class, this.counter::getAndIncrement);
|
||||
Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(result).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithPreparedActionRegistersAction() {
|
||||
TestApplicationPreparedAction action = new TestApplicationPreparedAction();
|
||||
Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement, action::run);
|
||||
this.registy.applicationContextPrepared(this.context);
|
||||
assertThat(result).isEqualTo(0);
|
||||
assertThat(action).wasCalledOnlyOnce().hasInstanceValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithPreparedActionWhenAlreadyRegisteredIgnoresRegistersAction() {
|
||||
TestApplicationPreparedAction action1 = new TestApplicationPreparedAction();
|
||||
TestApplicationPreparedAction action2 = new TestApplicationPreparedAction();
|
||||
this.registy.get(Integer.class, this.counter::getAndIncrement, action1::run);
|
||||
this.registy.get(Integer.class, this.counter::getAndIncrement, action2::run);
|
||||
this.registy.applicationContextPrepared(this.context);
|
||||
assertThat(action1).wasCalledOnlyOnce().hasInstanceValue(0);
|
||||
assertThat(action2).wasNotCalled();
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerAddsRegistration() {
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(registration.get()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenAlreadyRegisteredReplacesPreviousRegistration() {
|
||||
Registration<Integer> registration1 = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
Registration<Integer> registration2 = this.registy.register(Integer.class, () -> -1);
|
||||
assertThat(registration2).isNotEqualTo(registration1);
|
||||
assertThat(registration1.get()).isEqualTo(0);
|
||||
assertThat(registration2.get()).isEqualTo(-1);
|
||||
assertThat(this.registy.get(Integer.class, this.counter::getAndIncrement)).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredWhenNotRegisteredReturnsFalse() {
|
||||
assertThat(this.registy.isRegistered(Integer.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredWhenRegisteredReturnsTrue() {
|
||||
this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(this.registy.isRegistered(Integer.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRegistrationWhenNotRegisteredReturnsNull() {
|
||||
assertThat(this.registy.getRegistration(Integer.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRegistrationWhenRegisteredReturnsRegistration() {
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(this.registy.getRegistration(Integer.class)).isSameAs(registration);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applicationContextPreparedTriggersActions() {
|
||||
TestApplicationPreparedAction action1 = new TestApplicationPreparedAction();
|
||||
TestApplicationPreparedAction action2 = new TestApplicationPreparedAction();
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
registration.onApplicationContextPrepared(action1::run);
|
||||
registration.onApplicationContextPrepared(action2::run);
|
||||
this.registy.applicationContextPrepared(this.context);
|
||||
assertThat(action1).wasCalledOnlyOnce().hasInstanceValue(0);
|
||||
assertThat(action2).wasCalledOnlyOnce().hasInstanceValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationGetReturnsInstance() {
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(registration.get()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationGetWhenCalledMultipleTimesReturnsSingleInstance() {
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
assertThat(registration.get()).isEqualTo(0);
|
||||
assertThat(registration.get()).isEqualTo(0);
|
||||
assertThat(registration.get()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationOnApplicationContextPreparedAddsAction() {
|
||||
TestApplicationPreparedAction action = new TestApplicationPreparedAction();
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
registration.onApplicationContextPrepared(action::run);
|
||||
this.registy.applicationContextPrepared(this.context);
|
||||
assertThat(action).wasCalledOnlyOnce().hasInstanceValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationOnApplicationContextPreparedWhenActionIsNullDoesNotAddAction() {
|
||||
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
|
||||
registration.onApplicationContextPrepared(null);
|
||||
this.registy.applicationContextPrepared(this.context);
|
||||
}
|
||||
|
||||
private static class TestApplicationPreparedAction implements AssertProvider<ApplicationPreparedActionAssert> {
|
||||
|
||||
private Integer instance;
|
||||
|
||||
private int called;
|
||||
|
||||
void run(ConfigurableApplicationContext context, Integer instance) {
|
||||
this.instance = instance;
|
||||
this.called++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationPreparedActionAssert assertThat() {
|
||||
return new ApplicationPreparedActionAssert(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ApplicationPreparedActionAssert
|
||||
extends AbstractAssert<ApplicationPreparedActionAssert, TestApplicationPreparedAction> {
|
||||
|
||||
ApplicationPreparedActionAssert(TestApplicationPreparedAction actual) {
|
||||
super(actual, ApplicationPreparedActionAssert.class);
|
||||
}
|
||||
|
||||
ApplicationPreparedActionAssert hasInstanceValue(Integer expected) {
|
||||
assertThat(this.actual.instance).isEqualTo(expected);
|
||||
return this;
|
||||
}
|
||||
|
||||
ApplicationPreparedActionAssert wasCalledOnlyOnce() {
|
||||
assertThat(this.actual.called).as("action calls").isEqualTo(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
ApplicationPreparedActionAssert wasNotCalled() {
|
||||
assertThat(this.actual.called).as("action calls").isEqualTo(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot.conventions"
|
||||
}
|
||||
|
||||
description = "Spring Boot Bootstrap Registry smoke test"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
|
||||
|
||||
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.app;
|
||||
|
||||
import smoketest.bootstrapregistry.external.svn.SubversionClient;
|
||||
import smoketest.bootstrapregistry.external.svn.SubversionServerCertificate;
|
||||
|
||||
public class MySubversionClient extends SubversionClient {
|
||||
|
||||
public MySubversionClient(SubversionServerCertificate serverCertificate) {
|
||||
super(serverCertificate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String load(String location) {
|
||||
return "my-" + super.load(location);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.app;
|
||||
|
||||
import smoketest.bootstrapregistry.external.svn.SubversionClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class Printer {
|
||||
|
||||
Printer(@Value("${svn}") String svn, SubversionClient subversionClient) {
|
||||
System.out.println("--- svn " + svn);
|
||||
System.out.println("--- client " + subversionClient.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.app;
|
||||
|
||||
import smoketest.bootstrapregistry.external.svn.SubversionBootstrap;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleBootstrapRegistryApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// This example shows how a Bootstrapper can be used to register a custom
|
||||
// SubversionClient that still has access to data provided in the
|
||||
// application.properties file
|
||||
SpringApplication application = new SpringApplication(SampleBootstrapRegistryApplication.class);
|
||||
application.addBootstrapper(SubversionBootstrap.withCustomClient(MySubversionClient::new));
|
||||
application.run(args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.external.svn;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.BootstrapContext;
|
||||
import org.springframework.boot.Bootstrapper;
|
||||
|
||||
/**
|
||||
* Allows the user to register a {@link Bootstrapper} with a custom
|
||||
* {@link SubversionClient}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class SubversionBootstrap {
|
||||
|
||||
private SubversionBootstrap() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Bootstrapper} for the given client factory.
|
||||
* @param clientFactory the client factory
|
||||
* @return a {@link Bootstrapper} instance
|
||||
*/
|
||||
public static Bootstrapper withCustomClient(Function<SubversionServerCertificate, SubversionClient> clientFactory) {
|
||||
return (registry) -> registry.register(SubversionClient.class,
|
||||
(bootstrapContext) -> createSubversionClient(bootstrapContext, clientFactory));
|
||||
}
|
||||
|
||||
private static SubversionClient createSubversionClient(BootstrapContext bootstrapContext,
|
||||
Function<SubversionServerCertificate, SubversionClient> clientFactory) {
|
||||
return clientFactory.apply(bootstrapContext.get(SubversionServerCertificate.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.external.svn;
|
||||
|
||||
/**
|
||||
* A client that can connect to a subversion server.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SubversionClient {
|
||||
|
||||
private SubversionServerCertificate serverCertificate;
|
||||
|
||||
public SubversionClient(SubversionServerCertificate serverCertificate) {
|
||||
this.serverCertificate = serverCertificate;
|
||||
}
|
||||
|
||||
public String load(String location) {
|
||||
return "data from svn / " + location + "[" + this.serverCertificate + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.external.svn;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.BootstrapContext;
|
||||
import org.springframework.boot.BootstrapContextClosedEvent;
|
||||
import org.springframework.boot.BootstrapRegistry;
|
||||
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
|
||||
import org.springframework.boot.context.config.ConfigData;
|
||||
import org.springframework.boot.context.config.ConfigDataLoader;
|
||||
import org.springframework.boot.context.config.ConfigDataLoaderContext;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
/**
|
||||
* {@link ConfigDataLoader} for subversion.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class SubversionConfigDataLoader implements ConfigDataLoader<SubversionConfigDataLocation> {
|
||||
|
||||
private static final ApplicationListener<BootstrapContextClosedEvent> closeListener = SubversionConfigDataLoader::onBootstrapContextClosed;
|
||||
|
||||
SubversionConfigDataLoader(BootstrapRegistry bootstrapRegistry) {
|
||||
bootstrapRegistry.registerIfAbsent(SubversionClient.class, this::createSubversionClient);
|
||||
bootstrapRegistry.addCloseListener(closeListener);
|
||||
}
|
||||
|
||||
private SubversionClient createSubversionClient(BootstrapContext bootstrapContext) {
|
||||
return new SubversionClient(bootstrapContext.get(SubversionServerCertificate.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigData load(ConfigDataLoaderContext context, SubversionConfigDataLocation location)
|
||||
throws IOException, ConfigDataLocationNotFoundException {
|
||||
context.getBootstrapContext().registerIfAbsent(SubversionServerCertificate.class,
|
||||
InstanceSupplier.of(location.getServerCertificate()));
|
||||
SubversionClient client = context.getBootstrapContext().get(SubversionClient.class);
|
||||
String loaded = client.load(location.getLocation());
|
||||
PropertySource<?> propertySource = new MapPropertySource("svn", Collections.singletonMap("svn", loaded));
|
||||
return new ConfigData(Collections.singleton(propertySource));
|
||||
}
|
||||
|
||||
private static void onBootstrapContextClosed(BootstrapContextClosedEvent event) {
|
||||
event.getApplicationContext().getBeanFactory().registerSingleton("subversionClient",
|
||||
event.getBootstrapContext().get(SubversionClient.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.external.svn;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocation;
|
||||
|
||||
/**
|
||||
* A subversion {@link ConfigDataLocation}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class SubversionConfigDataLocation extends ConfigDataLocation {
|
||||
|
||||
private final String location;
|
||||
|
||||
private final SubversionServerCertificate serverCertificate;
|
||||
|
||||
SubversionConfigDataLocation(String location, String serverCertificate) {
|
||||
this.location = location;
|
||||
this.serverCertificate = SubversionServerCertificate.of(serverCertificate);
|
||||
}
|
||||
|
||||
String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
SubversionServerCertificate getServerCertificate() {
|
||||
return this.serverCertificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SubversionConfigDataLocation other = (SubversionConfigDataLocation) obj;
|
||||
return this.location.equals(other.location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.location.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.external.svn;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolver;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
|
||||
|
||||
/**
|
||||
* {@link ConfigDataLocationResolver} for subversion.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver<SubversionConfigDataLocation> {
|
||||
|
||||
private static final String PREFIX = "svn:";
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
|
||||
return location.startsWith(PREFIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubversionConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) throws ConfigDataLocationNotFoundException {
|
||||
String serverCertificate = context.getBinder().bind("spring.svn.server.certificate", String.class).orElse(null);
|
||||
return Collections.singletonList(
|
||||
new SubversionConfigDataLocation(location.substring(PREFIX.length()), serverCertificate));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.external.svn;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A certificate that can be used to provide a secure connection to the subversion server.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SubversionServerCertificate {
|
||||
|
||||
private final String data;
|
||||
|
||||
SubversionServerCertificate(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public static SubversionServerCertificate of(String data) {
|
||||
return StringUtils.hasText(data) ? new SubversionServerCertificate(data) : null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An example of a hypothetical library that supports subversion.
|
||||
*/
|
||||
package smoketest.bootstrapregistry.external.svn;
|
@ -0,0 +1,5 @@
|
||||
org.springframework.boot.context.config.ConfigDataLocationResolver=\
|
||||
smoketest.bootstrapregistry.external.svn.SubversionConfigDataLocationResolver
|
||||
|
||||
org.springframework.boot.context.config.ConfigDataLoader=\
|
||||
smoketest.bootstrapregistry.external.svn.SubversionConfigDataLoader
|
@ -0,0 +1,2 @@
|
||||
spring.svn.server.certificate=secret
|
||||
spring.config.import=svn:example.com
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 smoketest.bootstrapregistry.app;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SampleBootstrapRegistryApplication}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SampleBootstrapRegistryApplicationTests {
|
||||
|
||||
@Test
|
||||
void testBootrapper(CapturedOutput output) {
|
||||
SampleBootstrapRegistryApplication.main(new String[0]);
|
||||
assertThat(output).contains("svn my-data from svn / example.com[secret]")
|
||||
.contains("client smoketest.bootstrapregistry.app.MySubversionClient");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue