├── .gitignore ├── LICENSE.txt ├── README.md ├── glassfish ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── samaxes │ │ │ └── javax │ │ │ └── rs │ │ │ └── validation │ │ │ ├── AcceptLanguageRequestFilter.java │ │ │ ├── ApplicationConfig.java │ │ │ ├── LocaleSpecificMessageInterpolator.java │ │ │ ├── LocaleThreadLocal.java │ │ │ ├── Person.java │ │ │ ├── Persons.java │ │ │ ├── ValidationConfigurationContextResolver.java │ │ │ └── ValidationExceptionMapper.java │ ├── resources │ │ ├── ValidationMessages.properties │ │ └── ValidationMessages_pt.properties │ └── webapp │ │ ├── WEB-INF │ │ └── beans.xml │ │ └── index.jsp │ └── test │ └── java │ └── com │ └── samaxes │ └── javax │ └── rs │ └── validation │ └── PersonsIT.java └── wildfly ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── samaxes │ │ └── javax │ │ └── rs │ │ └── validation │ │ ├── AcceptLanguageRequestFilter.java │ │ ├── ApplicationConfig.java │ │ ├── LocaleSpecificMessageInterpolator.java │ │ ├── LocaleThreadLocal.java │ │ ├── Person.java │ │ ├── Persons.java │ │ ├── ValidationConfigurationContextResolver.java │ │ └── ValidationExceptionMapper.java ├── resources │ ├── ValidationMessages.properties │ └── ValidationMessages_pt.properties └── webapp │ ├── WEB-INF │ └── beans.xml │ └── index.jsp └── test └── java └── com └── samaxes └── javax └── rs └── validation └── PersonsIT.java /.gitignore: -------------------------------------------------------------------------------- 1 | glassfish/target 2 | glassfish/.settings 3 | glassfish/.project 4 | glassfish/.classpath 5 | glassfish/.idea 6 | glassfish/glassfish-jaxrs-validation.iml 7 | wildfly/target 8 | wildfly/.settings 9 | wildfly/.project 10 | wildfly/.classpath 11 | wildfly/.idea 12 | wildfly/wildfly-jaxrs-validation.iml 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Validating JAX-RS resource data with Bean Validation in Java EE 7 2 | 3 | This project goal is to demonstrate that, despite the used technologies being part of the Java EE 7 specification, proprietary dependencies are required to properly internationalize the error messages produced by Bean Validation. 4 | 5 | * The Glassfish code was first used on an article written for [JAX Magazine](http://jaxenter.com/jax-magazine/JAX-Magazine-2013-06) and later posted on [JAXenter](http://jaxenter.com/integrating-bean-validation-with-jax-rs-48461.html). 6 | * The WildFly code was used for a post on my own blog at [Validating JAX-RS resource data with Bean Validation in Java EE 7 and WildFly](http://www.samaxes.com/2014/04/jaxrs-beanvalidation-javaee7-wildfly/). 7 | -------------------------------------------------------------------------------- /glassfish/README.md: -------------------------------------------------------------------------------- 1 | # Integrating JAX-RS with Bean Validation and internationalize error messages in GlassFish 4 2 | 3 | * This code was first used on an article written for [JAX Magazine](http://jaxenter.com/jax-magazine/JAX-Magazine-2013-06) and later posted on [JAXenter](http://jaxenter.com/integrating-bean-validation-with-jax-rs-48461.html). 4 | -------------------------------------------------------------------------------- /glassfish/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.samaxes.javaee7 7 | glassfish-jaxrs-validation 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | Validating JAX-RS resource data with Bean Validation in Java EE 7 12 | Integrating JAX-RS with Bean Validation and internationalize error messages in GlassFish 13 | https://github.com/samaxes/jaxrs-beanvalidation-javaee7 14 | 2013 15 | 16 | samaxes 17 | http://www.samaxes.com/ 18 | 19 | 20 | 21 | The Apache Software License, Version 2.0 22 | http://www.apache.org/licenses/LICENSE-2.0.txt 23 | repo 24 | 25 | 26 | 27 | 28 | 29 | samaxes 30 | Samuel Santos 31 | http://www.samaxes.com/ 32 | 33 | project owner 34 | developer 35 | 36 | 0 37 | 38 | 39 | 40 | 41 | GitHub 42 | https://github.com/samaxes/jaxrs-beanvalidation-javaee7/issues 43 | 44 | 45 | scm:git:git://github.com/samaxes/jaxrs-beanvalidation-javaee7.git 46 | scm:git:git@github.com:samaxes/jaxrs-beanvalidation-javaee7.git 47 | https://github.com/samaxes/jaxrs-beanvalidation-javaee7 48 | 49 | 50 | 51 | 3.0 52 | 53 | 54 | 55 | jaxrs-beanvalidation-javaee7 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-compiler-plugin 60 | 3.1 61 | 62 | ${maven.compiler.source} 63 | ${maven.compiler.target} 64 | ${project.build.sourceEncoding} 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-resources-plugin 70 | 2.6 71 | 72 | ${project.build.resourceEncoding} 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-war-plugin 78 | 2.4 79 | 80 | 81 | 82 | true 83 | 84 | 85 | false 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-surefire-plugin 91 | ${surefire.version} 92 | 93 | true 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-failsafe-plugin 99 | ${surefire.version} 100 | 101 | ${project.build.sourceEncoding} 102 | 103 | 104 | 105 | integration-test 106 | 107 | integration-test 108 | 109 | 110 | 111 | verify 112 | 113 | verify 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.jboss.arquillian 125 | arquillian-bom 126 | 1.1.3.Final 127 | import 128 | pom 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.jboss.arquillian.junit 137 | arquillian-junit-container 138 | test 139 | 140 | 141 | org.jboss.shrinkwrap.resolver 142 | shrinkwrap-resolver-depchain 143 | test 144 | pom 145 | 146 | 147 | org.jboss.arquillian.container 148 | arquillian-glassfish-embedded-3.1 149 | 1.0.0.CR4 150 | test 151 | 152 | 153 | junit 154 | junit 155 | 4.11 156 | test 157 | 158 | 159 | 160 | javax 161 | javaee-api 162 | 7.0 163 | provided 164 | 165 | 166 | org.glassfish.main.extras 167 | glassfish-embedded-all 168 | 4.0 169 | provided 170 | 171 | 172 | 173 | 174 | 175 | UTF-8 176 | ISO-8859-1 177 | 1.7 178 | 1.7 179 | 2.16 180 | 181 | 182 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/AcceptLanguageRequestFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.io.IOException; 22 | 23 | import javax.ws.rs.container.ContainerRequestContext; 24 | import javax.ws.rs.container.ContainerRequestFilter; 25 | import javax.ws.rs.ext.Provider; 26 | 27 | /** 28 | * Checks whether the {@code Accept-Language} HTTP header exists and creates a {@link ThreadLocal} to store the 29 | * corresponding Locale. 30 | */ 31 | @Provider 32 | public class AcceptLanguageRequestFilter implements ContainerRequestFilter { 33 | 34 | /* 35 | * (non-Javadoc) 36 | * @see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext) 37 | */ 38 | @Override 39 | public void filter(ContainerRequestContext requestContext) throws IOException { 40 | if (!requestContext.getAcceptableLanguages().isEmpty()) { 41 | LocaleThreadLocal.set(requestContext.getAcceptableLanguages().get(0)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import javax.ws.rs.ApplicationPath; 22 | import javax.ws.rs.core.Application; 23 | 24 | @ApplicationPath("r") 25 | public class ApplicationConfig extends Application { 26 | } 27 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/LocaleSpecificMessageInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Locale; 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | 25 | import javax.validation.MessageInterpolator; 26 | 27 | /** 28 | * Delegates to a MessageInterpolator implementation but enforces a given Locale. 29 | */ 30 | public class LocaleSpecificMessageInterpolator implements MessageInterpolator { 31 | 32 | private static final Logger LOGGER = Logger.getLogger(LocaleSpecificMessageInterpolator.class.getName()); 33 | 34 | private final MessageInterpolator defaultInterpolator; 35 | 36 | public LocaleSpecificMessageInterpolator(MessageInterpolator interpolator) { 37 | this.defaultInterpolator = interpolator; 38 | } 39 | 40 | /** 41 | * Enforces the locale passed to the interpolator. 42 | */ 43 | @Override 44 | public String interpolate(String message, Context context) { 45 | LOGGER.log(Level.CONFIG, "Selecting the language " + LocaleThreadLocal.get() + " for the error message."); 46 | return defaultInterpolator.interpolate(message, context, LocaleThreadLocal.get()); 47 | } 48 | 49 | /* 50 | * (non-Javadoc) 51 | * @see javax.validation.MessageInterpolator#interpolate(java.lang.String, 52 | * javax.validation.MessageInterpolator.Context, java.util.Locale) 53 | */ 54 | @Override 55 | public String interpolate(String message, Context context, Locale locale) { 56 | return defaultInterpolator.interpolate(message, context, locale); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/LocaleThreadLocal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Locale; 22 | 23 | /** 24 | * {@link ThreadLocal} to store the Locale to be used in the message interpolator. 25 | */ 26 | public class LocaleThreadLocal { 27 | 28 | public static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); 29 | 30 | public static Locale get() { 31 | return (THREAD_LOCAL.get() == null) ? Locale.getDefault() : THREAD_LOCAL.get(); 32 | } 33 | 34 | public static void set(Locale locale) { 35 | THREAD_LOCAL.set(locale); 36 | } 37 | 38 | public static void unset() { 39 | THREAD_LOCAL.remove(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/Person.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import javax.validation.constraints.NotNull; 22 | import javax.validation.constraints.Size; 23 | import javax.xml.bind.annotation.XmlAccessType; 24 | import javax.xml.bind.annotation.XmlAccessorType; 25 | import javax.xml.bind.annotation.XmlRootElement; 26 | 27 | @XmlRootElement 28 | @XmlAccessorType(XmlAccessType.FIELD) 29 | public class Person { 30 | 31 | @NotNull 32 | private Integer id; 33 | 34 | @NotNull 35 | @Size(min = 2, max = 50) 36 | private String name; 37 | 38 | public Integer getId() { return id; } 39 | public void setId(Integer id) { this.id = id; } 40 | public String getName() { return name; } 41 | public void setName(String name) { this.name = name; } 42 | 43 | @Override 44 | public int hashCode() { 45 | final int prime = 31; 46 | int result = 1; 47 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 48 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 49 | return result; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object obj) { 54 | if (this == obj) { 55 | return true; 56 | } 57 | if (obj == null) { 58 | return false; 59 | } 60 | if (getClass() != obj.getClass()) { 61 | return false; 62 | } 63 | Person other = (Person) obj; 64 | if (id == null) { 65 | if (other.id != null) { 66 | return false; 67 | } 68 | } else if (!id.equals(other.id)) { 69 | return false; 70 | } 71 | if (name == null) { 72 | if (other.name != null) { 73 | return false; 74 | } 75 | } else if (!name.equals(other.name)) { 76 | return false; 77 | } 78 | return true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/Persons.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Collection; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.ConcurrentMap; 24 | 25 | import javax.validation.constraints.NotNull; 26 | import javax.validation.constraints.Pattern; 27 | import javax.validation.constraints.Size; 28 | import javax.ws.rs.Consumes; 29 | import javax.ws.rs.FormParam; 30 | import javax.ws.rs.GET; 31 | import javax.ws.rs.POST; 32 | import javax.ws.rs.Path; 33 | import javax.ws.rs.PathParam; 34 | import javax.ws.rs.Produces; 35 | import javax.ws.rs.core.MediaType; 36 | import javax.ws.rs.core.Response; 37 | 38 | @Path("persons") 39 | @Produces(MediaType.APPLICATION_JSON) 40 | public class Persons { 41 | 42 | private static final ConcurrentMap persons = new ConcurrentHashMap(); 43 | 44 | @GET 45 | public Collection getAll() { 46 | return persons.values(); 47 | } 48 | 49 | @GET 50 | @Path("{id}") 51 | public Person getPerson( 52 | @PathParam("id") 53 | @NotNull(message = "The id must not be null") 54 | @Pattern(regexp = "[0-9]+", message = "The id must be a valid number") 55 | String id) { 56 | return persons.get(id); 57 | } 58 | 59 | @POST 60 | @Path("create") 61 | @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 62 | public Response createPerson( 63 | @FormParam("id") 64 | @NotNull(message = "{person.id.notnull}") 65 | @Pattern(regexp = "[0-9]+", message = "{person.id.pattern}") 66 | String id, 67 | @FormParam("name") 68 | @Size(min = 2, max = 50, message = "{person.name.size}") 69 | String name) { 70 | Person person = new Person(); 71 | person.setId(Integer.valueOf(id)); 72 | person.setName(name); 73 | persons.put(id, person); 74 | return Response.status(Response.Status.CREATED).entity(person).build(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/ValidationConfigurationContextResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.lang.reflect.Constructor; 22 | import java.lang.reflect.Method; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | import javax.validation.ParameterNameProvider; 27 | import javax.validation.Validation; 28 | import javax.ws.rs.ext.ContextResolver; 29 | import javax.ws.rs.ext.Provider; 30 | 31 | import org.glassfish.jersey.server.validation.ValidationConfig; 32 | 33 | /** 34 | * Custom configuration of validation. This configuration can define custom: 35 | *
    36 | *
  • MessageInterpolator - interpolates a given constraint violation message.
  • 37 | *
  • TraversableResolver - determines if a property can be accessed by the Bean Validation provider.
  • 38 | *
  • ConstraintValidatorFactory - instantiates a ConstraintValidator instance based off its class. 39 | *
  • ParameterNameProvider - provides names for method and constructor parameters.
  • * 40 | *
41 | */ 42 | @Provider 43 | public class ValidationConfigurationContextResolver implements ContextResolver { 44 | 45 | /** 46 | * Get a context of type {@code ValidationConfig} that is applicable to the supplied type. 47 | * 48 | * @param type the class of object for which a context is desired 49 | * @return a context for the supplied type or {@code null} if a context for the supplied type is not available from 50 | * this provider. 51 | */ 52 | @Override 53 | public ValidationConfig getContext(Class type) { 54 | final ValidationConfig config = new ValidationConfig(); 55 | 56 | config.setMessageInterpolator(new LocaleSpecificMessageInterpolator(Validation.byDefaultProvider().configure() 57 | .getDefaultMessageInterpolator())); 58 | config.setParameterNameProvider(new CustomParameterNameProvider()); 59 | 60 | return config; 61 | } 62 | 63 | /** 64 | * If method input parameters are invalid, this class returns actual parameter names instead of the default ones ( 65 | * {@code arg0, arg1, ...}) 66 | */ 67 | private class CustomParameterNameProvider implements ParameterNameProvider { 68 | 69 | private final ParameterNameProvider nameProvider; 70 | 71 | public CustomParameterNameProvider() { 72 | nameProvider = Validation.byDefaultProvider().configure().getDefaultParameterNameProvider(); 73 | } 74 | 75 | @Override 76 | public List getParameterNames(final Constructor constructor) { 77 | return nameProvider.getParameterNames(constructor); 78 | } 79 | 80 | @Override 81 | public List getParameterNames(final Method method) { 82 | if ("getPerson".equals(method.getName())) { 83 | return Arrays.asList("id"); 84 | } 85 | if ("createPerson".equals(method.getName())) { 86 | return Arrays.asList("id", "name"); 87 | } 88 | return nameProvider.getParameterNames(method); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /glassfish/src/main/java/com/samaxes/javax/rs/validation/ValidationExceptionMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | import java.util.Set; 26 | import java.util.logging.Level; 27 | import java.util.logging.Logger; 28 | 29 | import javax.inject.Provider; 30 | import javax.validation.ConstraintViolation; 31 | import javax.validation.ConstraintViolationException; 32 | import javax.validation.ElementKind; 33 | import javax.validation.Path; 34 | import javax.validation.ValidationException; 35 | import javax.ws.rs.core.Configuration; 36 | import javax.ws.rs.core.Context; 37 | import javax.ws.rs.core.GenericEntity; 38 | import javax.ws.rs.core.GenericType; 39 | import javax.ws.rs.core.MediaType; 40 | import javax.ws.rs.core.Request; 41 | import javax.ws.rs.core.Response; 42 | import javax.ws.rs.core.Variant; 43 | import javax.ws.rs.ext.ExceptionMapper; 44 | 45 | import org.glassfish.jersey.server.validation.ValidationError; 46 | import org.glassfish.jersey.server.validation.internal.LocalizationMessages; 47 | 48 | /** 49 | * {@link ExceptionMapper} for {@link ValidationException}. 50 | *

51 | * Send a list of {@link ValidationError} instances in {@link Response} in addition to HTTP 400/500 status code. 52 | * Supported media types are: {@code application/json} / {@code application/xml} (if appropriate provider is registered 53 | * on server). 54 | *

55 | * 56 | * @see org.glassfish.jersey.server.validation.internal.ValidationExceptionMapper The original Glassfish class: 57 | * {@code org.glassfish.jersey.server.validation.internal.ValidationExceptionMapper} 58 | */ 59 | @javax.ws.rs.ext.Provider 60 | public class ValidationExceptionMapper implements ExceptionMapper { 61 | 62 | private static final Logger LOGGER = Logger.getLogger(ValidationExceptionMapper.class.getName()); 63 | 64 | @Context 65 | private Configuration config; 66 | 67 | @Context 68 | private Provider request; 69 | 70 | @Override 71 | public Response toResponse(final ValidationException exception) { 72 | if (exception instanceof ConstraintViolationException) { 73 | LOGGER.log(Level.FINER, LocalizationMessages.CONSTRAINT_VIOLATIONS_ENCOUNTERED(), exception); 74 | 75 | final ConstraintViolationException cve = (ConstraintViolationException) exception; 76 | final Response.ResponseBuilder response = Response.status(getStatus(cve)); 77 | 78 | // Entity 79 | final List variants = Variant.mediaTypes( 80 | MediaType.APPLICATION_XML_TYPE, 81 | MediaType.APPLICATION_JSON_TYPE).build(); 82 | final Variant variant = request.get().selectVariant(variants); 83 | if (variant != null) { 84 | response.type(variant.getMediaType()); 85 | } else { 86 | /* 87 | * default media type which will be used only when none media type from {@value variants} is in 88 | * accept header of original request. 89 | */ 90 | response.type(MediaType.TEXT_PLAIN_TYPE); 91 | } 92 | response.entity( 93 | new GenericEntity>( 94 | getEntity(cve.getConstraintViolations()), 95 | new GenericType>() {}.getType() 96 | ) 97 | ); 98 | 99 | return response.build(); 100 | } else { 101 | LOGGER.log(Level.WARNING, LocalizationMessages.VALIDATION_EXCEPTION_RAISED(), exception); 102 | 103 | return Response.serverError().entity(exception.getMessage()).build(); 104 | } 105 | } 106 | 107 | private List getEntity(final Set> violations) { 108 | final List errors = new ArrayList(); 109 | 110 | for (final ConstraintViolation violation : violations) { 111 | errors.add(new ValidationError(violation.getMessage(), violation.getMessageTemplate(), getPath(violation), 112 | getInvalidValue(violation.getInvalidValue()))); 113 | } 114 | 115 | return errors; 116 | } 117 | 118 | private String getInvalidValue(final Object invalidValue) { 119 | if (invalidValue == null) { 120 | return null; 121 | } 122 | 123 | if (invalidValue.getClass().isArray()) { 124 | return Arrays.toString((Object[]) invalidValue); 125 | } 126 | 127 | return invalidValue.toString(); 128 | } 129 | 130 | private Response.Status getStatus(final ConstraintViolationException exception) { 131 | return getResponseStatus(exception.getConstraintViolations()); 132 | } 133 | 134 | private Response.Status getResponseStatus(final Set> constraintViolations) { 135 | final Iterator> iterator = constraintViolations.iterator(); 136 | 137 | if (iterator.hasNext()) { 138 | return getResponseStatus(iterator.next()); 139 | } else { 140 | return Response.Status.BAD_REQUEST; 141 | } 142 | } 143 | 144 | private Response.Status getResponseStatus(final ConstraintViolation constraintViolation) { 145 | for (final Path.Node node : constraintViolation.getPropertyPath()) { 146 | final ElementKind kind = node.getKind(); 147 | 148 | if (ElementKind.RETURN_VALUE.equals(kind)) { 149 | return Response.Status.INTERNAL_SERVER_ERROR; 150 | } 151 | } 152 | 153 | return Response.Status.BAD_REQUEST; 154 | } 155 | 156 | private String getPath(final ConstraintViolation violation) { 157 | final String leafBeanName = violation.getLeafBean().getClass().getSimpleName(); 158 | final String leafBeanCleanName = (leafBeanName.contains("$")) ? leafBeanName.substring(0, 159 | leafBeanName.indexOf("$")) : leafBeanName; 160 | final String propertyPath = violation.getPropertyPath().toString(); 161 | 162 | return leafBeanCleanName + (!"".equals(propertyPath) ? '.' + propertyPath : ""); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /glassfish/src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | person.id.notnull=The person id must not be null 2 | person.id.pattern=The person id must be a valid number 3 | person.name.size=The person name must be between {min} and {max} chars long 4 | -------------------------------------------------------------------------------- /glassfish/src/main/resources/ValidationMessages_pt.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samaxes/jaxrs-beanvalidation-javaee7/e801ce84f3686e9c5e03b1a8285e2ce5abbff56f/glassfish/src/main/resources/ValidationMessages_pt.properties -------------------------------------------------------------------------------- /glassfish/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /glassfish/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@page contentType="text/html" pageEncoding="UTF-8" %> 2 | 3 | 4 | 5 | 6 | Validating JAX-RS resource data with Bean Validation in Java EE 7 7 | 8 | 15 | 16 | 17 |
18 |

Validating JAX-RS resource data with Bean Validation in Java EE 7

19 |
20 |
21 |
22 | Get all 23 |
24 |
25 |
26 | Person id:
27 | 28 |
29 |
30 |
31 |
32 | Person id:
33 | Person name:
34 | 35 |
36 |
37 |
38 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /glassfish/src/test/java/com/samaxes/javax/rs/validation/PersonsIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.io.IOException; 22 | import java.net.URL; 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.logging.Logger; 26 | 27 | import javax.json.JsonArray; 28 | import javax.json.JsonObject; 29 | import javax.json.JsonValue; 30 | import javax.json.stream.JsonGenerator; 31 | import javax.servlet.http.HttpServletResponse; 32 | import javax.ws.rs.client.Client; 33 | import javax.ws.rs.client.ClientBuilder; 34 | import javax.ws.rs.client.Entity; 35 | import javax.ws.rs.core.Form; 36 | import javax.ws.rs.core.GenericType; 37 | import javax.ws.rs.core.MediaType; 38 | import javax.ws.rs.core.Response; 39 | 40 | import org.glassfish.jersey.jsonp.JsonProcessingFeature; 41 | import org.jboss.arquillian.container.test.api.Deployment; 42 | import org.jboss.arquillian.container.test.api.RunAsClient; 43 | import org.jboss.arquillian.junit.Arquillian; 44 | import org.jboss.arquillian.junit.InSequence; 45 | import org.jboss.arquillian.test.api.ArquillianResource; 46 | import org.jboss.shrinkwrap.api.ArchivePaths; 47 | import org.jboss.shrinkwrap.api.ShrinkWrap; 48 | import org.jboss.shrinkwrap.api.asset.EmptyAsset; 49 | import org.jboss.shrinkwrap.api.formatter.Formatters; 50 | import org.jboss.shrinkwrap.api.spec.WebArchive; 51 | import org.junit.Assert; 52 | import org.junit.Test; 53 | import org.junit.runner.RunWith; 54 | 55 | @RunWith(Arquillian.class) 56 | public class PersonsIT { 57 | 58 | private static final Logger LOGGER = Logger.getLogger(PersonsIT.class.getName()); 59 | 60 | @Deployment 61 | public static WebArchive createDeployment() throws IOException { 62 | final WebArchive war = ShrinkWrap.create(WebArchive.class, "jaxrs-beanvalidation-javaee7.war") 63 | .addPackage("com.samaxes.javax.rs.validation") 64 | .addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); 65 | 66 | LOGGER.info(war.toString(Formatters.VERBOSE)); 67 | 68 | return war; 69 | } 70 | 71 | @Test 72 | @RunAsClient 73 | @InSequence(10) 74 | public void shouldReturnAllPersons(@ArquillianResource URL baseURL) { 75 | Client client = ClientBuilder.newBuilder() 76 | .register(JsonProcessingFeature.class) 77 | .property(JsonGenerator.PRETTY_PRINTING, true) 78 | .build(); 79 | Response response = client.target(baseURL + "r/persons") 80 | .request(MediaType.APPLICATION_JSON) 81 | .get(); 82 | response.bufferEntity(); 83 | 84 | logResponse("shouldReturnAllPersons", response, JsonArray.class); 85 | Assert.assertEquals(Collections.emptyList(), response.readEntity(new GenericType>() {})); 86 | } 87 | 88 | @Test 89 | @RunAsClient 90 | @InSequence(20) 91 | public void shouldReturnAValidationErrorWhenGettingAPerson(@ArquillianResource URL baseURL) { 92 | Client client = ClientBuilder.newBuilder() 93 | .register(JsonProcessingFeature.class) 94 | .property(JsonGenerator.PRETTY_PRINTING, true) 95 | .build(); 96 | Response response = client.target(baseURL + "r/persons/{id}") 97 | .resolveTemplate("id", "test") 98 | .request(MediaType.APPLICATION_JSON) 99 | .header("Accept-Language", "en") 100 | .get(); 101 | response.bufferEntity(); 102 | 103 | logResponse("shouldReturnAValidationErrorWhenGettingAPerson", response, JsonObject.class); 104 | Assert.assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); 105 | } 106 | 107 | @Test 108 | @RunAsClient 109 | @InSequence(30) 110 | public void shouldReturnAnEmptyPerson(@ArquillianResource URL baseURL) { 111 | Client client = ClientBuilder.newBuilder() 112 | .register(JsonProcessingFeature.class) 113 | .property(JsonGenerator.PRETTY_PRINTING, true) 114 | .build(); 115 | Response response = client.target(baseURL + "r/persons/{id}") 116 | .resolveTemplate("id", "10") 117 | .request(MediaType.APPLICATION_JSON) 118 | .get(); 119 | response.bufferEntity(); 120 | 121 | logResponse("shouldReturnAnEmptyPerson", response, JsonObject.class); 122 | Assert.assertEquals(null, response.readEntity(Person.class)); 123 | } 124 | 125 | @Test 126 | @RunAsClient 127 | @InSequence(40) 128 | public void shouldReturnAValidationErrorWhenCreatingAPerson(@ArquillianResource URL baseURL) { 129 | Form form = new Form(); 130 | 131 | Client client = ClientBuilder.newBuilder() 132 | .register(JsonProcessingFeature.class) 133 | .property(JsonGenerator.PRETTY_PRINTING, true) 134 | .build(); 135 | Response response = client.target(baseURL + "r/persons/create") 136 | .request(MediaType.APPLICATION_JSON) 137 | .header("Accept-Language", "pt") 138 | .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED)); 139 | response.bufferEntity(); 140 | 141 | logResponse("shouldReturnAValidationErrorWhenCreatingAPerson", response, JsonObject.class); 142 | Assert.assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); 143 | } 144 | 145 | @Test 146 | @RunAsClient 147 | @InSequence(50) 148 | public void shouldReturnACreatedPerson(@ArquillianResource URL baseURL) { 149 | Person person = new Person(); 150 | person.setId(20); 151 | person.setName("sam"); 152 | Form form = new Form(); 153 | form.param("id", String.valueOf(person.getId())); 154 | form.param("name", person.getName()); 155 | 156 | Client client = ClientBuilder.newBuilder() 157 | .register(JsonProcessingFeature.class) 158 | .property(JsonGenerator.PRETTY_PRINTING, true) 159 | .build(); 160 | Response response = client.target(baseURL + "r/persons/create") 161 | .request(MediaType.APPLICATION_JSON) 162 | .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED)); 163 | response.bufferEntity(); 164 | 165 | logResponse("shouldReturnACreatedPerson", response, JsonObject.class); 166 | Assert.assertEquals(person, response.readEntity(Person.class)); 167 | } 168 | 169 | private void logResponse(String method, Response response, Class type) { 170 | StringBuilder builder = new StringBuilder(method).append("\n"); 171 | builder.append("Response: ").append(response).append("\n"); 172 | builder.append("Entity: "); 173 | if (MediaType.APPLICATION_JSON_TYPE.equals(response.getMediaType())) { 174 | builder.append(response.readEntity(type)); 175 | } 176 | builder.append("\n"); 177 | LOGGER.info(builder.toString()); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /wildfly/README.md: -------------------------------------------------------------------------------- 1 | # Integrating JAX-RS with Bean Validation and internationalize error messages in WildFly 8 2 | 3 | * This code was used for a post on my own blog at [Validating JAX-RS resource data with Bean Validation in Java EE 7 and WildFly](http://www.samaxes.com/2014/04/jaxrs-beanvalidation-javaee7-wildfly/). 4 | -------------------------------------------------------------------------------- /wildfly/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.samaxes.javaee7 7 | wildfly-jaxrs-validation 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | Validating JAX-RS resource data with Bean Validation in Java EE 7 12 | Integrating JAX-RS with Bean Validation and internationalize error messages in WildFly 13 | https://github.com/samaxes/jaxrs-beanvalidation-javaee7 14 | 2014 15 | 16 | samaxes 17 | http://www.samaxes.com/ 18 | 19 | 20 | 21 | The Apache Software License, Version 2.0 22 | http://www.apache.org/licenses/LICENSE-2.0.txt 23 | repo 24 | 25 | 26 | 27 | 28 | 29 | samaxes 30 | Samuel Santos 31 | http://www.samaxes.com/ 32 | 33 | project owner 34 | developer 35 | 36 | 0 37 | 38 | 39 | 40 | 41 | GitHub 42 | https://github.com/samaxes/jaxrs-beanvalidation-javaee7/issues 43 | 44 | 45 | scm:git:git://github.com/samaxes/jaxrs-beanvalidation-javaee7.git 46 | scm:git:git@github.com:samaxes/jaxrs-beanvalidation-javaee7.git 47 | https://github.com/samaxes/jaxrs-beanvalidation-javaee7 48 | 49 | 50 | 51 | 3.0 52 | 53 | 54 | 55 | jaxrs-beanvalidation-javaee7 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-compiler-plugin 60 | 3.1 61 | 62 | ${maven.compiler.source} 63 | ${maven.compiler.target} 64 | ${project.build.sourceEncoding} 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-resources-plugin 70 | 2.6 71 | 72 | ${project.build.resourceEncoding} 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-war-plugin 78 | 2.4 79 | 80 | 81 | 82 | true 83 | 84 | 85 | false 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-dependency-plugin 91 | 2.8 92 | 93 | 94 | unpack 95 | pre-integration-test 96 | 97 | unpack 98 | 99 | 100 | 101 | 102 | org.wildfly 103 | wildfly-dist 104 | ${wildfly.version} 105 | zip 106 | ${project.build.directory} 107 | false 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-surefire-plugin 117 | ${surefire.version} 118 | 119 | true 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-failsafe-plugin 125 | ${surefire.version} 126 | 127 | ${project.build.sourceEncoding} 128 | 129 | org.jboss.logmanager.LogManager 130 | ${wildfly.home} 131 | ${wildfly.home}/modules 132 | 133 | 134 | 135 | 136 | integration-test 137 | 138 | integration-test 139 | 140 | 141 | 142 | verify 143 | 144 | verify 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.jboss.arquillian 156 | arquillian-bom 157 | 1.1.4.Final 158 | import 159 | pom 160 | 161 | 162 | org.jboss.resteasy 163 | resteasy-bom 164 | 3.0.6.Final 165 | import 166 | pom 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | junit 175 | junit 176 | 4.11 177 | test 178 | 179 | 180 | org.jboss.arquillian.junit 181 | arquillian-junit-container 182 | test 183 | 184 | 185 | org.jboss.shrinkwrap.resolver 186 | shrinkwrap-resolver-depchain 187 | test 188 | pom 189 | 190 | 191 | org.wildfly 192 | wildfly-arquillian-container-embedded 193 | ${wildfly.version} 194 | test 195 | 196 | 197 | 201 | org.jboss.resteasy 202 | resteasy-client 203 | test 204 | 205 | 206 | org.jboss.resteasy 207 | resteasy-jackson2-provider 208 | test 209 | 210 | 211 | 212 | javax 213 | javaee-api 214 | 7.0 215 | provided 216 | 217 | 223 | 224 | org.jboss.resteasy 225 | resteasy-jaxrs 226 | provided 227 | 228 | 229 | org.jboss.resteasy 230 | resteasy-validator-provider-11 231 | provided 232 | 233 | 234 | 235 | 236 | 237 | UTF-8 238 | ISO-8859-1 239 | 1.7 240 | 1.7 241 | 2.17 242 | 8.0.0.Final 243 | ${project.build.directory}/wildfly-${wildfly.version} 244 | 245 | 246 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/AcceptLanguageRequestFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.io.IOException; 22 | 23 | import javax.ws.rs.container.ContainerRequestContext; 24 | import javax.ws.rs.container.ContainerRequestFilter; 25 | import javax.ws.rs.ext.Provider; 26 | 27 | /** 28 | * Checks whether the {@code Accept-Language} HTTP header exists and creates a {@link ThreadLocal} to store the 29 | * corresponding Locale. 30 | */ 31 | @Provider 32 | public class AcceptLanguageRequestFilter implements ContainerRequestFilter { 33 | 34 | /* 35 | * (non-Javadoc) 36 | * @see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext) 37 | */ 38 | @Override 39 | public void filter(ContainerRequestContext requestContext) throws IOException { 40 | if (!requestContext.getAcceptableLanguages().isEmpty()) { 41 | LocaleThreadLocal.set(requestContext.getAcceptableLanguages().get(0)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import javax.ws.rs.ApplicationPath; 22 | import javax.ws.rs.core.Application; 23 | 24 | @ApplicationPath("r") 25 | public class ApplicationConfig extends Application { 26 | } 27 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/LocaleSpecificMessageInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Locale; 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | 25 | import javax.validation.MessageInterpolator; 26 | 27 | /** 28 | * Delegates to a MessageInterpolator implementation but enforces a given Locale. 29 | */ 30 | public class LocaleSpecificMessageInterpolator implements MessageInterpolator { 31 | 32 | private static final Logger LOGGER = Logger.getLogger(LocaleSpecificMessageInterpolator.class.getName()); 33 | 34 | private final MessageInterpolator defaultInterpolator; 35 | 36 | public LocaleSpecificMessageInterpolator(MessageInterpolator interpolator) { 37 | this.defaultInterpolator = interpolator; 38 | } 39 | 40 | /** 41 | * Enforces the locale passed to the interpolator. 42 | */ 43 | @Override 44 | public String interpolate(String message, Context context) { 45 | LOGGER.log(Level.CONFIG, "Selecting the language " + LocaleThreadLocal.get() + " for the error message."); 46 | return defaultInterpolator.interpolate(message, context, LocaleThreadLocal.get()); 47 | } 48 | 49 | /* 50 | * (non-Javadoc) 51 | * @see javax.validation.MessageInterpolator#interpolate(java.lang.String, 52 | * javax.validation.MessageInterpolator.Context, java.util.Locale) 53 | */ 54 | @Override 55 | public String interpolate(String message, Context context, Locale locale) { 56 | return defaultInterpolator.interpolate(message, context, locale); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/LocaleThreadLocal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Locale; 22 | 23 | /** 24 | * {@link ThreadLocal} to store the Locale to be used in the message interpolator. 25 | */ 26 | public class LocaleThreadLocal { 27 | 28 | public static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); 29 | 30 | public static Locale get() { 31 | return (THREAD_LOCAL.get() == null) ? Locale.getDefault() : THREAD_LOCAL.get(); 32 | } 33 | 34 | public static void set(Locale locale) { 35 | THREAD_LOCAL.set(locale); 36 | } 37 | 38 | public static void unset() { 39 | THREAD_LOCAL.remove(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/Person.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import javax.validation.constraints.NotNull; 22 | import javax.validation.constraints.Size; 23 | import javax.xml.bind.annotation.XmlAccessType; 24 | import javax.xml.bind.annotation.XmlAccessorType; 25 | import javax.xml.bind.annotation.XmlRootElement; 26 | 27 | @XmlRootElement 28 | @XmlAccessorType(XmlAccessType.FIELD) 29 | public class Person { 30 | 31 | @NotNull 32 | private Integer id; 33 | 34 | @NotNull 35 | @Size(min = 2, max = 50) 36 | private String name; 37 | 38 | public Integer getId() { return id; } 39 | public void setId(Integer id) { this.id = id; } 40 | public String getName() { return name; } 41 | public void setName(String name) { this.name = name; } 42 | 43 | @Override 44 | public int hashCode() { 45 | final int prime = 31; 46 | int result = 1; 47 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 48 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 49 | return result; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object obj) { 54 | if (this == obj) { 55 | return true; 56 | } 57 | if (obj == null) { 58 | return false; 59 | } 60 | if (getClass() != obj.getClass()) { 61 | return false; 62 | } 63 | Person other = (Person) obj; 64 | if (id == null) { 65 | if (other.id != null) { 66 | return false; 67 | } 68 | } else if (!id.equals(other.id)) { 69 | return false; 70 | } 71 | if (name == null) { 72 | if (other.name != null) { 73 | return false; 74 | } 75 | } else if (!name.equals(other.name)) { 76 | return false; 77 | } 78 | return true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/Persons.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Collection; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.ConcurrentMap; 24 | 25 | import javax.validation.constraints.NotNull; 26 | import javax.validation.constraints.Pattern; 27 | import javax.validation.constraints.Size; 28 | import javax.ws.rs.Consumes; 29 | import javax.ws.rs.FormParam; 30 | import javax.ws.rs.GET; 31 | import javax.ws.rs.POST; 32 | import javax.ws.rs.Path; 33 | import javax.ws.rs.PathParam; 34 | import javax.ws.rs.Produces; 35 | import javax.ws.rs.core.MediaType; 36 | import javax.ws.rs.core.Response; 37 | 38 | @Path("persons") 39 | @Produces(MediaType.APPLICATION_JSON) 40 | public class Persons { 41 | 42 | private static final ConcurrentMap persons = new ConcurrentHashMap(); 43 | 44 | @GET 45 | public Collection getAll() { 46 | return persons.values(); 47 | } 48 | 49 | @GET 50 | @Path("{id}") 51 | public Person getPerson( 52 | @PathParam("id") 53 | @NotNull(message = "The id must not be null") 54 | @Pattern(regexp = "[0-9]+", message = "The id must be a valid number") 55 | String id) { 56 | return persons.get(id); 57 | } 58 | 59 | @POST 60 | @Path("create") 61 | @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 62 | public Response createPerson( 63 | @FormParam("id") 64 | @NotNull(message = "{person.id.notnull}") 65 | @Pattern(regexp = "[0-9]+", message = "{person.id.pattern}") 66 | String id, 67 | @FormParam("name") 68 | @Size(min = 2, max = 50, message = "{person.name.size}") 69 | String name) { 70 | Person person = new Person(); 71 | person.setId(Integer.valueOf(id)); 72 | person.setName(name); 73 | persons.put(id, person); 74 | return Response.status(Response.Status.CREATED).entity(person).build(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/ValidationConfigurationContextResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.lang.reflect.Constructor; 22 | import java.lang.reflect.Method; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | import javax.validation.BootstrapConfiguration; 27 | import javax.validation.Configuration; 28 | import javax.validation.ParameterNameProvider; 29 | import javax.validation.Validation; 30 | import javax.ws.rs.ext.ContextResolver; 31 | import javax.ws.rs.ext.Provider; 32 | 33 | import org.jboss.resteasy.plugins.validation.GeneralValidatorImpl; 34 | import org.jboss.resteasy.spi.validation.GeneralValidator; 35 | 36 | /** 37 | * Custom configuration of validation. This configuration can define custom: 38 | *
    39 | *
  • MessageInterpolator - interpolates a given constraint violation message.
  • 40 | *
  • TraversableResolver - determines if a property can be accessed by the Bean Validation provider.
  • 41 | *
  • ConstraintValidatorFactory - instantiates a ConstraintValidator instance based off its class. 42 | *
  • ParameterNameProvider - provides names for method and constructor parameters.
  • * 43 | *
44 | */ 45 | @Provider 46 | public class ValidationConfigurationContextResolver implements ContextResolver { 47 | 48 | /** 49 | * Get a context of type {@code GeneralValidator} that is applicable to the supplied type. 50 | * 51 | * @param type the class of object for which a context is desired 52 | * @return a context for the supplied type or {@code null} if a context for the supplied type is not available from 53 | * this provider. 54 | */ 55 | @Override 56 | public GeneralValidator getContext(Class type) { 57 | Configuration config = Validation.byDefaultProvider().configure(); 58 | BootstrapConfiguration bootstrapConfiguration = config.getBootstrapConfiguration(); 59 | 60 | config.messageInterpolator(new LocaleSpecificMessageInterpolator(Validation.byDefaultProvider().configure() 61 | .getDefaultMessageInterpolator())); 62 | config.parameterNameProvider(new CustomParameterNameProvider()); 63 | 64 | return new GeneralValidatorImpl(config.buildValidatorFactory(), 65 | bootstrapConfiguration.isExecutableValidationEnabled(), 66 | bootstrapConfiguration.getDefaultValidatedExecutableTypes()); 67 | } 68 | 69 | /** 70 | * If method input parameters are invalid, this class returns actual parameter names instead of the default ones ( 71 | * {@code arg0, arg1, ...}) 72 | */ 73 | private class CustomParameterNameProvider implements ParameterNameProvider { 74 | 75 | private final ParameterNameProvider nameProvider; 76 | 77 | public CustomParameterNameProvider() { 78 | nameProvider = Validation.byDefaultProvider().configure().getDefaultParameterNameProvider(); 79 | } 80 | 81 | @Override 82 | public List getParameterNames(final Constructor constructor) { 83 | return nameProvider.getParameterNames(constructor); 84 | } 85 | 86 | @Override 87 | public List getParameterNames(final Method method) { 88 | if ("getPerson".equals(method.getName())) { 89 | return Arrays.asList("id"); 90 | } 91 | if ("createPerson".equals(method.getName())) { 92 | return Arrays.asList("id", "name"); 93 | } 94 | return nameProvider.getParameterNames(method); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /wildfly/src/main/java/com/samaxes/javax/rs/validation/ValidationExceptionMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.util.Iterator; 22 | import java.util.List; 23 | 24 | import javax.validation.ConstraintDeclarationException; 25 | import javax.validation.ConstraintDefinitionException; 26 | import javax.validation.GroupDefinitionException; 27 | import javax.validation.ValidationException; 28 | import javax.ws.rs.core.MediaType; 29 | import javax.ws.rs.core.Response; 30 | import javax.ws.rs.core.Response.ResponseBuilder; 31 | import javax.ws.rs.core.Response.Status; 32 | import javax.ws.rs.ext.ExceptionMapper; 33 | import javax.ws.rs.ext.Provider; 34 | 35 | import org.jboss.resteasy.api.validation.ResteasyViolationException; 36 | import org.jboss.resteasy.api.validation.Validation; 37 | import org.jboss.resteasy.api.validation.ViolationReport; 38 | 39 | /** 40 | * {@link ExceptionMapper} for {@link ValidationException}. 41 | *

42 | * Send a {@link ViolationReport} in {@link Response} in addition to HTTP 400/500 status code. Supported media types 43 | * are: {@code application/json} / {@code application/xml} (if appropriate provider is registered on server). 44 | *

45 | * 46 | * @see org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper The original WildFly class: 47 | * {@code org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper} 48 | */ 49 | @Provider 50 | public class ValidationExceptionMapper implements ExceptionMapper { 51 | 52 | @Override 53 | public Response toResponse(ValidationException exception) { 54 | if (exception instanceof ConstraintDefinitionException) { 55 | return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); 56 | } 57 | if (exception instanceof ConstraintDeclarationException) { 58 | return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); 59 | } 60 | if (exception instanceof GroupDefinitionException) { 61 | return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); 62 | } 63 | if (exception instanceof ResteasyViolationException) { 64 | ResteasyViolationException resteasyViolationException = ResteasyViolationException.class.cast(exception); 65 | Exception e = resteasyViolationException.getException(); 66 | if (e != null) { 67 | return buildResponse(unwrapException(e), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); 68 | } else if (resteasyViolationException.getReturnValueViolations().size() == 0) { 69 | return buildViolationReportResponse(resteasyViolationException, Status.BAD_REQUEST); 70 | } else { 71 | return buildViolationReportResponse(resteasyViolationException, Status.INTERNAL_SERVER_ERROR); 72 | } 73 | } 74 | return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); 75 | } 76 | 77 | protected Response buildResponse(Object entity, String mediaType, Status status) { 78 | ResponseBuilder builder = Response.status(status).entity(entity); 79 | builder.type(MediaType.TEXT_PLAIN); 80 | builder.header(Validation.VALIDATION_HEADER, "true"); 81 | return builder.build(); 82 | } 83 | 84 | protected Response buildViolationReportResponse(ResteasyViolationException exception, Status status) { 85 | ResponseBuilder builder = Response.status(status); 86 | builder.header(Validation.VALIDATION_HEADER, "true"); 87 | 88 | // Check standard media types. 89 | MediaType mediaType = getAcceptMediaType(exception.getAccept()); 90 | if (mediaType != null) { 91 | builder.type(mediaType); 92 | builder.entity(new ViolationReport(exception)); 93 | return builder.build(); 94 | } 95 | 96 | // Default media type. 97 | builder.type(MediaType.TEXT_PLAIN); 98 | builder.entity(exception.toString()); 99 | return builder.build(); 100 | } 101 | 102 | protected String unwrapException(Throwable t) { 103 | StringBuffer sb = new StringBuffer(); 104 | doUnwrapException(sb, t); 105 | return sb.toString(); 106 | } 107 | 108 | private void doUnwrapException(StringBuffer sb, Throwable t) { 109 | if (t == null) { 110 | return; 111 | } 112 | sb.append(t.toString()); 113 | if (t.getCause() != null && t != t.getCause()) { 114 | sb.append('['); 115 | doUnwrapException(sb, t.getCause()); 116 | sb.append(']'); 117 | } 118 | } 119 | 120 | private MediaType getAcceptMediaType(List accept) { 121 | Iterator it = accept.iterator(); 122 | while (it.hasNext()) { 123 | MediaType mt = it.next(); 124 | /* 125 | * application/xml media type causes an exception: 126 | * org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response 127 | * object of type: org.jboss.resteasy.api.validation.ViolationReport of media type: application/xml 128 | */ 129 | /*if (MediaType.APPLICATION_XML_TYPE.getType().equals(mt.getType()) 130 | && MediaType.APPLICATION_XML_TYPE.getSubtype().equals(mt.getSubtype())) { 131 | return MediaType.APPLICATION_XML_TYPE; 132 | }*/ 133 | if (MediaType.APPLICATION_JSON_TYPE.getType().equals(mt.getType()) 134 | && MediaType.APPLICATION_JSON_TYPE.getSubtype().equals(mt.getSubtype())) { 135 | return MediaType.APPLICATION_JSON_TYPE; 136 | } 137 | } 138 | return null; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /wildfly/src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | person.id.notnull=The person id must not be null 2 | person.id.pattern=The person id must be a valid number 3 | person.name.size=The person name must be between {min} and {max} chars long 4 | -------------------------------------------------------------------------------- /wildfly/src/main/resources/ValidationMessages_pt.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samaxes/jaxrs-beanvalidation-javaee7/e801ce84f3686e9c5e03b1a8285e2ce5abbff56f/wildfly/src/main/resources/ValidationMessages_pt.properties -------------------------------------------------------------------------------- /wildfly/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /wildfly/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@page contentType="text/html" pageEncoding="UTF-8" %> 2 | 3 | 4 | 5 | 6 | Validating JAX-RS resource data with Bean Validation in Java EE 7 7 | 8 | 15 | 16 | 17 |
18 |

Validating JAX-RS resource data with Bean Validation in Java EE 7

19 |
20 |
21 |
22 | Get all 23 |
24 |
25 |
26 | Person id:
27 | 28 |
29 |
30 |
31 |
32 | Person id:
33 | Person name:
34 | 35 |
36 |
37 |
38 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /wildfly/src/test/java/com/samaxes/javax/rs/validation/PersonsIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Integrating Bean Validation with JAX-RS in Java EE 7 3 | * https://github.com/samaxes/jaxrs-beanvalidation-javaee7 4 | * 5 | * Copyright (c) 2013 samaxes.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package com.samaxes.javax.rs.validation; 20 | 21 | import java.io.IOException; 22 | import java.net.URL; 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.logging.Logger; 26 | 27 | import javax.servlet.http.HttpServletResponse; 28 | import javax.ws.rs.client.Client; 29 | import javax.ws.rs.client.ClientBuilder; 30 | import javax.ws.rs.client.Entity; 31 | import javax.ws.rs.core.Form; 32 | import javax.ws.rs.core.GenericType; 33 | import javax.ws.rs.core.MediaType; 34 | import javax.ws.rs.core.Response; 35 | 36 | import org.jboss.arquillian.container.test.api.Deployment; 37 | import org.jboss.arquillian.container.test.api.RunAsClient; 38 | import org.jboss.arquillian.junit.Arquillian; 39 | import org.jboss.arquillian.junit.InSequence; 40 | import org.jboss.arquillian.test.api.ArquillianResource; 41 | import org.jboss.shrinkwrap.api.ArchivePaths; 42 | import org.jboss.shrinkwrap.api.ShrinkWrap; 43 | import org.jboss.shrinkwrap.api.asset.EmptyAsset; 44 | import org.jboss.shrinkwrap.api.formatter.Formatters; 45 | import org.jboss.shrinkwrap.api.spec.WebArchive; 46 | import org.junit.Assert; 47 | import org.junit.Test; 48 | import org.junit.runner.RunWith; 49 | 50 | import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; 51 | 52 | @RunWith(Arquillian.class) 53 | public class PersonsIT { 54 | 55 | private static final Logger LOGGER = Logger.getLogger(PersonsIT.class.getName()); 56 | 57 | @Deployment 58 | public static WebArchive createDeployment() throws IOException { 59 | final WebArchive war = ShrinkWrap.create(WebArchive.class, "jaxrs-beanvalidation-javaee7.war") 60 | .addPackage("com.samaxes.javax.rs.validation") 61 | .addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); 62 | 63 | LOGGER.info(war.toString(Formatters.VERBOSE)); 64 | 65 | return war; 66 | } 67 | 68 | @Test 69 | @RunAsClient 70 | @InSequence(10) 71 | public void shouldReturnAllPersons(@ArquillianResource URL baseURL) { 72 | Client client = ClientBuilder.newBuilder() 73 | .register(JacksonJsonProvider.class) 74 | .build(); 75 | Response response = client.target(baseURL + "r/persons") 76 | .request(MediaType.APPLICATION_JSON) 77 | .get(); 78 | response.bufferEntity(); 79 | 80 | logResponse("shouldReturnAllPersons", response); 81 | Assert.assertEquals(Collections.emptyList(), response.readEntity(new GenericType>() {})); 82 | } 83 | 84 | @Test 85 | @RunAsClient 86 | @InSequence(20) 87 | public void shouldReturnAValidationErrorWhenGettingAPerson(@ArquillianResource URL baseURL) { 88 | Client client = ClientBuilder.newBuilder() 89 | .register(JacksonJsonProvider.class) 90 | .build(); 91 | Response response = client.target(baseURL + "r/persons/{id}") 92 | .resolveTemplate("id", "test") 93 | .request(MediaType.APPLICATION_JSON) 94 | .header("Accept-Language", "en") 95 | .get(); 96 | response.bufferEntity(); 97 | 98 | logResponse("shouldReturnAValidationErrorWhenGettingAPerson", response); 99 | Assert.assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); 100 | } 101 | 102 | @Test 103 | @RunAsClient 104 | @InSequence(30) 105 | public void shouldReturnAnEmptyPerson(@ArquillianResource URL baseURL) { 106 | Client client = ClientBuilder.newBuilder() 107 | .register(JacksonJsonProvider.class) 108 | .build(); 109 | Response response = client.target(baseURL + "r/persons/{id}") 110 | .resolveTemplate("id", "10") 111 | .request(MediaType.APPLICATION_JSON) 112 | .get(); 113 | response.bufferEntity(); 114 | 115 | logResponse("shouldReturnAnEmptyPerson", response); 116 | Assert.assertEquals(null, response.readEntity(Person.class)); 117 | } 118 | 119 | @Test 120 | @RunAsClient 121 | @InSequence(40) 122 | public void shouldReturnAValidationErrorWhenCreatingAPerson(@ArquillianResource URL baseURL) { 123 | Form form = new Form(); 124 | 125 | Client client = ClientBuilder.newBuilder() 126 | .register(JacksonJsonProvider.class) 127 | .build(); 128 | Response response = client.target(baseURL + "r/persons/create") 129 | .request(MediaType.APPLICATION_JSON) 130 | .header("Accept-Language", "pt") 131 | .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED)); 132 | response.bufferEntity(); 133 | 134 | logResponse("shouldReturnAValidationErrorWhenCreatingAPerson", response); 135 | Assert.assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); 136 | } 137 | 138 | @Test 139 | @RunAsClient 140 | @InSequence(50) 141 | public void shouldReturnACreatedPerson(@ArquillianResource URL baseURL) { 142 | Person person = new Person(); 143 | person.setId(20); 144 | person.setName("sam"); 145 | Form form = new Form(); 146 | form.param("id", String.valueOf(person.getId())); 147 | form.param("name", person.getName()); 148 | 149 | Client client = ClientBuilder.newBuilder() 150 | .register(JacksonJsonProvider.class) 151 | .build(); 152 | Response response = client.target(baseURL + "r/persons/create") 153 | .request(MediaType.APPLICATION_JSON) 154 | .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED)); 155 | response.bufferEntity(); 156 | 157 | logResponse("shouldReturnACreatedPerson", response); 158 | Assert.assertEquals(person, response.readEntity(Person.class)); 159 | } 160 | 161 | private void logResponse(String method, Response response) { 162 | StringBuilder builder = new StringBuilder(method).append("\n"); 163 | builder.append("Response: ").append(response).append("\n"); 164 | builder.append("Entity: ").append(response.readEntity(String.class)).append("\n"); 165 | LOGGER.info(builder.toString()); 166 | } 167 | } 168 | --------------------------------------------------------------------------------