View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyrighted [year] [name of copyright owner]".
13   *
14   * Copyright 2011-2016 ForgeRock AS.
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   * Command-line interface to manipulate schemas.
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      // receives other command line parameters than options
82      @Argument
83      private List<String> arguments = new ArrayList<>();
84  
85      /**
86       * Entry point.
87       * @param args The CLI args.
88       * @throws Exception On failure.
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          // if you have a wider console, you could increase the value;
98          // here 80 is also the default
99          parser.setUsageWidth(80);
100 
101         try {
102             // parse the arguments.
103             parser.parseArgument(args);
104 
105             // you can parse additional arguments if you want.
106             // parser.parseArgument("more","args");
107 
108             // after parsing arguments, you should check
109             // if enough arguments are given.
110             /*if (arguments.isEmpty())
111                 throw new CmdLineException(parser, "No argument is given");*/
112 
113         } catch (CmdLineException e) {
114             // if there's a problem in the command line,
115             // you'll get this exception. this will report
116             // an error message.
117             System.err.println(e.getMessage());
118             System.err.println("java Main [options...] arguments...");
119             // print the list of available options
120             parser.printUsage(System.err);
121             System.err.println();
122 
123             // print option sample. This is useful some time
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         // set the base for all relative schema
131         URI base = new URI(schemeBase);
132         if (!base.isAbsolute()) {
133             throw new IllegalArgumentException("-b (-base) must be an absolute URI");
134         }
135 
136         // load all schema
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     //Initialization
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                 //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081
175                 //org.apache.http.client.utils.URIUtils.resolve(URI,URI)
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      * Recursively walk a directory tree and return a List of all
200      * Files found; the List is sorted using File.compareTo().
201      *
202      * @param aStartingDir is a valid directory, which can be read.
203      * @param filter
204      * @return
205      * @throws java.io.FileNotFoundException
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                 //must be a directory
213                 //recursive call!
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      * Directory is valid if it exists, does not represent a file, and can be read.
225      *
226      * @param aDirectory
227      * @throws java.io.FileNotFoundException
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     //Validation
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                 //control-D pressed
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 }