Skip to content

Commit 334092d

Browse files
authored
Merge pull request #61 from prime-framework/wied03/alternate_messages_path
Allow adding a fallback message action
2 parents 24c1b74 + d790446 commit 334092d

23 files changed

+379
-88
lines changed

build.savant

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ logbackVersion = "1.4.14"
2929
slf4jVersion = "2.0.13"
3030
testngVersion = "7.8.0"
3131

32-
project(group: "org.primeframework", name: "prime-mvc", version: "4.25.1", licenses: ["ApacheV2_0"]) {
32+
project(group: "org.primeframework", name: "prime-mvc", version: "4.26.0", licenses: ["ApacheV2_0"]) {
3333
workflow {
3434
fetch {
3535
// Dependency resolution order:

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>org.primeframework</groupId>
77
<artifactId>prime-mvc</artifactId>
8-
<version>4.25.1</version>
8+
<version>4.26.0</version>
99
<packaging>jar</packaging>
1010

1111
<name>FusionAuth App</name>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2024, Inversoft Inc., All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package org.primeframework.mvc.action.annotation;
17+
18+
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.annotation.Target;
22+
23+
/**
24+
* Customize message resource behavior for an action class
25+
*
26+
* @author Brady Wied
27+
*/
28+
@Retention(RetentionPolicy.RUNTIME)
29+
@Target(ElementType.TYPE)
30+
public @interface AlternateMessageResources {
31+
/**
32+
* @return By default, will look for messages, via the {@link org.primeframework.mvc.message.l10n.MessageProvider}
33+
* interface, in an implementation specific search path (see {@link org.primeframework.mvc.message.l10n.ResourceBundleMessageProvider} for an
34+
* example).
35+
* If you want an additional action's messages in the search path, use this attribute.
36+
*/
37+
Class<?>[] actions();
38+
}

src/main/java/org/primeframework/mvc/action/config/ActionConfiguration.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001-2023, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2001-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,6 +50,8 @@ public class ActionConfiguration {
5050

5151
public final boolean allowUnknownParameters;
5252

53+
public final List<String> alternateMessageURIs;
54+
5355
public final Action annotation;
5456

5557
public final Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<>();
@@ -122,15 +124,16 @@ public ActionConfiguration(Class<?> actionClass,
122124
String uri,
123125
List<Method> preValidationMethods,
124126
Field unknownParametersField,
125-
Set<String> validContentTypes) {
126-
127+
Set<String> validContentTypes,
128+
List<String> alternateMessageURIs) {
127129
Objects.requireNonNull(actionClass);
128130

129131
this.actionClass = actionClass;
130132
this.allowUnknownParameters = allowUnknownParameters;
131133
this.constraintValidationMethods = constraintValidationMethods;
132134
this.formPrepareMethods = formPrepareMethods;
133135
this.authorizationMethods = authorizationMethods;
136+
this.alternateMessageURIs = alternateMessageURIs;
134137
this.jwtAuthorizationMethods = jwtAuthorizationMethods;
135138
this.preValidationMethods = preValidationMethods;
136139
this.postValidationMethods = postValidationMethods;

src/main/java/org/primeframework/mvc/action/config/DefaultActionConfigurationBuilder.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2023, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2012-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import java.util.HashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Optional;
3031
import java.util.Set;
3132
import java.util.function.BiFunction;
3233
import java.util.function.Function;
@@ -44,6 +45,7 @@
4445
import org.primeframework.mvc.action.ValidationMethodConfiguration;
4546
import org.primeframework.mvc.action.annotation.Action;
4647
import org.primeframework.mvc.action.annotation.AllowUnknownParameters;
48+
import org.primeframework.mvc.action.annotation.AlternateMessageResources;
4749
import org.primeframework.mvc.action.result.annotation.ResultAnnotation;
4850
import org.primeframework.mvc.action.result.annotation.ResultContainerAnnotation;
4951
import org.primeframework.mvc.content.ValidContentTypes;
@@ -136,7 +138,13 @@ public ActionConfiguration build(Class<?> actionClass) {
136138
Field unknownParametersField = findUnknownParametersField(actionClass);
137139
Set<String> validContentTypes = findAllowedContentTypes(actionClass);
138140

139-
return new ActionConfiguration(actionClass, allowKnownParameters, constraintValidationMethods, executeMethods, validationMethods, formPrepareMethods, authorizationMethods, jwtAuthorizationMethods, postValidationMethods, preParameterMethods, postParameterMethods, resultAnnotations, preParameterMembers, preRenderMethodsMap, fileUploadMembers, memberNames, securitySchemes, scopeFields, additionalConfiguration, uri, preValidationMethods, unknownParametersField, validContentTypes);
141+
List<String> alternateMessageURIs = Optional.ofNullable(actionClass.getAnnotation(AlternateMessageResources.class))
142+
.map(alternate -> Arrays.stream(alternate.actions())
143+
.map(uriBuilder::build)
144+
.toList())
145+
.orElse(List.of());
146+
147+
return new ActionConfiguration(actionClass, allowKnownParameters, constraintValidationMethods, executeMethods, validationMethods, formPrepareMethods, authorizationMethods, jwtAuthorizationMethods, postValidationMethods, preParameterMethods, postParameterMethods, resultAnnotations, preParameterMembers, preRenderMethodsMap, fileUploadMembers, memberNames, securitySchemes, scopeFields, additionalConfiguration, uri, preValidationMethods, unknownParametersField, validContentTypes, alternateMessageURIs);
140148
}
141149

142150
/**

src/main/java/org/primeframework/mvc/message/l10n/ResourceBundleMessageProvider.java

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001-2019, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2001-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,13 +18,16 @@
1818
import java.util.Formatter;
1919
import java.util.LinkedList;
2020
import java.util.MissingResourceException;
21+
import java.util.Objects;
2122
import java.util.Queue;
2223
import java.util.ResourceBundle;
2324
import java.util.ResourceBundle.Control;
2425

2526
import com.google.inject.Inject;
2627
import org.primeframework.mvc.action.ActionInvocation;
2728
import org.primeframework.mvc.action.ActionInvocationStore;
29+
import org.primeframework.mvc.action.annotation.AlternateMessageResources;
30+
import org.primeframework.mvc.action.config.ActionConfiguration;
2831
import org.primeframework.mvc.locale.LocaleProvider;
2932
import org.slf4j.Logger;
3033
import org.slf4j.LoggerFactory;
@@ -120,24 +123,25 @@ protected Queue<String> determineBundles(String bundle) {
120123

121124
/**
122125
* Finds the message in a resource bundle using the search method described in the class comment.
126+
* If the action was annotated with a {@link AlternateMessageResources} annotation
127+
* and the message was not found with the request's action, that action will be searched as well.
123128
*
124129
* @param actionInvocation The action invocation.
125130
* @param key The key of the message.
126131
* @return The message or null if it doesn't exist.
127132
*/
128133
protected String findMessage(ActionInvocation actionInvocation, String key) {
129-
String uri = actionInvocation.actionURI;
130-
Queue<String> names = determineBundles(uri);
131-
for (String name : names) {
132-
try {
133-
ResourceBundle rb = ResourceBundle.getBundle(name, localeProvider.get(), control);
134-
return rb.getString(key);
135-
} catch (MissingResourceException ignore) {
136-
// Ignore and check the next bundle
137-
}
134+
String message = findMessage(actionInvocation.actionURI, key);
135+
if (message != null) {
136+
return message;
138137
}
139138

140-
return null;
139+
ActionConfiguration config = actionInvocation.configuration;
140+
return config.alternateMessageURIs.stream()
141+
.map(uri -> findMessage(uri, key))
142+
.filter(Objects::nonNull)
143+
.findFirst()
144+
.orElse(null);
141145
}
142146

143147
/**
@@ -157,4 +161,25 @@ private String findDefaultMessage(ActionInvocation actionInvocation, String key)
157161

158162
return null;
159163
}
160-
}
164+
165+
/**
166+
* Finds the message in a resource bundle using the search method described in the class comment.
167+
*
168+
* @param uri The action URI to use for searching.
169+
* @param key The key of the message.
170+
* @return The message or null if it doesn't exist.
171+
*/
172+
private String findMessage(String uri, String key) {
173+
Queue<String> names = determineBundles(uri);
174+
for (String name : names) {
175+
try {
176+
ResourceBundle rb = ResourceBundle.getBundle(name, localeProvider.get(), control);
177+
return rb.getString(key);
178+
} catch (MissingResourceException ignore) {
179+
// Ignore and check the next bundle
180+
}
181+
}
182+
183+
return null;
184+
}
185+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2024, Inversoft Inc., All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package org.example.action;
17+
18+
import com.google.inject.Inject;
19+
import org.example.action.nested.NestedMessageAction;
20+
import org.primeframework.mvc.action.annotation.Action;
21+
import org.primeframework.mvc.action.annotation.AlternateMessageResources;
22+
import org.primeframework.mvc.action.result.annotation.Status;
23+
import org.primeframework.mvc.message.MessageStore;
24+
import org.primeframework.mvc.message.MessageType;
25+
import org.primeframework.mvc.message.SimpleMessage;
26+
import org.primeframework.mvc.message.l10n.MessageProvider;
27+
28+
@Action
29+
@Status
30+
@AlternateMessageResources(actions = NestedMessageAction.class)
31+
public class AlternateMessageResourcesAnnotatedAction {
32+
private final MessageProvider messageProvider;
33+
34+
private final MessageStore messageStore;
35+
36+
public String messageKey;
37+
38+
@Inject
39+
public AlternateMessageResourcesAnnotatedAction(MessageProvider messageProvider, MessageStore messageStore) {
40+
this.messageProvider = messageProvider;
41+
this.messageStore = messageStore;
42+
}
43+
44+
public String get() {
45+
try {
46+
messageStore.add(new SimpleMessage(MessageType.INFO, messageKey, messageProvider.getMessage(messageKey)));
47+
} catch (Exception e) {
48+
messageStore.add(new SimpleMessage(MessageType.ERROR, messageKey, "not found"));
49+
}
50+
return "success";
51+
}
52+
}

src/test/java/org/example/action/CallbackAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public String get() {
5353
messageStore.add(new SimpleMessage(MessageType.INFO, "[INFO]", messageProvider.getMessage("[INFO]")));
5454
messageStore.add(new SimpleMessage(MessageType.WARNING, "[WARNING]", messageProvider.getMessage("[WARNING]")));
5555

56-
// Call another endpoint that adds messages to the message store
56+
// Call another endpoint that adds messages to the message store - we want to ensure those messages are scoped
57+
// with the request we make here using HttpClient, NOT with the request we are handling
5758
URI url = URI.create(request.getBaseURL() + "/message-store");
5859
var request = HttpRequest.newBuilder(url)
5960
.GET()

src/test/java/org/example/action/MessageStoreAction.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2022-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,9 +40,9 @@ public MessageStoreAction(MessageProvider messageProvider, MessageStore messageS
4040
}
4141

4242
public String get() {
43-
messageStore.add(new SimpleMessage(MessageType.ERROR, "[ERROR]", messageProvider.getMessage("[ERROR]")));
44-
messageStore.add(new SimpleMessage(MessageType.INFO, "[INFO]", messageProvider.getMessage("[INFO]")));
45-
messageStore.add(new SimpleMessage(MessageType.WARNING, "[WARNING]", messageProvider.getMessage("[WARNING]")));
43+
messageStore.add(new SimpleMessage(MessageType.ERROR, "[STORE_ERROR]", messageProvider.getMessage("[STORE_ERROR]")));
44+
messageStore.add(new SimpleMessage(MessageType.INFO, "[STORE_INFO]", messageProvider.getMessage("[STORE_INFO]")));
45+
messageStore.add(new SimpleMessage(MessageType.WARNING, "[STORE_WARNING]", messageProvider.getMessage("[STORE_WARNING]")));
4646
return "success";
4747
}
4848
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2024, Inversoft Inc., All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package org.example.action.nested;
17+
18+
import org.primeframework.mvc.action.annotation.Action;
19+
20+
@Action
21+
public class NestedMessageAction {
22+
public String execute() {
23+
return "success";
24+
}
25+
}

0 commit comments

Comments
 (0)