1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.json.schema;
18
19 import static org.forgerock.json.JsonValueFunctions.uri;
20 import static org.kohsuke.args4j.ExampleMode.ALL;
21 import static org.kohsuke.args4j.ExampleMode.REQUIRED;
22
23 import java.io.Console;
24 import java.io.File;
25 import java.io.FileFilter;
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Scanner;
37
38 import com.fasterxml.jackson.databind.ObjectMapper;
39 import org.forgerock.json.JsonValue;
40 import org.forgerock.json.schema.validator.Constants;
41 import org.forgerock.json.schema.validator.ErrorHandler;
42 import org.forgerock.json.schema.validator.FailFastErrorHandler;
43 import org.forgerock.json.schema.validator.ObjectValidatorFactory;
44 import org.forgerock.json.schema.validator.exceptions.SchemaException;
45 import org.forgerock.json.schema.validator.exceptions.ValidationException;
46 import org.forgerock.json.schema.validator.validators.Validator;
47 import org.kohsuke.args4j.Argument;
48 import org.kohsuke.args4j.CmdLineException;
49 import org.kohsuke.args4j.CmdLineParser;
50 import org.kohsuke.args4j.Option;
51
52
53
54
55 public final class Main {
56
57
58 private static final ObjectMapper MAPPER = new ObjectMapper();
59 private static final String ROOT_SCHEMA_ID = "http://www.forgerock.org/schema/";
60
61 private final Map<URI, Validator> schemaCache = new HashMap<>();
62
63 @Option(name = "-v", aliases = {"--verbose"}, usage = "display all validation error not just the first")
64 private boolean verbose;
65
66 @Option(name = "-s", aliases = {"--schemas"}, required = true, usage = "file or folder contains the schema(s)",
67 metaVar = "./schema")
68 private File schemaFile = new File("./schema");
69
70 @Option(name = "-b", aliases = {"--base"}, metaVar = ROOT_SCHEMA_ID,
71 usage = "base value to resolve relative schema IDs. Default: " + ROOT_SCHEMA_ID)
72 private String schemeBase = ROOT_SCHEMA_ID;
73
74 @Option(name = "-i", aliases = {"--id"},
75 usage = "id of the schema. Optional if the object has \"$schema\" property")
76 private String schemaURI;
77
78 @Option(name = "-f", aliases = {"--file"}, usage = "input from this file", metaVar = "sample.json")
79 private File inputFile;
80
81
82 @Argument
83 private List<String> arguments = new ArrayList<>();
84
85
86
87
88
89
90 public static void main(String[] args) throws Exception {
91 new Main().doMain(args);
92 }
93
94 private void doMain(String[] args) throws Exception {
95 CmdLineParser parser = new CmdLineParser(this);
96
97
98
99 parser.setUsageWidth(80);
100
101 try {
102
103 parser.parseArgument(args);
104
105
106
107
108
109
110
111
112
113 } catch (CmdLineException e) {
114
115
116
117 System.err.println(e.getMessage());
118 System.err.println("java Main [options...] arguments...");
119
120 parser.printUsage(System.err);
121 System.err.println();
122
123
124 System.err.println(" Example: java Main" + parser.printExample(REQUIRED));
125 System.err.println(" Example: java Main" + parser.printExample(ALL));
126
127 return;
128 }
129
130
131 URI base = new URI(schemeBase);
132 if (!base.isAbsolute()) {
133 throw new IllegalArgumentException("-b (-base) must be an absolute URI");
134 }
135
136
137 init(base);
138
139 if (null == inputFile) {
140 while (true) {
141 try {
142 validate(loadFromConsole());
143 } catch (Exception e) {
144 printOutException(e);
145 }
146 }
147 } else {
148 try {
149 validate(loadFromFile());
150 } catch (Exception e) {
151 printOutException(e);
152 }
153 }
154 }
155
156
157
158 private void init(URI base) throws IOException {
159 System.out.append("Loading schemas from: ")
160 .append(schemaFile.getAbsolutePath())
161 .append(" with base ")
162 .append(base.toString())
163 .println(" URI");
164 if (schemaFile.isDirectory()) {
165 validateDirectory(schemaFile);
166 FileFilter filter = new FileFilter() {
167
168 public boolean accept(File f) {
169 return (f.isDirectory()) || (f.getName().endsWith(".json"));
170 }
171 };
172
173 for (File f : getFileListingNoSort(schemaFile, filter)) {
174
175
176 URI relative = schemaFile.toURI().relativize(f.toURI());
177 loadSchema(base.resolve(relative), f);
178 }
179 } else if (schemaFile.isFile()) {
180 loadSchema(base, schemaFile);
181 } else {
182 System.exit(1);
183 }
184 }
185
186 private void loadSchema(URI base, File schemaFile) throws IOException {
187 JsonValue schemaMap = new JsonValue(MAPPER.readValue(new FileInputStream(schemaFile), Map.class));
188 URI id = schemaMap.get(Constants.ID).required().as(uri());
189 Validator v = ObjectValidatorFactory.getTypeValidator(schemaMap.asMap());
190 if (!id.isAbsolute()) {
191 id = base.resolve(id);
192 }
193 schemaCache.put(id, v);
194 System.out.append("Schema ").append(id.toString()).println(" loaded from file:");
195 System.out.append(" location: ").println(schemaFile.getAbsolutePath());
196 }
197
198
199
200
201
202
203
204
205
206
207 private List<File> getFileListingNoSort(File aStartingDir, FileFilter filter) throws FileNotFoundException {
208 List<File> result = new ArrayList<>();
209 List<File> filesDirs = Arrays.asList(aStartingDir.listFiles(filter));
210 for (File file : filesDirs) {
211 if (!file.isFile()) {
212
213
214 List<File> deeperList = getFileListingNoSort(file, filter);
215 result.addAll(deeperList);
216 } else {
217 result.add(file);
218 }
219 }
220 return result;
221 }
222
223
224
225
226
227
228
229 private void validateDirectory(File aDirectory) throws FileNotFoundException {
230 if (aDirectory == null) {
231 throw new IllegalArgumentException("Directory should not be null.");
232 }
233 if (!aDirectory.exists()) {
234 throw new FileNotFoundException("Directory does not exist: " + aDirectory);
235 }
236 if (!aDirectory.isDirectory()) {
237 throw new IllegalArgumentException("Is not a directory: " + aDirectory);
238 }
239 if (!aDirectory.canRead()) {
240 throw new IllegalArgumentException("Directory cannot be read: " + aDirectory);
241 }
242 }
243
244
245
246 private void validate(JsonValue value) throws SchemaException, URISyntaxException {
247 URI schemaId = value.get(Constants.SCHEMA).as(uri());
248 if (null == schemaId && isEmptyOrBlank(schemaURI)) {
249 System.out.println("-i (--id) must be an URI");
250 return;
251 } else if (null == schemaId) {
252 schemaId = new URI(schemaURI);
253 }
254
255 Validator validator = schemaCache.get(schemaId);
256 if (null != validator) {
257 if (verbose) {
258 final boolean[] valid = new boolean[1];
259 validator.validate(value.getObject(), null, new ErrorHandler() {
260 @Override
261 public void error(ValidationException exception) throws SchemaException {
262 valid[0] = false;
263 printOutException(exception);
264 }
265
266 @Override
267 @Deprecated
268 public void assembleException() throws ValidationException {
269 }
270 });
271 if (valid.length == 0) {
272 System.out.println("OK - Object is valid!");
273 }
274 } else {
275 validator.validate(value.getObject(), null, new FailFastErrorHandler());
276 System.out.println("OK - Object is valid!");
277 }
278 } else {
279 System.out.append("Schema ").append(schemaId.toString()).println(" not found!");
280 }
281 }
282
283 private JsonValue loadFromConsole() throws IOException {
284 System.out.println();
285 System.out.println("> Enter 'exit' and press enter to exit");
286 System.out.println("> Press ctrl-D to finish input");
287 System.out.println("Start data input:");
288 String input = null;
289 StringBuilder stringBuilder = new StringBuilder();
290 Console c = System.console();
291 if (c == null) {
292 System.err.println("No console.");
293 System.exit(1);
294 }
295
296 Scanner scanner = new Scanner(c.reader());
297
298 while (scanner.hasNext()) {
299 input = scanner.next();
300 if (null == input) {
301
302 break;
303 } else if ("exit".equalsIgnoreCase(input)) {
304 System.exit(0);
305 } else {
306 stringBuilder.append(input);
307 }
308 }
309 return new JsonValue(MAPPER.readValue(stringBuilder.toString(), Object.class));
310 }
311
312 private JsonValue loadFromFile() throws IOException {
313 return new JsonValue(MAPPER.readValue(inputFile, Object.class));
314 }
315
316
317 private static boolean isEmptyOrBlank(String str) {
318 return str == null || str.trim().isEmpty();
319 }
320
321 private void printOutException(Exception ex) {
322 String top = "> > > > > > < < < < < <";
323 String exName = ex.getClass().getSimpleName();
324 StringBuilder sb = new StringBuilder(top.substring(0, 40 - (exName.length() / 2))).append(exName);
325 sb.append(top.substring(sb.length()));
326
327 System.out.println(sb);
328 if ((ex instanceof SchemaException) && (null != ((SchemaException) ex).getJsonValue())) {
329 System.out.append("Path: ").println(((SchemaException) ex).getJsonValue().getPointer().toString());
330 }
331 System.out.append("Message: ").println(ex.getMessage());
332 System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
333
334 }
335
336 private Main() {
337
338 }
339 }