From e3028b84bb3e2b2fe5b9535d8b4954fe01d4d528 Mon Sep 17 00:00:00 2001 From: Antonio Garrote Date: Thu, 4 May 2017 21:51:04 +0100 Subject: [PATCH] Adding Java bindings fixes #65 --- README.md | 5 +- doc/java.md | 136 +++++++++++++ java/src/org/raml/amf/AMF.java | 66 +++++++ java/src/org/raml/amf/core/Model.java | 33 ++++ .../core/document/DeclaresDomainModel.java | 16 ++ .../org/raml/amf/core/document/Document.java | 54 +++++ .../raml/amf/core/document/DocumentModel.java | 187 ++++++++++++++++++ .../amf/core/document/EncodesDomainModel.java | 18 ++ .../org/raml/amf/core/document/Fragment.java | 35 ++++ .../org/raml/amf/core/document/Module.java | 37 ++++ .../amf/core/domain/APIDocumentation.java | 136 +++++++++++++ .../org/raml/amf/core/domain/DomainModel.java | 130 ++++++++++++ .../org/raml/amf/core/domain/EndPoint.java | 82 ++++++++ .../amf/core/domain/GenericOperationUnit.java | 86 ++++++++ .../amf/core/domain/GenericParameter.java | 52 +++++ .../org/raml/amf/core/domain/GenericTag.java | 61 ++++++ java/src/org/raml/amf/core/domain/Header.java | 30 +++ .../org/raml/amf/core/domain/Operation.java | 144 ++++++++++++++ .../org/raml/amf/core/domain/Parameter.java | 47 +++++ .../src/org/raml/amf/core/domain/Payload.java | 58 ++++++ .../src/org/raml/amf/core/domain/Request.java | 59 ++++++ .../org/raml/amf/core/domain/Response.java | 52 +++++ .../org/raml/amf/core/domain/SourceMap.java | 54 +++++ java/src/org/raml/amf/core/domain/Type.java | 54 +++++ .../exceptions/InvalidModelException.java | 15 ++ .../core/exceptions/ResolutionException.java | 19 ++ .../UnknownModelReferenceException.java | 23 +++ .../examples/BasicParsingAndNavigation.java | 110 +++++++++++ .../amf/generators/AMFJSONLDGenerator.java | 14 ++ .../raml/amf/generators/BaseGenerator.java | 102 ++++++++++ .../amf/generators/GenerationException.java | 11 ++ .../amf/generators/GenerationOptions.java | 47 +++++ .../raml/amf/generators/OpenAPIGenerator.java | 14 ++ .../raml/amf/generators/RAMLGenerator.java | 13 ++ .../org/raml/amf/parsers/AMFJSONLDParser.java | 16 ++ java/src/org/raml/amf/parsers/BaseParser.java | 113 +++++++++++ .../org/raml/amf/parsers/OpenAPIParser.java | 15 ++ .../raml/amf/parsers/ParsingException.java | 18 ++ .../org/raml/amf/parsers/ParsingOptions.java | 38 ++++ java/src/org/raml/amf/parsers/RAMLParser.java | 16 ++ java/src/org/raml/amf/utils/Clojure.java | 83 ++++++++ project.clj | 7 +- src/api_modeling_framework/core.cljc | 64 +++++- src/api_modeling_framework/model/domain.cljc | 4 +- 44 files changed, 2366 insertions(+), 8 deletions(-) create mode 100644 doc/java.md create mode 100644 java/src/org/raml/amf/AMF.java create mode 100644 java/src/org/raml/amf/core/Model.java create mode 100644 java/src/org/raml/amf/core/document/DeclaresDomainModel.java create mode 100644 java/src/org/raml/amf/core/document/Document.java create mode 100644 java/src/org/raml/amf/core/document/DocumentModel.java create mode 100644 java/src/org/raml/amf/core/document/EncodesDomainModel.java create mode 100644 java/src/org/raml/amf/core/document/Fragment.java create mode 100644 java/src/org/raml/amf/core/document/Module.java create mode 100644 java/src/org/raml/amf/core/domain/APIDocumentation.java create mode 100644 java/src/org/raml/amf/core/domain/DomainModel.java create mode 100644 java/src/org/raml/amf/core/domain/EndPoint.java create mode 100644 java/src/org/raml/amf/core/domain/GenericOperationUnit.java create mode 100644 java/src/org/raml/amf/core/domain/GenericParameter.java create mode 100644 java/src/org/raml/amf/core/domain/GenericTag.java create mode 100644 java/src/org/raml/amf/core/domain/Header.java create mode 100644 java/src/org/raml/amf/core/domain/Operation.java create mode 100644 java/src/org/raml/amf/core/domain/Parameter.java create mode 100644 java/src/org/raml/amf/core/domain/Payload.java create mode 100644 java/src/org/raml/amf/core/domain/Request.java create mode 100644 java/src/org/raml/amf/core/domain/Response.java create mode 100644 java/src/org/raml/amf/core/domain/SourceMap.java create mode 100644 java/src/org/raml/amf/core/domain/Type.java create mode 100644 java/src/org/raml/amf/core/exceptions/InvalidModelException.java create mode 100644 java/src/org/raml/amf/core/exceptions/ResolutionException.java create mode 100644 java/src/org/raml/amf/core/exceptions/UnknownModelReferenceException.java create mode 100644 java/src/org/raml/amf/examples/BasicParsingAndNavigation.java create mode 100644 java/src/org/raml/amf/generators/AMFJSONLDGenerator.java create mode 100644 java/src/org/raml/amf/generators/BaseGenerator.java create mode 100644 java/src/org/raml/amf/generators/GenerationException.java create mode 100644 java/src/org/raml/amf/generators/GenerationOptions.java create mode 100644 java/src/org/raml/amf/generators/OpenAPIGenerator.java create mode 100644 java/src/org/raml/amf/generators/RAMLGenerator.java create mode 100644 java/src/org/raml/amf/parsers/AMFJSONLDParser.java create mode 100644 java/src/org/raml/amf/parsers/BaseParser.java create mode 100644 java/src/org/raml/amf/parsers/OpenAPIParser.java create mode 100644 java/src/org/raml/amf/parsers/ParsingException.java create mode 100644 java/src/org/raml/amf/parsers/ParsingOptions.java create mode 100644 java/src/org/raml/amf/parsers/RAMLParser.java create mode 100644 java/src/org/raml/amf/utils/Clojure.java diff --git a/README.md b/README.md index 529751a..08435ca 100644 --- a/README.md +++ b/README.md @@ -124,9 +124,12 @@ $ lein jar $ lein npm install # this is only required on the first run $ lein node ``` - The output NPM package will be generated at `output/node`. +### Java Bindings + +The programming guide the Java Bindings can be found [here](doc/java.md). +Javadoc for the bindings can be consulted [here](https://raml-org.github.io/api-modeling-framework/doc/java/apidocs/index.html). ### API Modeling Framework Clojurescript/Web library diff --git a/doc/java.md b/doc/java.md new file mode 100644 index 0000000..b3b1377 --- /dev/null +++ b/doc/java.md @@ -0,0 +1,136 @@ +# AMF Java Programming Guide + +Java wrapping code for the AMF library can be found in the `org.raml.amf` namespace. +This package provides a Java friendly interface on top of the native Clojure code of the library. + +## Compiling + +At this moment we don't provide any artifacts in any repository to use AMF or these Java bindings, so they must be built manually with the help of Leiningen. +In order to build the library, you fist need to build an 'ubejar' with AMF. To accomplish this, from the main AMF project directory run the following instruction: + +``` bash +$ lein uberjar +``` +After Leiningen has finished you should have a standalone jar for AMF and all its dependencies in the `target/api-modeling-framework-0.1.2-SNAPSHOT-standalone.jar` location. Version might be different from the one at the moment of writing this documentation. + +Once the jar for AMF has been generated, we can generate the Java bindings jar. For that change to the `java` directory in the AMF project and use maven: + +``` bash +mvn package +``` + +After Maven has finished you should have an additional jar in the `target/amf-java-0.1.2-SNAPSHOT.jar` location with the Java bindings. Add both jars into your project to use the AMF Java bindings. + +## Parsing + +Parser can be found in the `org.raml.amf.parsers` package of the project. They can be build using the factories in `org.raml.amf.AMF` + +``` java +DocumentModel model = AMF.RAMLParser().parseFile(new URL("http://test.com/worldmusic/api.raml")); +``` + +Parsers are include for RAML, OpenAPI and the JSON-LD serialisation of the AMF model. + +Parsers can accept options, including a hash-map of URLs to local directories that will be used to resolve references in the parsed documents. + +For instance, in the next snippet all remote references to the URLs prefixed by `http://test.com/worldmusic` will be resolved looking into the local directory `/Users/antoniogarrote/world-music-api`. + +``` java +HashMap cacheDirs = new HashMap<>(); +cacheDirs.put("http://test.com/worldmusic","/Users/antoniogarrote/vocabs/world-music-api"); +ParsingOptions options = new ParsingOptions().setCacheDirs(cacheDirs); + +DocumentDocument model = (Document) AMF.RAMLParser().parseFile(new URL("http://test.com/worldmusic/api.raml"), options); +``` +The original parsed text can be retrieved using the `rawText` method. + +## Navigating the Document Model +Parsing will return an instance of a subclass of `DocumentModel` Depending on what is the parsed file, a `Document` a `Fragment` or a `Module` instance will be returned. + +No matter what is the actual Document Model class, the returned model will also include references to all linked references in the model. + +These references can be listed using the `references` method and new instances of `DocumentModel` can be build for these references using the `modelForReference` method: + +``` java +for (URL ref : model.references()) { + DocumentModel refModel = model.modelForReference(ref); + System.out.println("Found a reference model: " + refModel); +} +``` + +## Applying resolution + +To run the resolution algorithm and combine all the documents from the Document Model into a single Domain Model description, the method `resolve` can be invoked. + +``` java +DocumentModel resolvedModel = model.resolve(); +``` + +## Accessing the Domain Model + +The parsed Domain Model can be retrieved from the Document Model instance using the appropriate accessor. + +Fragments returns the encoded Domain Model element using the `encodes` method from the `org.raml.amf.core.document.EncodesDomainModel` interface. +Modules returns the list of declared Domain Model elements using the `declares` method from the `org.raml.amf.core.document.DeclaresDomainModel` interface. +Documents can use both methods to retrieve the top level encoded element and the list of declared elements in the root element. + +``` java +if (model instanceof EncodesDomainModel) { + System.out.println(model.encodes()); +} + +if (targetModel instanceof DeclaresDomainModel) { + List declarations = model.declares(); + for(DomainModel decl : declarations) { + System.out.println(decl); + } +} +``` + +## Navigating and mutating the Domain Model + +The Domain Model includes Java Bean classes for all elements in the AMF Domain Model. +This getters and setters can be used to navigate and mutate the model. Please, refer to the [documentation](https://raml-org.github.io/api-modeling-framework/doc/java/apidocs/index.html) for more details. + +``` java +APIDocumentation api = (APIDocumentation) model.encodes(); + +for (EndPoint endpoint : api.getEndpoints()) { + endpoint.setName("Modified " + endpoint.getName()); +} +``` + +## Serialisation + +AMF includes generators capable of serialising the AMF model back into one of the supported syntaxes. The method `generateString` can be used to generate a String representation and the method `generateFile` can be used to dump the serialised model directly into a file. +Factory methods for each generator can be found in the `org.raml.amf.AMF` class. + + +``` java +// Generating RAML +// Generate can accept just the model +String generated = AMF.RAMLGenerator().generateString(targetModel); +System.out.println(generated); + +// Generating OpenAPI +// It can also accept a destination File/URL for the model +generated = AMF.OpenAPIGenerator().generateString( + new File("world_music.json"), + targetModel +); +System.out.println(generated); + +// Generating JSON-LD +// Finally it can also accept a set of generation options +generated = AMF.JSONLDGenerator().generateString( + new File("world_music.jsonld"), + targetModel, + new GenerationOptions() + .setFullgraph(true) + .setSourceMapGeneration(true)); +System.out.println(generated); +``` + +Two options are available when generating JSON-LD documents. +`setFullGraph` will nest the JSON-LD graphs for the referenced documents in the model to be serialised, otherwise only URIs will be generated. +`setSourceMapGeneration` enables or disables the generation of source maps JSON-LD information in the output. diff --git a/java/src/org/raml/amf/AMF.java b/java/src/org/raml/amf/AMF.java new file mode 100644 index 0000000..d29da76 --- /dev/null +++ b/java/src/org/raml/amf/AMF.java @@ -0,0 +1,66 @@ +package org.raml.amf; + +import org.raml.amf.generators.AMFJSONLDGenerator; +import org.raml.amf.generators.OpenAPIGenerator; +import org.raml.amf.generators.RAMLGenerator; +import org.raml.amf.parsers.AMFJSONLDParser; +import org.raml.amf.parsers.OpenAPIParser; +import org.raml.amf.parsers.RAMLParser; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Facade class providing access to the main IO facilities in the library + */ +public class AMF { + + /** + * Builds a RAML to AMF parser + * @return + */ + public static RAMLParser RAMLParser() { + return new RAMLParser(); + } + + /** + * Builds an OpenAPI to AMF parser + * @return + */ + public static OpenAPIParser OpenAPIParser() { + return new OpenAPIParser(); + } + + /** + * Builds a AMF encoded JSON-LD to AMF parser + * @return + */ + public static AMFJSONLDParser JSONLDParser() { + return new AMFJSONLDParser(); + } + + /** + * Builds a AMF to RAML generator + * @return + */ + public static RAMLGenerator RAMLGenerator() { + return new RAMLGenerator(); + } + + /** + * Builds a AMF to OpenAPI generator + * @return + */ + public static OpenAPIGenerator OpenAPIGenerator() { + return new OpenAPIGenerator(); + } + + /** + * Builds a AMF to JSON-LD generator + * @return + */ + public static AMFJSONLDGenerator JSONLDGenerator() { + return new AMFJSONLDGenerator(); + } +} diff --git a/java/src/org/raml/amf/core/Model.java b/java/src/org/raml/amf/core/Model.java new file mode 100644 index 0000000..cb427c5 --- /dev/null +++ b/java/src/org/raml/amf/core/Model.java @@ -0,0 +1,33 @@ +package org.raml.amf.core; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +/** + * Base class for all AMF parsed models, provides methods to inspect and manipulate the model + */ +public abstract class Model { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + } + + protected Object rawModel; + + protected Model(Object rawModel) { + if (rawModel instanceof Exception) { + throw new InvalidModelException((Exception) rawModel); + } + this.rawModel = rawModel; + } + + /** + * Returns the raw Clojure data structure for this instance data + * @return + */ + public abstract Object clojureModel(); +} diff --git a/java/src/org/raml/amf/core/document/DeclaresDomainModel.java b/java/src/org/raml/amf/core/document/DeclaresDomainModel.java new file mode 100644 index 0000000..4c5d4e8 --- /dev/null +++ b/java/src/org/raml/amf/core/document/DeclaresDomainModel.java @@ -0,0 +1,16 @@ +package org.raml.amf.core.document; + +import org.raml.amf.core.domain.DomainModel; + +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ +public interface DeclaresDomainModel { + /** + * Declared DomainElements that can be re-used from other documents. + * @return List of domain elements. + */ + public List declares(); +} diff --git a/java/src/org/raml/amf/core/document/Document.java b/java/src/org/raml/amf/core/document/Document.java new file mode 100644 index 0000000..4a1fef3 --- /dev/null +++ b/java/src/org/raml/amf/core/document/Document.java @@ -0,0 +1,54 @@ +package org.raml.amf.core.document; + +import clojure.lang.IFn; +import org.raml.amf.core.domain.DomainModel; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * AMF Documents encode the main element of a description in a particular Domain Model + * For example, in RAML/HTTP, the main domain element is an APIDescription. + * + * Since AMF Documents encode Domain elements they behave like Fragments + * AMF Documents can also contains declarations of domain elements to be used in the description of the domain. + * From this point of view Documents also behave like Modules. + */ +public class Document extends DocumentModel implements EncodesDomainModel, DeclaresDomainModel { + public Document(Object rawModel) { + super(rawModel); + } + + /** + * Encoded domain element. It's considered to be the root element of a stand-alone description, not a domain element + * to be re-used and reference + * @return DomainElement encoded in the document. + * @throws InvalidModelException + */ + public DomainModel encodes() throws InvalidModelException { + IFn getFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT, "encodes"); + return DomainModel.fromRawModel(getFn.invoke(this.clojureModel())); + } + + /** + * List of domain elements declared in the document to be referenced in the encoded element. + * They are supposed to be private to the description and not meant to be re-used as in Modules. + * @return + */ + public List declares() { + IFn getFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT, "declares"); + List parsedElements = Clojure.toJavaList((List) getFn.invoke(this.clojureModel())); + ArrayList declared = new ArrayList<>(); + for(Object parsed : parsedElements) { + declared.add(DomainModel.fromRawModel(parsed)); + } + + return declared; + } +} diff --git a/java/src/org/raml/amf/core/document/DocumentModel.java b/java/src/org/raml/amf/core/document/DocumentModel.java new file mode 100644 index 0000000..f4f0d22 --- /dev/null +++ b/java/src/org/raml/amf/core/document/DocumentModel.java @@ -0,0 +1,187 @@ +package org.raml.amf.core.document; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +import api_modeling_framework.model.document.ParsedDocument; +import api_modeling_framework.model.document.ParsedFragment; +import api_modeling_framework.model.document.ParsedModule; +import clojure.lang.IFn; +import org.raml.amf.core.domain.DomainModel; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.core.Model; +import org.raml.amf.core.exceptions.ResolutionException; +import org.raml.amf.core.exceptions.UnknownModelReferenceException; +import org.raml.amf.utils.Clojure; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * AMF Document model that can be used to work with the graph of linked documents generated by the parser. + */ +public abstract class DocumentModel extends Model { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT); + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + DocumentModel(Object rawModel) throws InvalidModelException { + super(rawModel); + } + + private boolean resolved = false; + + /** + * Builds a new Model object for the referenced Document + * @param reference + * @return + * @throws MalformedURLException + * @throws InvalidModelException + * @throws UnknownModelReferenceException + */ + public DocumentModel modelForReference(URL reference) throws MalformedURLException, InvalidModelException, UnknownModelReferenceException { + IFn referenceModelFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "reference-model"); + URL[] refs = this.references(); + for(URL ref : refs) { + if (ref.sameFile(reference)) { + return DocumentModel.fromRawModel(referenceModelFn.invoke(this.rawModel, ref.toString().replace("file:",""))); + } + } + + throw new UnknownModelReferenceException(reference); + } + + + /** + * Returns the list document URIs referenced from the document that has been parsed to generate this model + * @return An array of URI locations for the remote documents + */ + public URL[] references() throws MalformedURLException { + IFn referencesFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "references"); + List references = (List) referencesFn.invoke(this.rawModel); + URL[] acc = new URL[references.size()]; + for (int i=0; i rawText() { + IFn rawFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "raw"); + String text = (String) rawFn.invoke(this.rawModel); + if (text != null) { + return Optional.of(text); + } else { + return Optional.empty(); + } + } + + + protected URL stringToURL(String location) throws MalformedURLException { + if (!location.contains("://")) { + return new URL("file://"+location); + } else { + return new URL(location); + } + } + + + /** + * Returns the native Clojure data structure for the model + * @return Clojure data structure encoding the model + */ + @Override + public Object clojureModel() { + IFn documentModelFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "document-model"); + return documentModelFn.invoke(this.rawModel); + } + + /** + * Factory method building the right wrapper Java DocumentModel subclass for the provided Clojure model data structure + * @param rawModel native Clojure encoded model + * @return The right DocumentModel + * @throws InvalidModelException + */ + public static DocumentModel fromRawModel(Object rawModel) throws InvalidModelException { + IFn unitKindFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "unit-kind"); + String unitKind = (String) unitKindFn.invoke(rawModel); + if (Objects.equals(unitKind, "module")) { + return new Module(rawModel); + } else if (Objects.equals(unitKind, "fragment")) { + return new Fragment(rawModel); + } else if (Objects.equals(unitKind, "document")) { + return new Document(rawModel); + } else { + throw new InvalidModelException(new Exception("Unknown type of document unit " + unitKind)); + } + } + + public DomainModel findDomainElement(String id) { + IFn findFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "find-element"); + Object res = findFn.invoke(this.rawModel, (isResolved() ? "domain": "document"), id); + if (res != null){ + Document doc = (Document) DocumentModel.fromRawModel(res); + return doc.encodes(); + } else { + return null; + } + } + + public boolean isResolved() { + return resolved; + } + + protected void setResolved(boolean resolved) { + this.resolved = resolved; + } +} diff --git a/java/src/org/raml/amf/core/document/EncodesDomainModel.java b/java/src/org/raml/amf/core/document/EncodesDomainModel.java new file mode 100644 index 0000000..f2e03d1 --- /dev/null +++ b/java/src/org/raml/amf/core/document/EncodesDomainModel.java @@ -0,0 +1,18 @@ +package org.raml.amf.core.document; + +import org.raml.amf.core.domain.DomainModel; +import org.raml.amf.core.exceptions.InvalidModelException; + +/** + * Created by antoniogarrote on 04/05/2017. + */ +public interface EncodesDomainModel { + + /** + * Encoded domain element described in the document element. + * @return DomainElement encoded in the document. + * @throws InvalidModelException + */ + public DomainModel encodes() throws InvalidModelException; + +} diff --git a/java/src/org/raml/amf/core/document/Fragment.java b/java/src/org/raml/amf/core/document/Fragment.java new file mode 100644 index 0000000..dea3431 --- /dev/null +++ b/java/src/org/raml/amf/core/document/Fragment.java @@ -0,0 +1,35 @@ +package org.raml.amf.core.document; + +import clojure.lang.IFn; +import clojure.lang.Keyword; +import org.raml.amf.core.domain.DomainModel; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * AMF Fragments encode a single DomainElement that can be referenced and re-used in other documents. + */ +public class Fragment extends DocumentModel implements EncodesDomainModel { + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT); + } + + public Fragment(Object rawModel) { + super(rawModel); + } + + /** + * Encoded Domain element that can referenced from other documents in the DocumentModel + * @return + * @throws InvalidModelException + */ + public DomainModel encodes() throws InvalidModelException { + IFn getFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT, "encodes"); + return DomainModel.fromRawModel(getFn.invoke(this.clojureModel())); + } +} diff --git a/java/src/org/raml/amf/core/document/Module.java b/java/src/org/raml/amf/core/document/Module.java new file mode 100644 index 0000000..135c00a --- /dev/null +++ b/java/src/org/raml/amf/core/document/Module.java @@ -0,0 +1,37 @@ +package org.raml.amf.core.document; + +import clojure.lang.IFn; +import org.raml.amf.core.domain.DomainModel; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * AMF Modules contains collections of DomainElements that can be re-used and referenced from other documents in the + * Documentmodel. + */ +public class Module extends DocumentModel implements DeclaresDomainModel { + public Module(Object rawModel) { + super(rawModel); + } + + /** + * Declared DomainElements that can be re-used from other documents. + * @return List of domain elements. + */ + public List declares() { + IFn getFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT, "declares"); + List parsedElements = Clojure.toJavaList((List) getFn.invoke(this.clojureModel())); + ArrayList declared = new ArrayList<>(); + for(Object parsed : parsedElements) { + declared.add(DomainModel.fromRawModel(parsed)); + } + + return declared; + } +} diff --git a/java/src/org/raml/amf/core/domain/APIDocumentation.java b/java/src/org/raml/amf/core/domain/APIDocumentation.java new file mode 100644 index 0000000..2e1ffb5 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/APIDocumentation.java @@ -0,0 +1,136 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedAPIDocumentation; +import clojure.lang.IFn; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +/** + * Main EntryPoint of the description of HTTP RPC API + */ +public class APIDocumentation extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public APIDocumentation(ParsedAPIDocumentation rawModel) { + super(rawModel); + } + + /** + * Build a new empty API Documentation for the provided URI + * @param id + */ + public APIDocumentation(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedAPIDocumentation" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + public String getTermsOfService() { + return (String) this.wrapped().terms_of_service(); + } + + public void setTermsOfService(String termsOfService) { + this.rawModel = Clojure.setKw(this.rawModel, "terms-of-service", termsOfService); + } + + public String getBasePath() { + Object res = this.wrapped().base_path(); + if (res != null) + return (String) res; + else + return null; + } + + public void setBasePath(String basePath) { + this.rawModel = Clojure.setKw(this.rawModel, "base-path", basePath); + } + + public String getHost() { + Object res = this.wrapped().host(); + if (res != null) + return (String) res; + else + return null; + } + + public void setHost(String host) { + this.rawModel = Clojure.setKw(this.rawModel, "host", host); + } + + /** + * URI Scheme for the paths in the API + * @return + */ + public String getScheme() { + Object res = this.wrapped().scheme(); + if (res != null) + return (String) res; + else + return null; + } + + public void setScheme(String scheme) { + this.rawModel = Clojure.setKw(this.rawModel, "scheme", scheme); + } + + public List getAccepts() { + return (List) this.wrapped().accepts(); + } + + public void setAccepts(List accepts) { + this.rawModel = Clojure.setKw(this.wrapped(), "accepts", Clojure.list(accepts)); + } + + public List getContentTypes() { + return (List) this.wrapped().content_type(); + } + + public void setContentTypes(List contentTypes) { + this.rawModel = Clojure.setKw(this.wrapped(), "content-type", Clojure.list(contentTypes)); + } + + /** + * List of EndPoints declared in this API + * @return + * @throws InvalidModelException + */ + public List getEndpoints() throws InvalidModelException { + List endpoints = (List) this.wrapped().endpoints(); + List tmp = Clojure.toJavaList(endpoints); + ArrayList eps = new ArrayList<>(); + for(api_modeling_framework.model.domain.ParsedEndPoint x : tmp) { + EndPoint parsed = new EndPoint(x); + eps.add(parsed); + } + + return eps; + } + + public void setEndPoints(List operations) { + ArrayList raws = new ArrayList<>(); + for(EndPoint x : operations) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "endpoints", Clojure.list(raws)); + } + + protected ParsedAPIDocumentation wrapped() { + return (ParsedAPIDocumentation) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/DomainModel.java b/java/src/org/raml/amf/core/domain/DomainModel.java new file mode 100644 index 0000000..8390657 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/DomainModel.java @@ -0,0 +1,130 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.document.Node; +import api_modeling_framework.model.document.DocumentSourceMap; +import api_modeling_framework.model.domain.*; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.core.Model; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ +public class DomainModel extends Model { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public DomainModel(Object rawModel) { + super(rawModel); + } + + @Override + public Object clojureModel() { + return this.rawModel; + } + + public static DomainModel fromRawModel(Object rawModel) { + if (rawModel instanceof ParsedAPIDocumentation) { + return new APIDocumentation((ParsedAPIDocumentation) rawModel); + } else if (rawModel instanceof ParsedEndPoint) { + return new EndPoint((ParsedEndPoint) rawModel); + } else if (rawModel instanceof ParsedOperation) { + return new Operation((ParsedOperation) rawModel); + } else if (rawModel instanceof ParsedRequest) { + return new Request((ParsedRequest) rawModel); + } else if (rawModel instanceof ParsedResponse) { + return new Response((ParsedResponse) rawModel); + } else if (rawModel instanceof Payload) { + return new Payload((ParsedPayload) rawModel); + } else if (rawModel instanceof ParsedType) { + return new Type((ParsedType) rawModel); + } else { + throw new InvalidModelException(new Exception("Unknown DomainModel class " + rawModel)); + } + } + + public String getId() { + return (String) wrappedNode().id(); + } + + public String getName() { + return (String) wrappedNode().name(); + } + + public void setName(String name) { + this.rawModel = Clojure.setKw(this.rawModel, "name", name); + } + + public Boolean getAbstract() { + return (Boolean) Clojure.getKw(this.rawModel, "abstract") || false; + } + + public void setAbstract(Boolean abstractBool) { + this.rawModel = Clojure.setKw(this.rawModel, "abstract", abstractBool); + } + + public String getDescription() { + return (String) wrappedNode().name(); + } + + public void setDescription(String description) { + this.rawModel = Clojure.setKw(this.rawModel, "description", description); + } + + public List getSourceMaps() { + List operations = (List) this.wrappedNode().sources(); + List tmp = Clojure.toJavaList(operations); + ArrayList eps = new ArrayList<>(); + for(DocumentSourceMap x : tmp) { + SourceMap parsed = new SourceMap(x); + eps.add(parsed); + } + + return eps; + } + + public void setSourceMaps(List sourceMaps) { + ArrayList raws = new ArrayList<>(); + for(SourceMap x : sourceMaps) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "sources", Clojure.list(raws)); + } + + public List getExtends() { + List operations = (List) Clojure.getKw(this.rawModel, "extends"); + List tmp = Clojure.toJavaList(operations); + ArrayList eps = new ArrayList<>(); + for(Object x : tmp) { + eps.add(DomainModel.fromRawModel(x)); + } + + return eps; + } + + public void setExtends(List toExtend) { + ArrayList raws = new ArrayList<>(); + for(DomainModel x : toExtend) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "extends", Clojure.list(raws)); + } + + protected void setId(String id) { + this.rawModel = Clojure.setKw(this.rawModel, "id", id); + } + protected Node wrappedNode() { + return (Node) this.rawModel; + } + + public String toString() { + return (this.getId() + " :: " + super.toString()); + } +} diff --git a/java/src/org/raml/amf/core/domain/EndPoint.java b/java/src/org/raml/amf/core/domain/EndPoint.java new file mode 100644 index 0000000..08deb2a --- /dev/null +++ b/java/src/org/raml/amf/core/domain/EndPoint.java @@ -0,0 +1,82 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedEndPoint; +import api_modeling_framework.model.domain.ParsedOperation; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * EndPoints contains information about a HTTP remote location where a number of API operations have been bound + */ +public class EndPoint extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public EndPoint(ParsedEndPoint rawModel) { + super(rawModel); + } + + /** + * Builds a new EndPoint for the provided URI + * @param id + */ + public EndPoint(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedEndPoint" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + protected ParsedEndPoint wrapped() { + return (ParsedEndPoint) rawModel; + } + + /** + * Path for the URL where the operations of the EndPoint are bound + * @return + */ + public String getPath() { + return (String) this.wrapped().path(); + } + + public void setPath(String path) { + this.rawModel = Clojure.setKw(this.wrapped(), "path", path); + } + + /** + * List of API Operations bound to this EndPoint + * @return + */ + public List getSupportedOperations() { + List operations = (List) this.wrapped().supported_operations(); + List tmp = Clojure.toJavaList(operations); + ArrayList eps = new ArrayList<>(); + for(api_modeling_framework.model.domain.ParsedOperation x : tmp) { + Operation parsed = new Operation(x); + eps.add(parsed); + } + + return eps; + } + + public void setSupportedOperations(List operations) { + ArrayList raws = new ArrayList<>(); + for(Operation x : operations) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "supported-operations", Clojure.list(raws)); + } + +} diff --git a/java/src/org/raml/amf/core/domain/GenericOperationUnit.java b/java/src/org/raml/amf/core/domain/GenericOperationUnit.java new file mode 100644 index 0000000..d148bdc --- /dev/null +++ b/java/src/org/raml/amf/core/domain/GenericOperationUnit.java @@ -0,0 +1,86 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.HeadersHolder; +import api_modeling_framework.model.domain.PayloadHolder; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Base class for the Request and Response of an API + */ +public abstract class GenericOperationUnit extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public GenericOperationUnit(Object rawModel) { + super(rawModel); + } + + /** + * List of HTTP headers in this unit + * @return + */ + public List
getHeaders() { + List headers = (List) this.headersHolder().headers(); + List tmp = Clojure.toJavaList(headers); + ArrayList
eps = new ArrayList<>(); + for(api_modeling_framework.model.domain.ParsedParameter x : tmp) { + Header parsed = new Header(x); + eps.add(parsed); + } + + return eps; + } + + public void setHeaders(List
headers) { + ArrayList raws = new ArrayList<>(); + for(Header x : headers) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "headers", Clojure.list(raws)); + } + + /** + * List of Payloads in the unit + * @return + */ + public List getPayloads() { + List payloads = (List) this.payloadHolder().payloads(); + List tmp = Clojure.toJavaList(payloads); + ArrayList eps = new ArrayList<>(); + for(api_modeling_framework.model.domain.ParsedPayload x : tmp) { + Payload parsed = new Payload(x); + eps.add(parsed); + } + + return eps; + } + + public void setPayloads(List payloads) { + ArrayList raws = new ArrayList<>(); + for(Payload x : payloads) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "payloads", Clojure.list(raws)); + } + + + private HeadersHolder headersHolder() { + return (HeadersHolder) this.rawModel; + } + + private PayloadHolder payloadHolder() { + return (PayloadHolder) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/GenericParameter.java b/java/src/org/raml/amf/core/domain/GenericParameter.java new file mode 100644 index 0000000..21b6969 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/GenericParameter.java @@ -0,0 +1,52 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedParameter; +import api_modeling_framework.model.domain.ParsedType; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +/** + * Parameters include all kind of input or output information that is requested or returned by an API Operation that + * are not encoded in the Request or Response payloads. + * Parameters can be located in HTTP headers or in the domain, path or arguments of the request URL. + */ +public abstract class GenericParameter extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public GenericParameter(ParsedParameter rawModel) { + super(rawModel); + } + + public Boolean getRequired() { + return (Boolean) this.wrapped().required(); + } + + public void setRequired(Boolean required) { + this.rawModel = Clojure.setKw(this.rawModel, "required", required); + } + + public Type getSchema() { + ParsedType type = (ParsedType) this.wrapped().shape(); + return new Type(type); + } + + protected void setParameterKindInternal(String parameterKind) { + this.rawModel = Clojure.setKw(this.rawModel, "parameter-kind", parameterKind); + } + + public void setSchema(Type type) { + this.rawModel = Clojure.setKw(this.wrapped(), "shape", type.clojureModel()); + } + + protected ParsedParameter wrapped() { + return (ParsedParameter) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/GenericTag.java b/java/src/org/raml/amf/core/domain/GenericTag.java new file mode 100644 index 0000000..58378d5 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/GenericTag.java @@ -0,0 +1,61 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.document.Tag; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Tag included in a SourceMap + * Tags are tuples with an identifier, describing the kind of information in the mapping and an arbitrary value. + * Tags are also elements of the model, so they also have an associated URI. + */ +public class GenericTag extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public GenericTag(Object rawModel) { + super(rawModel); + } + + public GenericTag(String id, Object value) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT, + "api-modeling-framework.model.document/->APITagTag" + ).invoke(id, value) + ); + } + + public GenericTag(String id, String tagId, Object value) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOCUMENT, + "api-modeling-framework.model.document/->APITagTag" + ).invoke(id, value) + ); + this.setTagId(tagId); + } + + public String getTagId() { + return (String) this.wrapped().tag_id(); + } + + public void setTagId(String tagId) { + this.rawModel = Clojure.setKw(this.rawModel, "tag-id", tagId); + } + + public Object getValue() { + return this.wrapped().value(); + } + + public void setValue(Object value) { + this.rawModel = Clojure.setKw(this.rawModel, "value", value); + } + + protected Tag wrapped() { + return (Tag) rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/Header.java b/java/src/org/raml/amf/core/domain/Header.java new file mode 100644 index 0000000..5ec9300 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Header.java @@ -0,0 +1,30 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedParameter; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +public class Header extends GenericParameter { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Header(ParsedParameter rawModel) { + super(rawModel); + } + + public Header(String id) { + super((ParsedParameter) Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedParameter" + ).invoke(Clojure.map()) + ); + this.setId(id); + this.setParameterKindInternal("header"); + } +} diff --git a/java/src/org/raml/amf/core/domain/Operation.java b/java/src/org/raml/amf/core/domain/Operation.java new file mode 100644 index 0000000..e09f36a --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Operation.java @@ -0,0 +1,144 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedOperation; +import api_modeling_framework.model.domain.ParsedRequest; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * A unit of business logic exposed by the API. Operations can be invoked using the associated HTTP method. + */ +public class Operation extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Operation(api_modeling_framework.model.domain.ParsedOperation rawModel) throws InvalidModelException { + super(rawModel); + } + + /** + * Builds a new empty Operation with the provide URI + * @param id + */ + public Operation(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedOperation" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + /** + * HTTP method that must be used to invoke the Operation + * @return + */ + public String getMethod() { + Object res = this.wrapped().method(); + if (res != null) { + return (String) res; + } else { + return null; + } + } + + public void setMethod(String method) { + this.rawModel = Clojure.setKw(this.wrapped(), "method", method); + } + + /** + * HTTP scheme that must be used to invoke the operation, overrides the default in APIDocumentation + * @return + */ + public String getScheme() { + Object res = this.wrapped().scheme(); + if (res != null) { + return (String) res; + } else { + return null; + } + } + + public void setScheme(String scheme) { + this.rawModel = Clojure.setKw(this.rawModel, "scheme", scheme); + } + + /** + * HTTP media-types accepted by the operation, overrides the default in APIDocumentation + */ + public List getAccepts() { + return (List) this.wrapped().accepts(); + } + + public void setAccepts(List accepts) { + this.rawModel = Clojure.setKw(this.wrapped(), "accepts", Clojure.list(accepts)); + } + + /** + * HTTP media-types returned by the operation, overrides the default in APIDocumentation + */ + public List getContentTypes() { + return (List) this.wrapped().content_type(); + } + + public void setContentTypes(List contentTypes) { + this.rawModel = Clojure.setKw(this.wrapped(), "content-type", Clojure.list(contentTypes)); + } + + /** + * List of responses for different HTTP status codes supported by this operation + * @return + */ + public List getResponses() { + List responses = (List) this.wrapped().responses(); + List tmp = Clojure.toJavaList(responses); + ArrayList eps = new ArrayList<>(); + for(api_modeling_framework.model.domain.ParsedResponse x : tmp) { + Response parsed = new Response(x); + eps.add(parsed); + } + + return eps; + } + + public void setResponses(List responses) { + ArrayList raws = new ArrayList<>(); + for(Operation x : responses) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.wrapped(), "responses", Clojure.list(raws)); + } + + /** + * Request information for the operation + * @return + */ + public Request getRequest() { + Object res = this.wrapped().request(); + if (res != null) { + ParsedRequest request = (ParsedRequest) res; + return new Request(request); + } else { + return null; + } + } + + public void setRequest(Request request) { + this.rawModel = Clojure.setKw(this.wrapped(), "request", request.clojureModel()); + } + + private ParsedOperation wrapped() { + return (ParsedOperation) this.rawModel; + } + +} diff --git a/java/src/org/raml/amf/core/domain/Parameter.java b/java/src/org/raml/amf/core/domain/Parameter.java new file mode 100644 index 0000000..e74cbef --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Parameter.java @@ -0,0 +1,47 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedParameter; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ +public class Parameter extends GenericParameter { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Parameter(ParsedParameter rawModel) { + super(rawModel); + } + + public Parameter(String id, String parameterKind) { + super((ParsedParameter) Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedParameter" + ).invoke(Clojure.map()) + ); + this.setId(id); + this.setParameterKindInternal(parameterKind); + } + + protected void setParameterKind(String parameterKind) { + setParameterKindInternal(parameterKind); + } + + public String getParameterKind() { + Object res = this.wrapped().parameter_kind(); + if (res != null) { + return (String) res; + } else { + return null; + } + } + + + protected ParsedParameter wrapped() { + return (ParsedParameter) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/Payload.java b/java/src/org/raml/amf/core/domain/Payload.java new file mode 100644 index 0000000..4e840c2 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Payload.java @@ -0,0 +1,58 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedPayload; +import api_modeling_framework.model.domain.ParsedType; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Schema information for a Payload associated to a particular media-type + */ +public class Payload extends DomainModel { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Payload(ParsedPayload rawModel) { + super(rawModel); + } + + public Payload(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedPayload" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + public String getMediaType() { + return (String) this.wrapped().media_type(); + } + + public void setMediaType(String mediaType) { + this.rawModel = Clojure.setKw(this.rawModel, "media-type", mediaType); + } + + /** + * Schema information for the payload + * @return + */ + public Type getSchema() { + ParsedType type = (ParsedType) this.wrapped().schema(); + return new Type(type); + } + + public void setSchema(Type type) { + this.rawModel = Clojure.setKw(this.wrapped(), "schema", type.clojureModel()); + } + + private ParsedPayload wrapped() { + return (ParsedPayload) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/Request.java b/java/src/org/raml/amf/core/domain/Request.java new file mode 100644 index 0000000..a58ce6e --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Request.java @@ -0,0 +1,59 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedRequest; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +public class Request extends GenericOperationUnit { + + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Request(ParsedRequest request) throws InvalidModelException { + super(request); + } + + public Request(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedRequest" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + public List getParameters() { + List parameters = (List) this.wrapped().parameters(); + List tmp = Clojure.toJavaList(parameters); + ArrayList eps = new ArrayList<>(); + for(api_modeling_framework.model.domain.ParsedParameter x : tmp) { + Parameter parsed = new Parameter(x); + eps.add(parsed); + } + + return eps; + } + + public void setParameters(List parameters) { + ArrayList raws = new ArrayList<>(); + for(Parameter x : parameters) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "parameters", Clojure.list(raws)); + } + + private ParsedRequest wrapped() { + return (ParsedRequest) this.rawModel; + } + +} diff --git a/java/src/org/raml/amf/core/domain/Response.java b/java/src/org/raml/amf/core/domain/Response.java new file mode 100644 index 0000000..21f3c8e --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Response.java @@ -0,0 +1,52 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedResponse; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +/** + * Information about the response returned by an operation, associated to a particular status + */ +public class Response extends GenericOperationUnit { + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Response(ParsedResponse rawModel) { + super(rawModel); + } + + public Response(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedResponse" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + /** + * Status code for the response + * @return + */ + public String getStatusCode() { + String code = (String) this.wrapped().status_code(); + if (code != null) + return code; + else + return null; + } + + public void setStatusCode(String status) { + this.rawModel = Clojure.setKw(this.wrapped(), "status-code", status); + } + + private ParsedResponse wrapped() { + return (ParsedResponse) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/domain/SourceMap.java b/java/src/org/raml/amf/core/domain/SourceMap.java new file mode 100644 index 0000000..2b4a769 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/SourceMap.java @@ -0,0 +1,54 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.document.DocumentSourceMap; +import api_modeling_framework.model.document.Tag; +import org.raml.amf.utils.Clojure; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +public class SourceMap extends DomainModel { + + public SourceMap(DocumentSourceMap rawModel) { + super(rawModel); + } + + protected DocumentSourceMap wrapped() { + return (DocumentSourceMap) this.rawModel; + } + + public String getSource() { + return (String) this.wrapped().source(); + } + + public void setSource(String source) { + this.rawModel = Clojure.setKw(this.rawModel, "source", source); + } + + public List getTags() { + List operations = (List) this.wrapped().tags(); + List tmp = Clojure.toJavaList(operations); + ArrayList eps = new ArrayList<>(); + for(Tag x : tmp) { + GenericTag parsed = new GenericTag(x); + eps.add(parsed); + } + + return eps; + } + + public void setTags(List tags) { + ArrayList raws = new ArrayList<>(); + for(GenericTag x : tags) { + raws.add(x.clojureModel()); + } + + this.rawModel = Clojure.setKw(this.rawModel, "tags", Clojure.list(raws)); + } + +} diff --git a/java/src/org/raml/amf/core/domain/Type.java b/java/src/org/raml/amf/core/domain/Type.java new file mode 100644 index 0000000..3ad54c6 --- /dev/null +++ b/java/src/org/raml/amf/core/domain/Type.java @@ -0,0 +1,54 @@ +package org.raml.amf.core.domain; + +import api_modeling_framework.model.domain.ParsedType; +import clojure.lang.IFn; +import org.raml.amf.utils.Clojure; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Data Shape that describing a set of constraints over an operation unit payload + */ +public class Type extends DomainModel { + static { + Clojure.require(Clojure.CHESHIRE_CORE); + Clojure.require(Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN); + } + + public Type(Object rawModel) { + super(rawModel); + } + + public Type(String id) { + super(Clojure.var( + Clojure.API_MODELING_FRAMEWORK_MODEL_DOMAIN, + "api-modeling-framework.model.domain/map->ParsedType" + ).invoke(Clojure.map()) + ); + this.setId(id); + } + + /** + * JSON-LD string containing a SHACL shape that can be used to validate payloads for this operation unit + * @return + */ + public String getShape() { + IFn generateStringFn = Clojure.var(Clojure.CHESHIRE_CORE, "generate-string"); + return (String) generateStringFn.invoke(this.wrapped().shape()); + } + + /** + * Sets the SHACL shape for the payloads of this operation unit + * @param shaclShape valid SHACL shape encoded as JSON-LD string + */ + public void setShape(String shaclShape) { + IFn parseStringFn = Clojure.var(Clojure.CHESHIRE_CORE, "parse-string"); + this.rawModel = Clojure.setKw(this.rawModel, "shape", parseStringFn.invoke(shaclShape)); + } + + public ParsedType wrapped() { + return (ParsedType) this.rawModel; + } +} diff --git a/java/src/org/raml/amf/core/exceptions/InvalidModelException.java b/java/src/org/raml/amf/core/exceptions/InvalidModelException.java new file mode 100644 index 0000000..da99ed7 --- /dev/null +++ b/java/src/org/raml/amf/core/exceptions/InvalidModelException.java @@ -0,0 +1,15 @@ +package org.raml.amf.core.exceptions; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Exception related to a native clojure model that is not matching the expected value + */ +public class InvalidModelException extends RuntimeException { + + public InvalidModelException(Exception ex) { + super(ex); + } +} diff --git a/java/src/org/raml/amf/core/exceptions/ResolutionException.java b/java/src/org/raml/amf/core/exceptions/ResolutionException.java new file mode 100644 index 0000000..e54e660 --- /dev/null +++ b/java/src/org/raml/amf/core/exceptions/ResolutionException.java @@ -0,0 +1,19 @@ +package org.raml.amf.core.exceptions; + +import org.raml.amf.core.document.DocumentModel; + +/** + * Created by antoniogarrote on 04/05/2017. + */ +public class ResolutionException extends Exception { + private final DocumentModel model; + + public ResolutionException(DocumentModel model, Exception ex) { + super("Error resolving model", ex); + this.model = model; + } + + public DocumentModel getModel() { + return model; + } +} diff --git a/java/src/org/raml/amf/core/exceptions/UnknownModelReferenceException.java b/java/src/org/raml/amf/core/exceptions/UnknownModelReferenceException.java new file mode 100644 index 0000000..941c6de --- /dev/null +++ b/java/src/org/raml/amf/core/exceptions/UnknownModelReferenceException.java @@ -0,0 +1,23 @@ +package org.raml.amf.core.exceptions; + +import java.net.URL; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Exception due to a reference to an unknown model + */ +public class UnknownModelReferenceException extends Exception { + private URL uknownReference; + + public UnknownModelReferenceException(URL reference) { + super("Cannot find model with reference " + reference.toString()); + this.uknownReference = reference; + } + + public URL getUknownReference() { + return uknownReference; + } +} diff --git a/java/src/org/raml/amf/examples/BasicParsingAndNavigation.java b/java/src/org/raml/amf/examples/BasicParsingAndNavigation.java new file mode 100644 index 0000000..e7b6365 --- /dev/null +++ b/java/src/org/raml/amf/examples/BasicParsingAndNavigation.java @@ -0,0 +1,110 @@ +package org.raml.amf.examples; + +import org.raml.amf.AMF; +import org.raml.amf.core.document.Document; +import org.raml.amf.core.document.DocumentModel; +import org.raml.amf.core.document.EncodesDomainModel; +import org.raml.amf.core.document.Module; +import org.raml.amf.core.domain.APIDocumentation; +import org.raml.amf.core.domain.DomainModel; +import org.raml.amf.core.domain.EndPoint; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.core.exceptions.ResolutionException; +import org.raml.amf.core.exceptions.UnknownModelReferenceException; +import org.raml.amf.generators.*; +import org.raml.amf.parsers.ParsingException; +import org.raml.amf.parsers.ParsingOptions; +import org.raml.amf.parsers.RAMLParser; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ +public class BasicParsingAndNavigation { + + public static void main(String[] args) throws MalformedURLException, ParsingException, InvalidModelException, UnknownModelReferenceException, GenerationException, ResolutionException { + + URL toParse = new URL("file:///Users/antoniogarrote/Development/api-modelling-framework/resources/other-examples/world-music-api/api.raml"); + // Model model = new RAMLParser().parseFile(); + HashMap cacheDirs = new HashMap<>(); + cacheDirs.put("http://test.com/something","/Users/antoniogarrote/Development/api-modelling-framework/resources/other-examples/world-music-api"); + ParsingOptions options = new ParsingOptions().setCacheDirs(cacheDirs); + Document model = (Document) AMF.RAMLParser().parseFile(new URL("http://test.com/something/api.raml"), options); + System.out.println("GOT A MODEL"); + System.out.println(model); + + System.out.println("LOCATION: " + model.location()); + for (URL ref : model.references()) { + System.out.println("REFERENCE: " + ref); + } + + + toParse = new URL("file:///Users/antoniogarrote/Development/api-modelling-framework/resources/other-examples/world-music-api/api.raml"); + model = (Document) new RAMLParser().parseFile(toParse); + System.out.println("GOT A MODEL"); + System.out.println(model); + + DocumentModel resolvedModel = model.resolve(); + System.out.println("RESOLVED"); + System.out.println(AMF.RAMLGenerator().generateString(resolvedModel)); + + System.out.println("LOCATION: " + model.location()); + for (URL ref : model.references()) { + System.out.println("REFERENCE: " + ref); + } + + System.out.println(model.rawText().get()); + + URL targetRef = model.references()[1]; + System.out.println("TARGETTING " + targetRef); + DocumentModel targetModel = model.modelForReference(targetRef); + System.out.println("TARGET LOCATION: " + targetModel.location()); + System.out.println("TARGET MODEL CLASS " + targetModel.getClass()); + List declarations = ((Module) targetModel).declares(); + System.out.println("DECLARATIONS:"); + for(DomainModel decl : declarations) { + System.out.println(decl); + } + + Object foundModel = targetModel.findDomainElement("/Users/antoniogarrote/Development/api-modelling-framework/resources/other-examples/world-music-api/libraries/api.lib.raml#/definitions/Cat"); + System.out.println("FOUND?"); + System.out.println(foundModel); + + APIDocumentation api = (APIDocumentation) model.encodes(); + + System.out.println("API DOCUMENTATION " + api); + + System.out.println("ENDPOINTS " + api.getEndpoints()); + for (EndPoint endpoint : api.getEndpoints()) { + endpoint.setName("Modified " + endpoint.getName()); + System.out.println(endpoint.getName()); + } + + String generated = AMF.OpenAPIGenerator().generateString(targetModel); + System.out.println("GENERATED OpenAPI"); + System.out.println(generated); + + generated = AMF.RAMLGenerator().generateString( + new File("world_music.raml"), + targetModel + ); + System.out.println("GENERATED RAML"); + System.out.println(generated); + + generated = AMF.JSONLDGenerator().generateString( + new File("world_music.jsonld"), + targetModel, + new GenerationOptions() + .setFullgraph(true) + .setSourceMapGeneration(true) + ); + System.out.println("GENERATED JSONLD"); + System.out.println(generated); + } +} diff --git a/java/src/org/raml/amf/generators/AMFJSONLDGenerator.java b/java/src/org/raml/amf/generators/AMFJSONLDGenerator.java new file mode 100644 index 0000000..58fabc2 --- /dev/null +++ b/java/src/org/raml/amf/generators/AMFJSONLDGenerator.java @@ -0,0 +1,14 @@ +package org.raml.amf.generators; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +public class AMFJSONLDGenerator extends BaseGenerator { + + @Override + protected String generatorConstructor() { + return "->APIModelGenerator"; + } +} diff --git a/java/src/org/raml/amf/generators/BaseGenerator.java b/java/src/org/raml/amf/generators/BaseGenerator.java new file mode 100644 index 0000000..c6540d9 --- /dev/null +++ b/java/src/org/raml/amf/generators/BaseGenerator.java @@ -0,0 +1,102 @@ +package org.raml.amf.generators; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +import clojure.lang.IFn; +import org.raml.amf.core.document.DocumentModel; +import org.raml.amf.utils.Clojure; + +import java.io.File; +import java.net.MalformedURLException; + +/** + * Basic interface for all AMF generators. It allows to generate syntax files out of AMF Document Models. + */ +public abstract class BaseGenerator { + static { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + } + + /** + * Serialises the model and stores it in the provided file path and options + * @param path Path where the model will be serialised + * @param model DocumentModel to be serialised + */ + public void generateFile(File path, DocumentModel model, GenerationOptions options) throws GenerationException { + String location = path.getAbsolutePath().toString(); + IFn parserFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, generatorConstructor()); + Object generator = parserFn.invoke(); + IFn parseFileSync = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "generate-file-sync"); + try { + Object result = parseFileSync.invoke(generator, location, model.clojureModel(), options.build()); + if (result instanceof Exception) { + throw new GenerationException((Exception) result); + } + + } catch (RuntimeException e) { + throw new GenerationException(e); + } + } + + /** + * Serialises the model and stores it in the provided file path. + * @param path + * @param model + * @throws GenerationException + */ + public void generateFile(File path, DocumentModel model) throws GenerationException { + generateFile(path, model, new GenerationOptions()); + } + + /** + * Serialises the model and uses the provided file path as the default model location, applying the provided options + * @param path Path where the model will be serialised + * @param model DocumentModel to be serialised + */ + public String generateString(File path, DocumentModel model, GenerationOptions options) throws GenerationException { + return generateStringInternal(path.getAbsolutePath().toString(), model, options); + } + + /** + * Serialises the model and stores it in the using the privded file path as the model location + * @param path + * @param model + * @throws GenerationException + */ + public String generateString(File path, DocumentModel model) throws GenerationException { + return generateString(path, model, new GenerationOptions()); + } + + /** + * Serialises the model using the default location stored in the model + * @param model + * @throws GenerationException + */ + public String generateString(DocumentModel model) throws GenerationException { + try { + return generateStringInternal(model.location().toString(), model, new GenerationOptions()); + } catch (MalformedURLException ex) { + throw new GenerationException(ex); + } + } + + protected String generateStringInternal(String location, DocumentModel model, GenerationOptions options) throws GenerationException { + IFn parserFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, generatorConstructor()); + Object generator = parserFn.invoke(); + IFn parseFileSync = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "generate-string-sync"); + try { + Object result = parseFileSync.invoke(generator, location, model.clojureModel(), options.build()); + if (result instanceof Exception) { + throw new GenerationException((Exception) result); + } else { + return (String) result; + } + + } catch (RuntimeException e) { + throw new GenerationException(e); + } + } + protected abstract String generatorConstructor(); +} diff --git a/java/src/org/raml/amf/generators/GenerationException.java b/java/src/org/raml/amf/generators/GenerationException.java new file mode 100644 index 0000000..3817d26 --- /dev/null +++ b/java/src/org/raml/amf/generators/GenerationException.java @@ -0,0 +1,11 @@ +package org.raml.amf.generators; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +public class GenerationException extends Throwable { + public GenerationException(Exception rawModel) { + super(rawModel); + } +} diff --git a/java/src/org/raml/amf/generators/GenerationOptions.java b/java/src/org/raml/amf/generators/GenerationOptions.java new file mode 100644 index 0000000..440e64c --- /dev/null +++ b/java/src/org/raml/amf/generators/GenerationOptions.java @@ -0,0 +1,47 @@ +package org.raml.amf.generators; + +import clojure.lang.IPersistentMap; +import clojure.lang.PersistentHashMap; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +public class GenerationOptions { + + private Boolean generateSourceMaps; + private Boolean generateFullGraph; + + /** + * When serialising to JSON-LD, enables or disables the generation of source-maps + * @param shouldGenerate + */ + public GenerationOptions setSourceMapGeneration(Boolean shouldGenerate) { + this.generateSourceMaps = shouldGenerate; + return this; + } + + /** + * When serialising into JSON-LD, if set to true, all the JSON-LD RDF graph for referenced documents will be nested inside + * the JSON-LD document of the model. + * If set to false, only the URI will be serialised + * @param shouldGenerateFullGraph + */ + public GenerationOptions setFullgraph(Boolean shouldGenerateFullGraph) { + this.generateFullGraph = shouldGenerateFullGraph; + return this; + } + + public IPersistentMap build() { + IPersistentMap options = PersistentHashMap.EMPTY; + if (this.generateSourceMaps != null) { + options = options.assoc("source-maps?", this.generateSourceMaps); + } + if (this.generateFullGraph != null) { + options = options.assoc("full-graph?", this.generateSourceMaps); + } + + return options; + } +} diff --git a/java/src/org/raml/amf/generators/OpenAPIGenerator.java b/java/src/org/raml/amf/generators/OpenAPIGenerator.java new file mode 100644 index 0000000..97973fa --- /dev/null +++ b/java/src/org/raml/amf/generators/OpenAPIGenerator.java @@ -0,0 +1,14 @@ +package org.raml.amf.generators; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + + +public class OpenAPIGenerator extends BaseGenerator { + + @Override + protected String generatorConstructor() { + return "->OpenAPIGenerator"; + } +} diff --git a/java/src/org/raml/amf/generators/RAMLGenerator.java b/java/src/org/raml/amf/generators/RAMLGenerator.java new file mode 100644 index 0000000..5ade444 --- /dev/null +++ b/java/src/org/raml/amf/generators/RAMLGenerator.java @@ -0,0 +1,13 @@ +package org.raml.amf.generators; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +public class RAMLGenerator extends BaseGenerator { + + @Override + protected String generatorConstructor() { + return "->RAMLGenerator"; + } +} diff --git a/java/src/org/raml/amf/parsers/AMFJSONLDParser.java b/java/src/org/raml/amf/parsers/AMFJSONLDParser.java new file mode 100644 index 0000000..c87d937 --- /dev/null +++ b/java/src/org/raml/amf/parsers/AMFJSONLDParser.java @@ -0,0 +1,16 @@ +package org.raml.amf.parsers; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Wrapper class for the AMF OWL model parser, processes AMF model specification JSON-LD documents and generate the DocumentModel out of them + */ +public class AMFJSONLDParser extends BaseParser { + + @Override + protected String parserConstructor() { + return "->APIModelParser"; + } +} diff --git a/java/src/org/raml/amf/parsers/BaseParser.java b/java/src/org/raml/amf/parsers/BaseParser.java new file mode 100644 index 0000000..9267652 --- /dev/null +++ b/java/src/org/raml/amf/parsers/BaseParser.java @@ -0,0 +1,113 @@ +package org.raml.amf.parsers; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +import clojure.lang.IFn; +import org.raml.amf.core.document.DocumentModel; +import org.raml.amf.core.exceptions.InvalidModelException; +import org.raml.amf.utils.Clojure; + +import java.net.URL; + +/** + * Basic interface for all AMF parsers. It allows to parse syntax files and syntax text and generate the AMF Model out + * of it. + */ +public abstract class BaseParser { + + /** + * Generates a model parsing the file referenced by the provided URL. + * @param url Local or remote URL + * @return The parsed model + * @throws ParsingException + */ + public DocumentModel parseFile(URL url) throws ParsingException, InvalidModelException { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + IFn parserFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, parserConstructor()); + Object parser = parserFn.invoke(); + IFn parseFileSync = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "parse-file-sync"); + try { + Object rawModel = parseFileSync.invoke(parser, url.toString().replace("file:","")); + if (rawModel instanceof Exception) { + throw new ParsingException((Exception) rawModel); + } + return DocumentModel.fromRawModel(rawModel); + } catch (RuntimeException e) { + throw new ParsingException(e); + } + } + + /** + * Generates a model parsing the file referenced by the provided URL and parsing options. + * @param url Local or remote URL + * @param options Parsing options + * @return The parsed model + * @throws ParsingException + */ + public DocumentModel parseFile(URL url, ParsingOptions options) throws ParsingException, InvalidModelException { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + IFn parserFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, parserConstructor()); + Object parser = parserFn.invoke(); + IFn parseFileSync = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "parse-file-sync"); + try { + Object rawModel = parseFileSync.invoke(parser, url.toString().replace("file:",""), options.build()); + if (rawModel instanceof Exception) { + throw new ParsingException((Exception) rawModel); + } + return DocumentModel.fromRawModel(rawModel); + } catch (RuntimeException e) { + throw new ParsingException(e); + } + } + + protected abstract String parserConstructor(); + + /** + * Generates a model parsing the provided textual input syntax. + * @param text Input syntax to parse + * @param url Base URL for the document being parsed. It will be the base URL for the inclusions in this file + * @return The parsed Model + * @throws ParsingException + */ + public DocumentModel parseString(String text, URL url) throws ParsingException { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + IFn parserFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, parserConstructor()); + Object parser = parserFn.invoke(); + IFn parseFileSync = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "parse-string-sync"); + try { + Object rawModel = parseFileSync.invoke(parser, url.toString().replace("file:",""), text); + if (rawModel instanceof Exception) { + throw new ParsingException((Exception) rawModel); + } + return DocumentModel.fromRawModel(rawModel); + } catch (RuntimeException e) { + throw new ParsingException(e); + } + } + + /** + * Generates a model parsing the provided textual input syntax and parsing options. + * @param text Input syntax to parse + * @param url Base URL for the document being parsed. It will be the base URL for the inclusions in this file + * @param options Parsing options + * @return The parsed Model + * @throws ParsingException + */ + public DocumentModel parseFile(String text, URL url, ParsingOptions options) throws ParsingException { + Clojure.require(Clojure.API_MODELING_FRAMEWORK_CORE); + IFn parserFn = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, parserConstructor()); + Object parser = parserFn.invoke(); + IFn parseFileSync = Clojure.var(Clojure.API_MODELING_FRAMEWORK_CORE, "parse-string-sync"); + try { + Object rawModel = parseFileSync.invoke(parser, url.toString().replace("file:",""), text, options.build()); + if (rawModel instanceof Exception) { + throw new ParsingException((Exception) rawModel); + } + return DocumentModel.fromRawModel(rawModel); + } catch (RuntimeException e) { + throw new ParsingException(e); + } + } +} diff --git a/java/src/org/raml/amf/parsers/OpenAPIParser.java b/java/src/org/raml/amf/parsers/OpenAPIParser.java new file mode 100644 index 0000000..9cab6fe --- /dev/null +++ b/java/src/org/raml/amf/parsers/OpenAPIParser.java @@ -0,0 +1,15 @@ +package org.raml.amf.parsers; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Wrapper class for the AMF OpenAPI parser, processes OpenAPI specification documents and generate the DocumentModel out of them + */ +public class OpenAPIParser extends BaseParser { + @Override + protected String parserConstructor() { + return "->OpenAPIParser"; + } +} diff --git a/java/src/org/raml/amf/parsers/ParsingException.java b/java/src/org/raml/amf/parsers/ParsingException.java new file mode 100644 index 0000000..7720854 --- /dev/null +++ b/java/src/org/raml/amf/parsers/ParsingException.java @@ -0,0 +1,18 @@ +package org.raml.amf.parsers; + +import java.io.IOException; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Exception produced while parsing an input syntax for the AMF parser + */ +public class ParsingException extends IOException { + + public ParsingException(Exception ex) { + super(ex); + } + +} diff --git a/java/src/org/raml/amf/parsers/ParsingOptions.java b/java/src/org/raml/amf/parsers/ParsingOptions.java new file mode 100644 index 0000000..6f7c56e --- /dev/null +++ b/java/src/org/raml/amf/parsers/ParsingOptions.java @@ -0,0 +1,38 @@ +package org.raml.amf.parsers; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +import clojure.lang.IPersistentMap; +import clojure.lang.PersistentHashMap; + +import java.util.HashMap; + +/** + * Parsing options for parsing + */ +public class ParsingOptions { + private IPersistentMap cacheDirs; + + /** + * Sets a mapping from URL prefixes for references to local directories to resolve references in the syntax + * @param uriToDirs + */ + public ParsingOptions setCacheDirs(HashMap uriToDirs) { + this.cacheDirs = PersistentHashMap.EMPTY; + for (String key : uriToDirs.keySet()) { + this.cacheDirs = this.cacheDirs.assoc(key, uriToDirs.get(key)); + } + return this; + } + + public IPersistentMap build() { + IPersistentMap options = PersistentHashMap.EMPTY; + if (this.cacheDirs != null) { + options = options.assoc("cacheDirs", this.cacheDirs); + } + + return options; + } +} diff --git a/java/src/org/raml/amf/parsers/RAMLParser.java b/java/src/org/raml/amf/parsers/RAMLParser.java new file mode 100644 index 0000000..473d9cb --- /dev/null +++ b/java/src/org/raml/amf/parsers/RAMLParser.java @@ -0,0 +1,16 @@ +package org.raml.amf.parsers; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Wrapper class for the AMF RAML parser, processes RAML specification documents and generate the DocumentModel out of them + */ +public class RAMLParser extends BaseParser { + + @Override + protected String parserConstructor() { + return "->RAMLParser"; + } +} diff --git a/java/src/org/raml/amf/utils/Clojure.java b/java/src/org/raml/amf/utils/Clojure.java new file mode 100644 index 0000000..c50b7ca --- /dev/null +++ b/java/src/org/raml/amf/utils/Clojure.java @@ -0,0 +1,83 @@ +package org.raml.amf.utils; + +import clojure.lang.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by antoniogarrote on 04/05/2017. + */ + +/** + * Clojure interop utilties. + * Many of this utilities and this particular approach to interop is because of a bug related to core.async preventing + * us to use aot compilation and the class that could have been generated in api-modeling-framework.core + */ +public class Clojure { + + public static final String CLOJURE_CORE = "clojure.core"; + public static final String API_MODELING_FRAMEWORK_CORE = "api-modeling-framework.core"; + public static final String API_MODELING_FRAMEWORK_MODEL_DOCUMENT = "api-modeling-framework.model.document"; + public static final String API_MODELING_FRAMEWORK_MODEL_DOMAIN = "api-modeling-framework.model.domain"; + public static final String CHESHIRE_CORE = "cheshire.core"; + public static final Var REQUIRE= RT.var("clojure.core", "require"); + + + public static Object require(String nsName) { + return REQUIRE.invoke(Symbol.intern(nsName)); + } + + /** + * Looks up a var by name in the given namespace. + * + * The var can subsequently be invoked if it is a function. + * @param nsName + * @param varName + * @return + */ + public static Var var(String nsName, String varName) { + return RT.var(nsName,varName); + } + + public static Keyword kw(String name) { + return Keyword.find(name); + } + + public static Object getKw(Object target, String name) { + IFn getFn= var(CLOJURE_CORE, "get"); + return getFn.invoke(target, kw(name)); + } + + public static Object setKw(Object target, String name, Object value) { + IFn setFn= var(CLOJURE_CORE, "assoc"); + return setFn.invoke(target, kw(name), value); + } + + public static IPersistentVector list(List list) { + IPersistentVector tmp = PersistentVector.EMPTY; + for(Object e : list) { + tmp = tmp.cons(e); + } + + return tmp; + } + + public static List toJavaList(List xs) { + ArrayList tmp = new ArrayList<>(); + IFn firstFn= var(CLOJURE_CORE, "first"); + IFn restFn= var(CLOJURE_CORE, "rest"); + IFn emptyFn= var(CLOJURE_CORE, "empty?"); + + while(! (Boolean) emptyFn.invoke(xs)) { + tmp.add((T) firstFn.invoke(xs)); + xs = (List) restFn.invoke(xs); + } + + return tmp; + } + + public static IPersistentMap map() { + return PersistentHashMap.EMPTY; + } +} diff --git a/project.clj b/project.clj index ca3df49..f64bf9c 100644 --- a/project.clj +++ b/project.clj @@ -19,6 +19,8 @@ ;; dev only [difform "1.1.2"]] + :aot [api-modeling-framework.model.domain] + :plugins [[lein-cljsbuild "1.1.5"] [lein-npm "0.6.2"] [lein-doo "0.1.7"]] @@ -27,7 +29,10 @@ [json-to-ast "2.0.0-alpha1.2"]]} :profiles {:build {:source-paths ["build"] - :main api-modeling-framework.build}} + :main api-modeling-framework.build} + :precomp {:aot [api-modeling-framework.model.domain] } + :java-compile {:source-paths [] + :java-source-paths ["java/src"]}} :aliases {"node" ["with-profile" "build" "run" "node"] "web" ["with-profile" "build" "run" "web"] diff --git a/src/api_modeling_framework/core.cljc b/src/api_modeling_framework/core.cljc index b772f65..598980d 100644 --- a/src/api_modeling_framework/core.cljc +++ b/src/api_modeling_framework/core.cljc @@ -1,6 +1,6 @@ (ns api-modeling-framework.core #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]])) - #?(:clj (:require [clojure.core.async :refer [! go chan] :as async] + #?(:clj (:require [clojure.core.async :refer [! sync [f] + #?(:cljs (throw (js/Error "Synchronous version not supported")) + :clj (! c e) + (>! c res))))) + c)))) (defrecord ^:export RAMLParser [] Parser + (parse-file-sync [this uri] + (cb->sync (partial parse-file this uri))) + (parse-file-sync [this uri options] + (cb->sync (partial parse-file this uri options))) (parse-file [this uri cb] (parse-file this uri {} cb)) (parse-file [this uri options cb] (go (let [res (sync (partial parse-string this uri string))) + (parse-string-sync [this uri string options] + (cb->sync (partial parse-string this uri string options))) (parse-string [this uri string cb] (parse-string this uri string {} cb)) (parse-string [this uri string options cb] (go (let [res (sync (partial parse-file this uri))) + (parse-file-sync [this uri options] + (cb->sync (partial parse-file this uri options))) (parse-file [this uri cb] (parse-file this uri {} cb)) (parse-file [this uri options cb] (go (let [res (sync (partial parse-string this uri string))) + (parse-string-sync [this uri string options] + (cb->sync (partial parse-string this uri string options))) (parse-string [this uri string cb] (parse-string this uri string {} cb)) (parse-string [this uri string options cb] (go (let [res (sync (partial parse-file this uri))) + (parse-file-sync [this uri options] + (cb->sync (partial parse-file this uri options))) (parse-file [this uri cb] (parse-file this uri {} cb)) (parse-file [this uri options cb] (debug "Parsing APIModel file") @@ -138,6 +177,10 @@ (try (cb nil (to-model (jsonld-document-parser/from-jsonld res))) (catch #?(:clj Exception :cljs js/Error) ex (cb (platform/<-clj ex) nil))))))) + (parse-string-sync [this uri string] + (cb->sync (partial parse-string this uri string))) + (parse-string-sync [this uri string options] + (cb->sync (partial parse-string this uri string options))) (parse-string [this uri string cb] (parse-string this uri string {} cb)) (parse-string [this uri string options cb] (debug "Parsing APIModel string") @@ -150,6 +193,8 @@ (defrecord ^:export APIModelGenerator [] Generator + (generate-string-sync [this uri model options] (cb->sync (partial generate-string this uri model options))) + (generate-file-sync [this uri model options] (cb->sync (partial generate-file this uri model options))) (generate-string [this uri model options cb] (debug "Generating APIModel string") (go (try (let [options (keywordize-keys options) @@ -178,6 +223,8 @@ (defrecord ^:export RAMLGenerator [] Generator + (generate-string-sync [this uri model options] (cb->sync (partial generate-string this uri model options))) + (generate-file-sync [this uri model options] (cb->sync (partial generate-file this uri model options))) (generate-string [this uri model options cb] (debug "Generating RAML string") (go (try (let [options (keywordize-keys (merge (or (platform/->clj options) {}) @@ -207,6 +254,8 @@ (defrecord ^:export OpenAPIGenerator [] Generator + (generate-string-sync [this uri model options] (cb->sync (partial generate-string this uri model options))) + (generate-file-sync [this uri model options] (cb->sync (partial generate-file this uri model options))) (generate-string [this uri model options cb] (debug "Generating OpenAPI string") (go (try (let [options (keywordize-keys (merge (or (platform/->clj options) {}) @@ -278,6 +327,13 @@ (let [domain-cache (atom nil) lexical-cache-raml (atom {})] (reify Model + (unit-kind [_] + (cond + (and (satisfies? document/Module res) + (satisfies? document/Fragment res)) "document" + (satisfies? document/Module res) "module" + (satisfies? document/Fragment res) "fragment" + :else "unit")) (location [_] (document/location res)) (document-model [_] res) diff --git a/src/api_modeling_framework/model/domain.cljc b/src/api_modeling_framework/model/domain.cljc index f2c8987..0b672d6 100644 --- a/src/api_modeling_framework/model/domain.cljc +++ b/src/api_modeling_framework/model/domain.cljc @@ -32,8 +32,8 @@ CommonAPIProperties (host [this] host) (scheme [this] scheme) - (accepts [this] accepts) - (content-type [this] content-type) + (accepts [this] (or accepts [])) + (content-type [this] (or content-type [])) HeadersHolder (headers [this] (or headers [])) ParametersHolder