This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-3737-custom-separator in repository https://gitbox.apache.org/repos/asf/struts.git
commit c41f05fe68c4f89ba5042747a43bb74e108ce550 Author: Lukasz Lenart <lukaszlen...@apache.org> AuthorDate: Wed Oct 19 13:57:59 2022 +0200 WW-3737 Allows to define a custom separator used to split patterns --- .../com/opensymphony/xwork2/XWorkTestCase.java | 31 ++-- .../opensymphony/xwork2/inject/ContainerImpl.java | 175 +++++++++------------ .../java/org/apache/struts2/StrutsConstants.java | 3 + .../apache/struts2/dispatcher/InitOperations.java | 37 +++-- .../struts2/dispatcher/InitOperationsTest.java | 86 ++++++++++ 5 files changed, 202 insertions(+), 130 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java index 330e96412..ec7a8d755 100644 --- a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java +++ b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java @@ -34,22 +34,22 @@ import java.util.Locale; import java.util.Map; /** - * Base JUnit TestCase to extend for XWork specific JUnit tests. Uses + * Base JUnit TestCase to extend for XWork specific JUnit tests. Uses * the generic test setup for logic. * * @author plightbo */ public abstract class XWorkTestCase extends TestCase { - + protected ConfigurationManager configurationManager; protected Configuration configuration; protected Container container; protected ActionProxyFactory actionProxyFactory; - + public XWorkTestCase() { super(); } - + @Override protected void setUp() throws Exception { configurationManager = XWorkTestCaseHelper.setUp(); @@ -57,7 +57,7 @@ public abstract class XWorkTestCase extends TestCase { container = configuration.getContainer(); actionProxyFactory = container.getInstance(ActionProxyFactory.class); } - + @Override protected void tearDown() throws Exception { XWorkTestCaseHelper.tearDown(configurationManager); @@ -66,34 +66,33 @@ public abstract class XWorkTestCase extends TestCase { container = null; actionProxyFactory = null; } - + protected void loadConfigurationProviders(ConfigurationProvider... providers) { configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers); configuration = configurationManager.getConfiguration(); container = configuration.getContainer(); actionProxyFactory = container.getInstance(ActionProxyFactory.class); } - - protected void loadButAdd(final Class<?> type, final Object impl) { + + protected <T> void loadButAdd(final Class<T> type, final T impl) { loadButAdd(type, Container.DEFAULT_NAME, impl); } - - protected void loadButAdd(final Class<?> type, final String name, final Object impl) { + + protected <T> void loadButAdd(final Class<T> type, final String name, final T impl) { loadConfigurationProviders(new StubConfigurationProvider() { @Override - public void register(ContainerBuilder builder, - LocatableProperties props) throws ConfigurationException { + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { if (impl instanceof String || ClassUtils.isPrimitiveOrWrapper(impl.getClass())) { props.setProperty(name, "" + impl); } else { - builder.factory(type, name, new Factory() { - public Object create(Context context) throws Exception { + builder.factory(type, name, new Factory<T>() { + public T create(Context context) throws Exception { return impl; } @Override - public Class type() { - return impl.getClass(); + public Class<T> type() { + return (Class<T>) impl.getClass(); } }, Scope.SINGLETON); } diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java index ad3d43c2f..455f8a449 100644 --- a/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java +++ b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java @@ -19,10 +19,26 @@ import com.opensymphony.xwork2.inject.util.ReferenceCache; import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.*; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ReflectPermission; import java.security.AccessControlException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; /** * Default {@link Container} implementation. @@ -39,11 +55,7 @@ class ContainerImpl implements Container { this.factories = factories; final Map<Class<?>, Set<String>> map = new HashMap<>(); for (Key<?> key : factories.keySet()) { - Set<String> names = map.get(key.getType()); - if (names == null) { - names = new HashSet<>(); - map.put(key.getType(), names); - } + Set<String> names = map.computeIfAbsent(key.getType(), k -> new HashSet<>()); names.add(key.getName()); } @@ -63,20 +75,20 @@ class ContainerImpl implements Container { * Field and method injectors. */ final Map<Class<?>, List<Injector>> injectors = - new ReferenceCache<Class<?>, List<Injector>>() { - @Override - protected List<Injector> create(Class<?> key) { - List<Injector> injectors = new ArrayList<>(); - addInjectors(key, injectors); - return injectors; - } - }; + new ReferenceCache<Class<?>, List<Injector>>() { + @Override + protected List<Injector> create(Class<?> key) { + List<Injector> injectors = new ArrayList<>(); + addInjectors(key, injectors); + return injectors; + } + }; /** * Recursively adds injectors for fields and methods from the given class to the given list. Injects parent classes * before sub classes. */ - void addInjectors(Class clazz, List<Injector> injectors) { + void addInjectors(Class<?> clazz, List<Injector> injectors) { if (clazz == Object.class) { return; } @@ -97,38 +109,24 @@ class ContainerImpl implements Container { addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors); } - callInContext(new ContextualCallable<Void>() { - public Void call(InternalContext context) { - for (Injector injector : injectors) { - injector.inject(context, null); - } - return null; + callInContext((ContextualCallable<Void>) context -> { + for (Injector injector : injectors) { + injector.inject(context, null); } + return null; }); } void addInjectorsForMethods(Method[] methods, boolean statics, List<Injector> injectors) { - addInjectorsForMembers(Arrays.asList(methods), statics, injectors, - new InjectorFactory<Method>() { - public Injector create(ContainerImpl container, Method method, - String name) throws MissingDependencyException { - return new MethodInjector(container, method, name); - } - }); + addInjectorsForMembers(Arrays.asList(methods), statics, injectors, MethodInjector::new); } void addInjectorsForFields(Field[] fields, boolean statics, List<Injector> injectors) { - addInjectorsForMembers(Arrays.asList(fields), statics, injectors, - new InjectorFactory<Field>() { - public Injector create(ContainerImpl container, Field field, - String name) throws MissingDependencyException { - return new FieldInjector(container, field, name); - } - }); + addInjectorsForMembers(Arrays.asList(fields), statics, injectors, FieldInjector::new); } <M extends Member & AnnotatedElement> void addInjectorsForMembers( - List<M> members, boolean statics, List<Injector> injectors, InjectorFactory<M> injectorFactory) { + List<M> members, boolean statics, List<Injector> injectors, InjectorFactory<M> injectorFactory) { for (M member : members) { if (isStatic(member) == statics) { Inject inject = member.getAnnotation(Inject.class); @@ -148,12 +146,12 @@ class ContainerImpl implements Container { interface InjectorFactory<M extends Member & AnnotatedElement> { Injector create(ContainerImpl container, M member, String name) - throws MissingDependencyException; + throws MissingDependencyException; } /** * Determines if a given {@link Member} is static or not. - * + * * @param member checked for the static modifier. * @return true if member is static, false otherwise. */ @@ -163,13 +161,13 @@ class ContainerImpl implements Container { /** * Determines if a given {@link Member} is considered to be public for reflection usage or not. - * + * * @param member checked to see if it is public for reflection usage. * @return true if member is public for reflection usage, false otherwise. */ private static boolean isPublicForReflection(Member member) { return Modifier.isPublic(member.getModifiers()) && - Modifier.isPublic(member.getDeclaringClass().getModifiers()); + Modifier.isPublic(member.getDeclaringClass().getModifiers()); } static class FieldInjector implements Injector { @@ -179,7 +177,7 @@ class ContainerImpl implements Container { final ExternalContext<?> externalContext; public FieldInjector(ContainerImpl container, Field field, String name) - throws MissingDependencyException { + throws MissingDependencyException { this.field = field; if (!isPublicForReflection(field) && !field.isAccessible()) { SecurityManager sm = System.getSecurityManager(); @@ -190,7 +188,7 @@ class ContainerImpl implements Container { field.setAccessible(true); } catch (AccessControlException e) { throw new DependencyException("Security manager in use, could not access field: " - + field.getDeclaringClass().getName() + "(" + field.getName() + ")", e); + + field.getDeclaringClass().getName() + "(" + field.getName() + ")", e); } } @@ -225,8 +223,12 @@ class ContainerImpl implements Container { * @param parameterTypes parameter types * @return injections */ - <M extends AccessibleObject & Member> ParameterInjector<?>[] - getParametersInjectors(M member, Annotation[][] annotations, Class[] parameterTypes, String defaultName) throws MissingDependencyException { + <M extends AccessibleObject & Member> ParameterInjector<?>[] getParametersInjectors( + M member, + Annotation[][] annotations, + Class<?>[] parameterTypes, + String defaultName + ) throws MissingDependencyException { final List<ParameterInjector<?>> parameterInjectors = new ArrayList<>(); final Iterator<Annotation[]> annotationsIterator = Arrays.asList(annotations).iterator(); @@ -247,12 +249,11 @@ class ContainerImpl implements Container { } final ExternalContext<T> externalContext = ExternalContext.newInstance(member, key, this); - return new ParameterInjector<T>(externalContext, factory); + return new ParameterInjector<>(externalContext, factory); } - @SuppressWarnings("unchecked") private ParameterInjector<?>[] toArray(List<ParameterInjector<?>> parameterInjections) { - return parameterInjections.toArray(new ParameterInjector[parameterInjections.size()]); + return parameterInjections.toArray(new ParameterInjector[0]); } /** @@ -261,7 +262,7 @@ class ContainerImpl implements Container { Inject findInject(Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation.annotationType() == Inject.class) { - return Inject.class.cast(annotation); + return (Inject) annotation; } } return null; @@ -283,7 +284,7 @@ class ContainerImpl implements Container { method.setAccessible(true); } catch (AccessControlException e) { throw new DependencyException("Security manager in use, could not access method: " - + name + "(" + method.getName() + ")", e); + + name + "(" + method.getName() + ")", e); } } @@ -292,7 +293,7 @@ class ContainerImpl implements Container { throw new DependencyException(method + " has no parameters to inject."); } parameterInjectors = container.getParametersInjectors( - method, method.getParameterAnnotations(), parameterTypes, name); + method, method.getParameterAnnotations(), parameterTypes, name); } @Override @@ -305,14 +306,12 @@ class ContainerImpl implements Container { } } - Map<Class<?>, ConstructorInjector> constructors = - new ReferenceCache<Class<?>, ConstructorInjector>() { - @Override - @SuppressWarnings("unchecked") - protected ConstructorInjector<?> create(Class<?> implementation) { - return new ConstructorInjector(ContainerImpl.this, implementation); - } - }; + Map<Class<?>, ConstructorInjector<?>> constructors = new ReferenceCache<Class<?>, ConstructorInjector<?>>() { + @Override + protected ConstructorInjector<?> create(Class<?> implementation) { + return new ConstructorInjector<>(ContainerImpl.this, implementation); + } + }; static class ConstructorInjector<T> { @@ -334,7 +333,7 @@ class ContainerImpl implements Container { constructor.setAccessible(true); } catch (AccessControlException e) { throw new DependencyException("Security manager in use, could not access constructor: " - + implementation.getName() + "(" + constructor.getName() + ")", e); + + implementation.getName() + "(" + constructor.getName() + ")", e); } } @@ -359,14 +358,14 @@ class ContainerImpl implements Container { } ParameterInjector<?>[] constructParameterInjector( - Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException { + Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException { return constructor.getParameterTypes().length == 0 - ? null // default constructor. - : container.getParametersInjectors( - constructor, - constructor.getParameterAnnotations(), - constructor.getParameterTypes(), - inject.value() + ? null // default constructor. + : container.getParametersInjectors( + constructor, + constructor.getParameterAnnotations(), + constructor.getParameterTypes(), + inject.value() ); } @@ -378,7 +377,7 @@ class ContainerImpl implements Container { if (constructor.getAnnotation(Inject.class) != null) { if (found != null) { throw new DependencyException("More than one constructor annotated" - + " with @Inject found in " + implementation + "."); + + " with @Inject found in " + implementation + "."); } found = constructor; } @@ -466,7 +465,7 @@ class ContainerImpl implements Container { } } - private static Object[] getParameters(Member member, InternalContext context, ParameterInjector[] parameterInjectors) { + private static Object[] getParameters(Member member, InternalContext context, ParameterInjector<?>[] parameterInjectors) { if (parameterInjectors == null) { return null; } @@ -494,13 +493,12 @@ class ContainerImpl implements Container { } } - @SuppressWarnings("unchecked") <T> T getInstance(Class<T> type, String name, InternalContext context) { final ExternalContext<?> previous = context.getExternalContext(); final Key<T> key = Key.newInstance(type, name); context.setExternalContext(ExternalContext.newInstance(null, key, this)); try { - final InternalFactory o = getFactory(key); + final InternalFactory<? extends T> o = getFactory(key); if (o != null) { return getFactory(key).create(context); } else { @@ -517,39 +515,25 @@ class ContainerImpl implements Container { @Override public void inject(final Object o) { - callInContext(new ContextualCallable<Void>() { - public Void call(InternalContext context) { - inject(o, context); - return null; - } + callInContext((ContextualCallable<Void>) context -> { + inject(o, context); + return null; }); } @Override public <T> T inject(final Class<T> implementation) { - return callInContext(new ContextualCallable<T>() { - public T call(InternalContext context) { - return inject(implementation, context); - } - }); + return callInContext(context -> inject(implementation, context)); } @Override public <T> T getInstance(final Class<T> type, final String name) { - return callInContext(new ContextualCallable<T>() { - public T call(InternalContext context) { - return getInstance(type, name, context); - } - }); + return callInContext(context -> getInstance(type, name, context)); } @Override public <T> T getInstance(final Class<T> type) { - return callInContext(new ContextualCallable<T>() { - public T call(InternalContext context) { - return getInstance(type, context); - } - }); + return callInContext(context -> getInstance(type, context)); } @Override @@ -561,12 +545,7 @@ class ContainerImpl implements Container { return names; } - ThreadLocal<Object[]> localContext = new ThreadLocal<Object[]>() { - @Override - protected Object[] initialValue() { - return new Object[1]; - } - }; + ThreadLocal<Object[]> localContext = ThreadLocal.withInitial(() -> new Object[1]); /** * Looks up thread local context. Creates (and removes) a new context if necessary. @@ -598,7 +577,7 @@ class ContainerImpl implements Container { */ @SuppressWarnings("unchecked") <T> ConstructorInjector<T> getConstructor(Class<T> implementation) { - return constructors.get(implementation); + return (ConstructorInjector<T>) constructors.get(implementation); } final ThreadLocal<Object> localScopeStrategy = new ThreadLocal<>(); diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 18ccfbc93..f37a85078 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -56,6 +56,9 @@ public final class StrutsConstants { /** Comma separated list of patterns (java.util.regex.Pattern) to be excluded from Struts2-processing */ public static final String STRUTS_ACTION_EXCLUDE_PATTERN = "struts.action.excludePattern"; + /** A custom separator used to split list of patterns (java.util.regex.Pattern) to be excluded from Struts2-processing */ + public static final String STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR = "struts.action.excludePattern.separator"; + /** Whether to use the response encoding (JSP page encoding) for s:include tag processing (false - use STRUTS_I18N_ENCODING - by default) */ public static final String STRUTS_TAG_INCLUDETAG_USERESPONSEENCODING = "struts.tag.includetag.useResponseEncoding"; diff --git a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java index 7c6b7626f..819e7cdb9 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java @@ -21,7 +21,12 @@ package org.apache.struts2.dispatcher; import com.opensymphony.xwork2.ActionContext; import org.apache.struts2.StrutsConstants; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.regex.Pattern; /** @@ -36,10 +41,9 @@ public class InitOperations { * Creates and initializes the dispatcher * * @param filterConfig host configuration - * * @return the dispatcher */ - public Dispatcher initDispatcher( HostConfig filterConfig ) { + public Dispatcher initDispatcher(HostConfig filterConfig) { Dispatcher dispatcher = createDispatcher(filterConfig); dispatcher.init(); return dispatcher; @@ -49,10 +53,10 @@ public class InitOperations { * Initializes the static content loader with the filter configuration * * @param filterConfig host configuration - * @param dispatcher the dispatcher + * @param dispatcher the dispatcher * @return the static content loader */ - public StaticContentLoader initStaticContentLoader( HostConfig filterConfig, Dispatcher dispatcher ) { + public StaticContentLoader initStaticContentLoader(HostConfig filterConfig, Dispatcher dispatcher) { StaticContentLoader loader = dispatcher.getContainer().getInstance(StaticContentLoader.class); loader.setHostConfig(filterConfig); return loader; @@ -60,7 +64,6 @@ public class InitOperations { /** * @return The dispatcher on the thread. - * * @throws IllegalStateException If there is no dispatcher available */ public Dispatcher findDispatcherOnThread() { @@ -75,12 +78,11 @@ public class InitOperations { * Create a {@link Dispatcher} * * @param filterConfig host configuration - * * @return The dispatcher on the thread. */ protected Dispatcher createDispatcher(HostConfig filterConfig) { Map<String, String> params = new HashMap<>(); - for ( Iterator<String> parameterNames = filterConfig.getInitParameterNames(); parameterNames.hasNext(); ) { + for (Iterator<String> parameterNames = filterConfig.getInitParameterNames(); parameterNames.hasNext(); ) { String name = parameterNames.next(); String value = filterConfig.getInitParameter(name); params.put(name, value); @@ -96,20 +98,23 @@ public class InitOperations { * Extract a list of patterns to exclude from request filtering * * @param dispatcher The dispatcher to check for exclude pattern configuration - * * @return a List of Patterns for request to exclude if apply, or <tt>null</tt> - * * @see org.apache.struts2.StrutsConstants#STRUTS_ACTION_EXCLUDE_PATTERN */ - public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) { - return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN)); + public List<Pattern> buildExcludedPatternsList(Dispatcher dispatcher) { + String excludePatterns = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN); + String separator = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR); + if (separator == null) { + separator = ","; + } + return buildExcludedPatternsList(excludePatterns, separator); } - - private List<Pattern> buildExcludedPatternsList( String patterns ) { + + private List<Pattern> buildExcludedPatternsList(String patterns, String separator) { if (null != patterns && patterns.trim().length() != 0) { List<Pattern> list = new ArrayList<>(); - String[] tokens = patterns.split(","); - for ( String token : tokens ) { + String[] tokens = patterns.split(separator); + for (String token : tokens) { list.add(Pattern.compile(token.trim())); } return Collections.unmodifiableList(list); diff --git a/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java b/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java new file mode 100644 index 000000000..aa2aaeaa3 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.dispatcher; + +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.config.PropertiesConfigurationProvider; + +import java.util.List; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class InitOperationsTest extends StrutsInternalTestCase { + + public void testExcludePatterns() { + // given + loadConfigurationProviders(new PropertiesConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json"); + } + }); + + Dispatcher mockDispatcher = mock(Dispatcher.class); + when(mockDispatcher.getContainer()).thenReturn(container); + + // when + InitOperations init = new InitOperations(); + List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher); + + // then + assertThat(patterns).extracting(Pattern::toString).containsOnly( + "/ns1/.*\\.json", + "/ns2/.*\\.json" + ); + } + + public void testExcludePatternsUsingCustomSeparator() { + // given + loadConfigurationProviders(new PropertiesConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json"); + props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//"); + } + }); + + Dispatcher mockDispatcher = mock(Dispatcher.class); + when(mockDispatcher.getContainer()).thenReturn(container); + + // when + InitOperations init = new InitOperations(); + + String separator = container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR); + List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher); + + // then + assertThat(separator).isNotBlank().isEqualTo("//"); + assertThat(patterns).extracting(Pattern::toString).containsOnly( + "/ns1/[a-z]{1,10}.json", + "/ns2/[a-z]{1,10}.json" + ); + } +}