Compare commits
23 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a89e89504f | |
|
|
aae9f47cbc | |
|
|
580ad488f0 | |
|
|
4b8db9b630 | |
|
|
85bbbc99ad | |
|
|
01d93513c7 | |
|
|
40453e76f0 | |
|
|
7459fe0163 | |
|
|
859606ae13 | |
|
|
fc8f9a322d | |
|
|
bd5264e28e | |
|
|
d4408ab9cd | |
|
|
1650092890 | |
|
|
7bc6ebc169 | |
|
|
1e96c6cd02 | |
|
|
2f27912aea | |
|
|
5bb8d711ce | |
|
|
977be74768 | |
|
|
0f4de3c4f4 | |
|
|
39c0d17b87 | |
|
|
4d94fcba9a | |
|
|
0884005212 | |
|
|
384e8b9b74 |
|
|
@ -13,6 +13,6 @@
|
||||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.21/ui-bundle.zip"
|
"ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,10 +230,18 @@ class JavaConventions {
|
||||||
CoreJavadocOptions options = (CoreJavadocOptions) javadoc.getOptions();
|
CoreJavadocOptions options = (CoreJavadocOptions) javadoc.getOptions();
|
||||||
options.source("17");
|
options.source("17");
|
||||||
options.encoding("UTF-8");
|
options.encoding("UTF-8");
|
||||||
options.addStringOption("Xdoclint:none", "-quiet");
|
addValuelessOption(options, "Xdoclint:none");
|
||||||
|
addValuelessOption(options, "quiet");
|
||||||
|
if (!javadoc.getName().contains("aggregated")) {
|
||||||
|
addValuelessOption(options, "-no-fonts");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addValuelessOption(CoreJavadocOptions options, String option) {
|
||||||
|
options.addMultilineMultiValueOption(option).setValue(List.of(Collections.emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
private void configureJavaConventions(Project project) {
|
private void configureJavaConventions(Project project) {
|
||||||
if (!project.hasProperty("toolchainVersion")) {
|
if (!project.hasProperty("toolchainVersion")) {
|
||||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@
|
||||||
|
|
||||||
package org.springframework.boot.build.architecture;
|
package org.springframework.boot.build.architecture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.gradle.api.Plugin;
|
import org.gradle.api.Plugin;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.Task;
|
|
||||||
import org.gradle.api.plugins.JavaPlugin;
|
import org.gradle.api.plugins.JavaPlugin;
|
||||||
import org.gradle.api.plugins.JavaPluginExtension;
|
import org.gradle.api.plugins.JavaPluginExtension;
|
||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
import org.gradle.api.tasks.TaskProvider;
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile;
|
||||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool;
|
||||||
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
@ -46,28 +46,45 @@ public class ArchitecturePlugin implements Plugin<Project> {
|
||||||
|
|
||||||
private void registerTasks(Project project, ArchitectureCheckExtension extension) {
|
private void registerTasks(Project project, ArchitectureCheckExtension extension) {
|
||||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||||
List<TaskProvider<ArchitectureCheck>> packageTangleChecks = new ArrayList<>();
|
|
||||||
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
||||||
TaskProvider<ArchitectureCheck> checkPackageTangles = project.getTasks()
|
registerArchitectureCheck(sourceSet, "java", project).configure((task) -> {
|
||||||
.register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class,
|
task.setClasses(project.files(project.getTasks()
|
||||||
(task) -> {
|
.named(sourceSet.getCompileTaskName("java"), JavaCompile.class)
|
||||||
task.getSourceSet().set(sourceSet.getName());
|
.flatMap((compile) -> compile.getDestinationDirectory())));
|
||||||
task.getCompileClasspath().from(sourceSet.getCompileClasspath());
|
|
||||||
task.setClasses(sourceSet.getOutput().getClassesDirs());
|
|
||||||
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
|
|
||||||
task.dependsOn(sourceSet.getProcessResourcesTaskName());
|
|
||||||
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
|
|
||||||
+ " source set.");
|
|
||||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
|
||||||
task.getNullMarkedEnabled().set(extension.getNullMarked().getEnabled());
|
task.getNullMarkedEnabled().set(extension.getNullMarked().getEnabled());
|
||||||
task.getNullMarkedIgnoredPackages().set(extension.getNullMarked().getIgnoredPackages());
|
task.getNullMarkedIgnoredPackages().set(extension.getNullMarked().getIgnoredPackages());
|
||||||
});
|
});
|
||||||
packageTangleChecks.add(checkPackageTangles);
|
project.getPlugins()
|
||||||
}
|
.withId("org.jetbrains.kotlin.jvm",
|
||||||
if (!packageTangleChecks.isEmpty()) {
|
(kotlinPlugin) -> registerArchitectureCheck(sourceSet, "kotlin", project).configure((task) -> {
|
||||||
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
|
task.setClasses(project.files(project.getTasks()
|
||||||
checkTask.configure((check) -> check.dependsOn(packageTangleChecks));
|
.named(sourceSet.getCompileTaskName("kotlin"), KotlinCompileTool.class)
|
||||||
|
.flatMap((compile) -> compile.getDestinationDirectory())));
|
||||||
|
task.getNullMarkedEnabled().set(false);
|
||||||
|
task.getNullMarkedIgnoredPackages().set(Collections.emptySet());
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TaskProvider<ArchitectureCheck> registerArchitectureCheck(SourceSet sourceSet, String language,
|
||||||
|
Project project) {
|
||||||
|
TaskProvider<ArchitectureCheck> checkArchitecture = project.getTasks()
|
||||||
|
.register(
|
||||||
|
"checkArchitecture"
|
||||||
|
+ StringUtils.capitalize(sourceSet.getName() + StringUtils.capitalize(language)),
|
||||||
|
ArchitectureCheck.class, (task) -> {
|
||||||
|
task.getSourceSet().set(sourceSet.getName());
|
||||||
|
task.getCompileClasspath().from(sourceSet.getCompileClasspath());
|
||||||
|
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
|
||||||
|
task.dependsOn(sourceSet.getProcessResourcesTaskName());
|
||||||
|
task.setDescription("Checks the architecture of the " + language + " classes of the "
|
||||||
|
+ sourceSet.getName() + " source set.");
|
||||||
|
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||||
|
});
|
||||||
|
project.getTasks()
|
||||||
|
.named(LifecycleBasePlugin.CHECK_TASK_NAME)
|
||||||
|
.configure((check) -> check.dependsOn(checkArchitecture));
|
||||||
|
return checkArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -421,6 +421,8 @@ final class ArchitectureRules {
|
||||||
return ArchRuleDefinition.members()
|
return ArchRuleDefinition.members()
|
||||||
.that()
|
.that()
|
||||||
.areDeclaredInClassesThat(areRegularAutoConfiguration())
|
.areDeclaredInClassesThat(areRegularAutoConfiguration())
|
||||||
|
.and()
|
||||||
|
.areDeclaredInClassesThat(areNotKotlinClasses())
|
||||||
.and(areNotDefaultConstructors())
|
.and(areNotDefaultConstructors())
|
||||||
.and(areNotConstants())
|
.and(areNotConstants())
|
||||||
.and(dontOverridePublicMethods())
|
.and(dontOverridePublicMethods())
|
||||||
|
|
@ -440,13 +442,18 @@ final class ArchitectureRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
static DescribedPredicate<JavaClass> areRegularAutoConfiguration() {
|
static DescribedPredicate<JavaClass> areRegularAutoConfiguration() {
|
||||||
return DescribedPredicate.describe("Regular @AutoConfiguration",
|
return DescribedPredicate.describe("are regular @AutoConfiguration",
|
||||||
(javaClass) -> javaClass.isAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
|
(javaClass) -> javaClass.isAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
|
||||||
&& !javaClass.getName().contains("TestAutoConfiguration") && !javaClass.isAnnotation());
|
&& !javaClass.getName().contains("TestAutoConfiguration") && !javaClass.isAnnotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DescribedPredicate<JavaClass> areNotKotlinClasses() {
|
||||||
|
return DescribedPredicate.describe("are not Kotlin classes",
|
||||||
|
(javaClass) -> !javaClass.isAnnotatedWith("kotlin.Metadata"));
|
||||||
|
}
|
||||||
|
|
||||||
static DescribedPredicate<JavaClass> areTestAutoConfiguration() {
|
static DescribedPredicate<JavaClass> areTestAutoConfiguration() {
|
||||||
return DescribedPredicate.describe("Test @AutoConfiguration",
|
return DescribedPredicate.describe("are test @AutoConfiguration",
|
||||||
(javaClass) -> javaClass.isAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
|
(javaClass) -> javaClass.isAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
|
||||||
&& javaClass.getName().contains("TestAutoConfiguration") && !javaClass.isAnnotation());
|
&& javaClass.getName().contains("TestAutoConfiguration") && !javaClass.isAnnotation());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,16 @@ import org.springframework.util.ReflectionUtils;
|
||||||
/**
|
/**
|
||||||
* Finds all configurations from auto-configurations (either nested configurations or
|
* Finds all configurations from auto-configurations (either nested configurations or
|
||||||
* imported ones) and checks that these classes don't contain public members.
|
* imported ones) and checks that these classes don't contain public members.
|
||||||
|
* <p>
|
||||||
|
* Kotlin classes are ignored as Kotlin does not have package-private visibility and
|
||||||
|
* {@code internal} isn't a good substitute.
|
||||||
*
|
*
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
*/
|
*/
|
||||||
class AutoConfigurationChecker {
|
class AutoConfigurationChecker {
|
||||||
|
|
||||||
private final DescribedPredicate<JavaClass> isAutoConfiguration = ArchitectureRules.areRegularAutoConfiguration();
|
private final DescribedPredicate<JavaClass> isAutoConfiguration = ArchitectureRules.areRegularAutoConfiguration()
|
||||||
|
.and(ArchitectureRules.areNotKotlinClasses());
|
||||||
|
|
||||||
EvaluationResult check(JavaClasses javaClasses) {
|
EvaluationResult check(JavaClasses javaClasses) {
|
||||||
AutoConfigurations autoConfigurations = new AutoConfigurations();
|
AutoConfigurations autoConfigurations = new AutoConfigurations();
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,7 @@ class ArchitectureCheckTests {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "checkArchitecture" + StringUtils.capitalize(this.sourceSetName);
|
return "checkArchitecture" + StringUtils.capitalize(this.sourceSetName) + "Java";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,41 +207,20 @@ This file will not be packaged in your uber jar or your container.
|
||||||
[[howto.data-initialization.migration-tool.liquibase-tests]]
|
[[howto.data-initialization.migration-tool.liquibase-tests]]
|
||||||
=== Use Liquibase for Test-only Migrations
|
=== Use Liquibase for Test-only Migrations
|
||||||
|
|
||||||
If you want to create Liquibase migrations which populate your test database, you have to create a test changelog which also includes the production changelog.
|
If you want to create Liquibase migrations which populate your test database, you can leverage https://docs.liquibase.com/reference-guide/changelog-attributes/what-are-contexts[Liquibase contexts].
|
||||||
|
See also the related https://www.liquibase.com/blog/contexts-vs-labels[blog post].
|
||||||
|
|
||||||
First, you need to configure Liquibase to use a different changelog when running the tests.
|
In practical terms, this translates into adding a `context:@test` attribute to changesets containing test data, for example:
|
||||||
One way to do this is to create a Spring Boot `test` profile and put the Liquibase properties in there.
|
|
||||||
For that, create a file named `src/test/resources/application-test.properties` and put the following property in there:
|
|
||||||
|
|
||||||
[configprops,yaml]
|
[source,sql]
|
||||||
----
|
----
|
||||||
spring:
|
--liquibase formatted sql
|
||||||
liquibase:
|
|
||||||
change-log: "classpath:/db/changelog/db.changelog-test.yaml"
|
--changeset alice:1 context:@test
|
||||||
|
insert into project (id, name) values (1, 'Spring Boot');
|
||||||
----
|
----
|
||||||
|
|
||||||
This configures Liquibase to use a different changelog when running in the `test` profile.
|
And using `spring.liquibase.contexts=test` in environments where you would like changesets containing test data to be applied.
|
||||||
|
|
||||||
Now create the changelog file at `src/test/resources/db/changelog/db.changelog-test.yaml`:
|
|
||||||
|
|
||||||
[source,yaml]
|
|
||||||
----
|
|
||||||
databaseChangeLog:
|
|
||||||
- include:
|
|
||||||
file: classpath:/db/changelog/db.changelog-master.yaml
|
|
||||||
- changeSet:
|
|
||||||
runOrder: "last"
|
|
||||||
id: "test"
|
|
||||||
changes:
|
|
||||||
# Insert your changes here
|
|
||||||
----
|
|
||||||
|
|
||||||
This changelog will be used when the tests are run and it will not be packaged in your uber jar or your container.
|
|
||||||
It includes the production changelog and then declares a new changeset, whose `runOrder: last` setting specifies that it runs after all the production changesets have been run.
|
|
||||||
You can now use for example the https://docs.liquibase.com/change-types/insert.html[insert changeset] to insert data or the https://docs.liquibase.com/change-types/sql.html[sql changeset] to execute SQL directly.
|
|
||||||
|
|
||||||
The last thing to do is to configure Spring Boot to activate the `test` profile when running tests.
|
|
||||||
To do this, you can add the `@ActiveProfiles("test")` annotation to your javadoc:org.springframework.boot.test.context.SpringBootTest[format=annotation] annotated test classes.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,7 @@ spring:
|
||||||
Caches can be created on startup by setting the configprop:spring.cache.cache-names[] property.
|
Caches can be created on startup by setting the configprop:spring.cache.cache-names[] property.
|
||||||
If a custom javadoc:org.infinispan.configuration.cache.ConfigurationBuilder[] bean is defined, it is used to customize the caches.
|
If a custom javadoc:org.infinispan.configuration.cache.ConfigurationBuilder[] bean is defined, it is used to customize the caches.
|
||||||
|
|
||||||
To be compatible with Spring Boot's Jakarta EE 9 baseline, Infinispan's `-jakarta` modules must be used.
|
For more details, see https://infinispan.org/docs/stable/titles/spring/spring.html[the documentation].
|
||||||
For every module with a `-jakarta` variant, the variant must be used in place of the standard module.
|
|
||||||
For example, `infinispan-core-jakarta` and `infinispan-commons-jakarta` must be used in place of `infinispan-core` and `infinispan-commons` respectively.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[io.caching.provider.couchbase]]
|
[[io.caching.provider.couchbase]]
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@ public final class MyAutoConfiguration {
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnClass(SomeService.class)
|
@ConditionalOnClass(SomeService.class)
|
||||||
public static class SomeServiceConfiguration {
|
static class SomeServiceConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public SomeService someService() {
|
SomeService someService() {
|
||||||
return new SomeService();
|
return new SomeService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
package org.springframework.boot.docs.features.developingautoconfiguration.conditionannotations.beanconditions
|
package org.springframework.boot.docs.features.developingautoconfiguration.conditionannotations.beanconditions
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@AutoConfiguration
|
||||||
class MyAutoConfiguration {
|
class MyAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@
|
||||||
|
|
||||||
package org.springframework.boot.docs.features.developingautoconfiguration.conditionannotations.classconditions
|
package org.springframework.boot.docs.features.developingautoconfiguration.conditionannotations.classconditions
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@AutoConfiguration
|
||||||
// Some conditions ...
|
// Some conditions ...
|
||||||
class MyAutoConfiguration {
|
class MyAutoConfiguration {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version=4.0.0-SNAPSHOT
|
version=4.0.1-SNAPSHOT
|
||||||
latestVersion=true
|
latestVersion=true
|
||||||
spring.build-type=oss
|
spring.build-type=oss
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.configureEach {
|
tasks.configureEach {
|
||||||
if ("checkArchitectureMain".equals(it.name)) {
|
if ("checkArchitectureMainJava".equals(it.name)) {
|
||||||
prohibitObjectsRequireNonNull = false
|
prohibitObjectsRequireNonNull = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,24 @@
|
||||||
"description": "Whether to create an AmqpAdmin bean.",
|
"description": "Whether to create an AmqpAdmin bean.",
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.rabbitmq.listener.direct.retry.max-attempts",
|
||||||
|
"type": "java.lang.Long",
|
||||||
|
"deprecation": {
|
||||||
|
"level": "error",
|
||||||
|
"replacement": "spring.rabbitmq.listener.direct.retry.max-retries",
|
||||||
|
"since": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.rabbitmq.listener.simple.retry.max-attempts",
|
||||||
|
"type": "java.lang.Long",
|
||||||
|
"deprecation": {
|
||||||
|
"level": "error",
|
||||||
|
"replacement": "spring.rabbitmq.listener.simple.retry.max-retries",
|
||||||
|
"since": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spring.rabbitmq.listener.simple.transaction-size",
|
"name": "spring.rabbitmq.listener.simple.transaction-size",
|
||||||
"type": "java.lang.Integer",
|
"type": "java.lang.Integer",
|
||||||
|
|
@ -37,6 +55,15 @@
|
||||||
"level": "error",
|
"level": "error",
|
||||||
"since": "2.2.0"
|
"since": "2.2.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.rabbitmq.template.retry.max-attempts",
|
||||||
|
"type": "java.lang.Long",
|
||||||
|
"deprecation": {
|
||||||
|
"level": "error",
|
||||||
|
"replacement": "spring.rabbitmq.template.retry.max-retries",
|
||||||
|
"since": "4.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue