Advertisement
Guest User

Untitled

a guest
Dec 5th, 2016
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.27 KB | None | 0 0
  1. package org.tkareine.validation_example;
  2.  
  3. import java.sql.SQLException;
  4. import java.time.LocalDate;
  5. import java.util.LinkedHashMap;
  6. import java.util.Map;
  7. import java.util.Optional;
  8.  
  9. /**
  10. * Design considerations:
  11. *
  12. * <ul>
  13. * <li>validate all user input, showing all failures</li>
  14. * <li>integrate with legacy backend code that uses nulls and (checked)
  15. * exceptions</li>
  16. * </ul>
  17. *
  18. * Usage:
  19. *
  20. * <pre>
  21. * $ mkdir -p target &&
  22. * javac -d target src/org/tkareine/validation_example/BuilderExample.java &&
  23. * java -ea -cp target org.tkareine.validation_example.BuilderExample
  24. * </pre>
  25. */
  26. public class BuilderExample {
  27. public static void main(String[] args) {
  28. // demonstrates dependency injection
  29. UserInputValidation userInputValidation = new UserInputValidation(new SafeLegacyBackend(new LegacyBackend("jdbc:oracle:thin:@sunspark:1521:monolithic")));
  30.  
  31. testSuccess(userInputValidation);
  32. testFailureWhenEmpty(userInputValidation);
  33. testFailureWhenInvalid(userInputValidation);
  34. testFailureWhenRoleNotFound(userInputValidation);
  35.  
  36. System.out.println("ok");
  37. }
  38.  
  39. private static void testSuccess(UserInputValidation userInputValidation) {
  40. assert(userInputValidation.builder()
  41. .birthYear("1942")
  42. .role("scientist")
  43. .build()
  44. .isSuccess());
  45. }
  46.  
  47. private static void testFailureWhenEmpty(UserInputValidation userInputValidation) {
  48. UserInputValidation.Result validationResult = userInputValidation.builder()
  49. .birthYear("")
  50. .role("")
  51. .build();
  52.  
  53. assert(!validationResult.isSuccess());
  54. assert(validationResult.getValidationFailures().size() == 2);
  55. assert(validationResult.getValidationFailures().get("birthYear").equals("empty"));
  56. assert(validationResult.getValidationFailures().get("role").equals("empty"));
  57. }
  58.  
  59. private static void testFailureWhenInvalid(UserInputValidation userInputValidation) {
  60. UserInputValidation.Result validationResult = userInputValidation.builder()
  61. .birthYear("-1981")
  62. .role("en")
  63. .build();
  64.  
  65. assert(!validationResult.isSuccess());
  66. assert(validationResult.getValidationFailures().size() == 2);
  67. assert(validationResult.getValidationFailures().get("birthYear").equals("not a year: \"-1981\""));
  68. assert(validationResult.getValidationFailures().get("role").equals("no such role: \"en\""));
  69. }
  70.  
  71. private static void testFailureWhenRoleNotFound(UserInputValidation userInputValidation) {
  72. UserInputValidation.Result validationResult = userInputValidation.builder()
  73. .birthYear("1981")
  74. .role("engineer")
  75. .build();
  76.  
  77. assert(!validationResult.isSuccess());
  78. assert(validationResult.getValidationFailures().size() == 1);
  79. assert(validationResult.getValidationFailures().get("role").equals("no such role: \"engineer\""));
  80. }
  81.  
  82. public static class UserInputValidation {
  83. private final SafeLegacyBackend safeLegacyBackend;
  84.  
  85. public UserInputValidation(SafeLegacyBackend safeLegacyBackend) {
  86. this.safeLegacyBackend = safeLegacyBackend;
  87. }
  88.  
  89. public Builder builder() {
  90. return new Builder();
  91. }
  92.  
  93. public class Builder {
  94. private final Map<String, String> failures = new LinkedHashMap<>();
  95.  
  96. public Builder birthYear(String input) {
  97. if (!checkEmptyInput("birthYear", input)) {
  98. return this;
  99. }
  100.  
  101. int year;
  102.  
  103. try {
  104. year = Integer.parseUnsignedInt(input);
  105. } catch (NumberFormatException e) {
  106. return putFailure("birthYear", "not a year: \"" + input + "\"");
  107. }
  108.  
  109. if (year < 1900 || year > LocalDate.now().getYear()) {
  110. return putFailure("birthYear", "impossible: " + input);
  111. }
  112.  
  113. return this;
  114. }
  115.  
  116. public Builder role(String input) {
  117. if (!checkEmptyInput("role", input)) {
  118. return this;
  119. }
  120.  
  121. if (!safeLegacyBackend.parseRole(input).isPresent()) {
  122. return putFailure("role", "no such role: \"" + input + "\"");
  123. }
  124.  
  125. return this;
  126. }
  127.  
  128. public Result build() {
  129. return failures.isEmpty()
  130. ? Result.success()
  131. : Result.failure(failures);
  132. }
  133.  
  134. private boolean checkEmptyInput(String fieldName, String input) {
  135. if (input.isEmpty()) {
  136. putFailure(fieldName, "empty");
  137. return false;
  138. } else {
  139. return true;
  140. }
  141. }
  142.  
  143. private Builder putFailure(String fieldName, String failure) {
  144. failures.put(fieldName, failure);
  145. return this;
  146. }
  147. }
  148.  
  149. interface Result {
  150. boolean isSuccess();
  151. Map<String, String> getValidationFailures();
  152.  
  153. static Result success() {
  154. return Success.INSTANCE;
  155. }
  156.  
  157. static Result failure(Map<String, String> validationFailures) {
  158. return new Failure(validationFailures);
  159. }
  160. }
  161.  
  162. public final static class Success implements Result {
  163. private static Success INSTANCE = new Success();
  164.  
  165. private Success() {} // hide
  166.  
  167. @Override
  168. public boolean isSuccess() {
  169. return true;
  170. }
  171.  
  172. @Override
  173. public Map<String, String> getValidationFailures() {
  174. // if you call me, you have a bug in the calling side
  175. throw new UnsupportedOperationException("getValidationFailures for success result");
  176. }
  177. }
  178.  
  179. public final static class Failure implements Result {
  180. private final Map<String, String> failures;
  181.  
  182. private Failure(Map<String, String> failures) {
  183. this.failures = failures;
  184. }
  185.  
  186. @Override
  187. public boolean isSuccess() {
  188. return false;
  189. }
  190.  
  191. @Override
  192. public Map<String, String> getValidationFailures() {
  193. return failures;
  194. }
  195. }
  196. }
  197.  
  198. /**
  199. * More reasonable interface to {@link LegacyBackend} which you design and
  200. * implement to suit your needs.
  201. */
  202. public static class SafeLegacyBackend {
  203. private LegacyBackend legacyBackend;
  204.  
  205. public SafeLegacyBackend(LegacyBackend legacyBackend) {
  206. this.legacyBackend = legacyBackend;
  207. }
  208.  
  209. public Optional<LegacyBackend.Role> parseRole(String name) {
  210. LegacyBackend.Role role;
  211.  
  212. try {
  213. role = legacyBackend.getRole(name);
  214. } catch (IllegalArgumentException ignored) {
  215. // Catch only the exceptions that are due to input. Let severe
  216. // exceptions, such as SQLException, to propagate up the call
  217. // stack. Severe exceptions might be due to system failure or
  218. // programming error -- usually there's nothing you can do about
  219. // them in the app's logic, anyway.
  220. //
  221. // This is known as the Fail Fast Principle.
  222. return Optional.empty();
  223. } catch (SQLException e) {
  224. // Wrap checked exception inside an unchecked exception in
  225. // order to avoid tainting new code with legacy design choices.
  226. throw new RuntimeException(e);
  227. }
  228.  
  229. // Consider designing new code so that you avoid using `null`. That
  230. // way you avoid null checks and you can be sure that a method
  231. // signature always returns a value of the specified return type.
  232. return Optional.ofNullable(role);
  233. }
  234. }
  235.  
  236. public static class LegacyBackend {
  237. final Database database;
  238.  
  239. public LegacyBackend(String dbURL) {
  240. database = Database.connect(dbURL);
  241. }
  242.  
  243. public Role getRole(String name) throws SQLException {
  244. if (name == null || name.length() < 3) {
  245. throw new IllegalArgumentException("invalid role name");
  246. }
  247.  
  248. return database.getRoleByName(name);
  249. }
  250.  
  251. static class Database {
  252. private Database() {} // hide
  253.  
  254. static Database connect(String dbURL) {
  255. return new Database(/* DriverManager.getConnection(dbURL) */);
  256. }
  257.  
  258. Role getRoleByName(String name) throws SQLException {
  259. // fake implementation
  260. return name.equals("scientist")
  261. ? new Role(1L, "scientist", 42)
  262. : null; // not found
  263. }
  264. }
  265.  
  266. static class Role {
  267. private final long id;
  268. private final String name;
  269. private final int karma;
  270.  
  271. public Role(long id, String name, int karma) {
  272. this.id = id;
  273. this.name = name;
  274. this.karma = karma;
  275. }
  276. }
  277. }
  278. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement