http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/dto/jsonschema/package.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/dto/jsonschema/package.html b/juneau-core/src/main/java/org/apache/juneau/dto/jsonschema/package.html new file mode 100644 index 0000000..51977bf --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/dto/jsonschema/package.html @@ -0,0 +1,518 @@ +<!DOCTYPE HTML> +<!-- +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + ***************************************************************************************************************************/ +--> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>JSON-Schema Data Transfer Objects</p> +<script> + function toggle(x) { + var div = x.nextSibling; + while (div != null && div.nodeType != 1) + div = div.nextSibling; + if (div != null) { + var d = div.style.display; + if (d == 'block' || d == '') { + div.style.display = 'none'; + x.className += " closed"; + } else { + div.style.display = 'block'; + x.className = x.className.replace(/(?:^|\s)closed(?!\S)/g , '' ); + } + } + } +</script> +<a id='TOC'></a><h5 class='toc'>Table of Contents</h5> +<ol class='toc'> + <li><p><a class='doclink' href='#Overview'>Overview</a></p> + <ol> + <li><p><a class='doclink' href='#SchemaDefinition'>JSON-Schema schema definition</a></p> + <li><p><a class='doclink' href='#Serialize'>Creating JSON-Schema documents</a></p> + <ol> + <li><p><a class='doclink' href='#SerializeToOther'>Serializing to other data types</a></p> + </ol> + <li><p><a class='doclink' href='#Parse'>Parsing JSON-Schema documents</a></p> + </ol> +</ol> +<!-- ======================================================================================================== --> +<a id="Overview"></a> +<h2 class='topic' onclick='toggle(this)'>1 - Overview</h2> +<div class='topic'> + <p> + Juneau supports serializing and parsing of JSON-Schema documents through the use of beans defined in the <code>org.apache.juneau.dto.jsonschema</code> package.<br> + These beans are used with the existing {@link org.apache.juneau.json.JsonSerializer} and {@link org.apache.juneau.json.JsonParser} classes to produce and consume JSON-Schema documents. + </p> + <p> + <b>NOTE:</b> JSON-Schema is currently in draft form. This API may change as the JSON-Schema specification changes. + </p> + + <!-- ======================================================================================================== --> + <a id="SchemaDefinition"></a> + <h3 class='topic' onclick='toggle(this)'>1.1 - JSON-Schema schema definition</h3> + <div class='topic'> + <p> + The draft JSON-Schema specification that the JSON-Schema beans are modeled after is as follows: + </p> + <p class='bcode'> + { + <js>"id"</js>: <js>"http://json-schema.org/draft-04/schema#"</js>, + <js>"$schema"</js>: <js>"http://json-schema.org/draft-04/schema#"</js>, + <js>"description"</js>: <js>"Core schema meta-schema"</js>, + <js>"definitions"</js>: { + <js>"schemaArray"</js>: { + <js>"type"</js>: <js>"array"</js>, + <js>"minItems"</js>: 1, + <js>"items"</js>: { <js>"$ref"</js>: <js>"#"</js> } + }, + <js>"positiveInteger"</js>: { + <js>"type"</js>: <js>"integer"</js>, + <js>"minimum"</js>: 0 + }, + <js>"positiveIntegerDefault0"</js>: { + <js>"allOf"</js>: [ { <js>"$ref"</js>: <js>"#/definitions/positiveInteger"</js> }, { <js>"default"</js>: 0 } ] + }, + <js>"simpleTypes"</js>: { + <js>"enum"</js>: [ <js>"array"</js>, <js>"boolean"</js>, <js>"integer"</js>, <js>"null"</js>, <js>"number"</js>, <js>"object"</js>, <js>"string"</js> ] + }, + <js>"stringArray"</js>: { + <js>"type"</js>: <js>"array"</js>, + <js>"items"</js>: { <js>"type"</js>: <js>"string"</js> }, + <js>"minItems"</js>: 1, + <js>"uniqueItems"</js>: <jk>true</jk> + } + }, + <js>"type"</js>: <js>"object"</js>, + <js>"properties"</js>: { + <js>"id"</js>: { + <js>"type"</js>: <js>"string"</js>, + <js>"format"</js>: <js>"uri"</js> + }, + <js>"$schema"</js>: { + <js>"type"</js>: <js>"string"</js>, + <js>"format"</js>: <js>"uri"</js> + }, + <js>"title"</js>: { + <js>"type"</js>: <js>"string"</js> + }, + <js>"description"</js>: { + <js>"type"</js>: <js>"string"</js> + }, + <js>"default"</js>: {}, + <js>"multipleOf"</js>: { + <js>"type"</js>: <js>"number"</js>, + <js>"minimum"</js>: 0, + <js>"exclusiveMinimum"</js>: <jk>true</jk> + }, + <js>"maximum"</js>: { + <js>"type"</js>: <js>"number"</js> + }, + <js>"exclusiveMaximum"</js>: { + <js>"type"</js>: <js>"boolean"</js>, + <js>"default"</js>: <jk>false</jk> + }, + <js>"minimum"</js>: { + <js>"type"</js>: <js>"number"</js> + }, + <js>"exclusiveMinimum"</js>: { + <js>"type"</js>: <js>"boolean"</js>, + <js>"default"</js>: <jk>false</jk> + }, + <js>"maxLength"</js>: { <js>"$ref"</js>: <js>"#/definitions/positiveInteger"</js> }, + <js>"minLength"</js>: { <js>"$ref"</js>: <js>"#/definitions/positiveIntegerDefault0"</js> }, + <js>"pattern"</js>: { + <js>"type"</js>: <js>"string"</js>, + <js>"format"</js>: <js>"regex"</js> + }, + <js>"additionalItems"</js>: { + <js>"anyOf"</js>: [ + { <js>"type"</js>: <js>"boolean"</js> }, + { <js>"$ref"</js>: <js>"#"</js> } + ], + <js>"default"</js>: {} + }, + <js>"items"</js>: { + <js>"anyOf"</js>: [ + { <js>"$ref"</js>: <js>"#"</js> }, + { <js>"$ref"</js>: <js>"#/definitions/schemaArray"</js> } + ], + <js>"default"</js>: {} + }, + <js>"maxItems"</js>: { <js>"$ref"</js>: <js>"#/definitions/positiveInteger"</js> }, + <js>"minItems"</js>: { <js>"$ref"</js>: <js>"#/definitions/positiveIntegerDefault0"</js> }, + <js>"uniqueItems"</js>: { + <js>"type"</js>: <js>"boolean"</js>, + <js>"default"</js>: <jk>false</jk> + }, + <js>"maxProperties"</js>: { <js>"$ref"</js>: <js>"#/definitions/positiveInteger"</js> }, + <js>"minProperties"</js>: { <js>"$ref"</js>: <js>"#/definitions/positiveIntegerDefault0"</js> }, + <js>"required"</js>: { <js>"$ref"</js>: <js>"#/definitions/stringArray"</js> }, + <js>"additionalProperties"</js>: { + <js>"anyOf"</js>: [ + { <js>"type"</js>: <js>"boolean"</js> }, + { <js>"$ref"</js>: <js>"#"</js> } + ], + <js>"default"</js>: {} + }, + <js>"definitions"</js>: { + <js>"type"</js>: <js>"object"</js>, + <js>"additionalProperties"</js>: { <js>"$ref"</js>: <js>"#"</js> }, + <js>"default"</js>: {} + }, + <js>"properties"</js>: { + <js>"type"</js>: <js>"object"</js>, + <js>"additionalProperties"</js>: { <js>"$ref"</js>: <js>"#"</js> }, + <js>"default"</js>: {} + }, + <js>"patternProperties"</js>: { + <js>"type"</js>: <js>"object"</js>, + <js>"additionalProperties"</js>: { <js>"$ref"</js>: <js>"#"</js> }, + <js>"default"</js>: {} + }, + <js>"dependencies"</js>: { + <js>"type"</js>: <js>"object"</js>, + <js>"additionalProperties"</js>: { + <js>"anyOf"</js>: [ + { <js>"$ref"</js>: <js>"#"</js> }, + { <js>"$ref"</js>: <js>"#/definitions/stringArray"</js> } + ] + } + }, + <js>"enum"</js>: { + <js>"type"</js>: <js>"array"</js>, + <js>"minItems"</js>: 1, + <js>"uniqueItems"</js>: <jk>true</jk> + }, + <js>"type"</js>: { + <js>"anyOf"</js>: [ + { <js>"$ref"</js>: <js>"#/definitions/simpleTypes"</js> }, + { + <js>"type"</js>: <js>"array"</js>, + <js>"items"</js>: { <js>"$ref"</js>: <js>"#/definitions/simpleTypes"</js> }, + <js>"minItems"</js>: 1, + <js>"uniqueItems"</js>: <jk>true</jk> + } + ] + }, + <js>"allOf"</js>: { <js>"$ref"</js>: <js>"#/definitions/schemaArray"</js> }, + <js>"anyOf"</js>: { <js>"$ref"</js>: <js>"#/definitions/schemaArray"</js> }, + <js>"oneOf"</js>: { <js>"$ref"</js>: <js>"#/definitions/schemaArray"</js> }, + <js>"not"</js>: { <js>"$ref"</js>: <js>"#"</js> } + }, + <js>"dependencies"</js>: { + <js>"exclusiveMaximum"</js>: [ <js>"maximum"</js> ], + <js>"exclusiveMinimum"</js>: [ <js>"minimum"</js> ] + }, + <js>"default"</js>: {} + } + </p> + <p> + The bean classes that make up the model are as follows: + </p> + <ul class='spaced-list'> + <li>{@link org.apache.juneau.dto.jsonschema.Schema} - Top level schema object. + <li>{@link org.apache.juneau.dto.jsonschema.SchemaProperty} - A subclass of <code>Schema</code> for representing properties. + <li>{@link org.apache.juneau.dto.jsonschema.SchemaPropertySimpleArray} - A convenience subclass of <code>SchemaProperty</code> for representing properties of simple array types. + <li>{@link org.apache.juneau.dto.jsonschema.SchemaRef} - Represents a URI reference to another schema. + <li>{@link org.apache.juneau.dto.jsonschema.SchemaArray} - An array of <code>Schema</code> objects. + <li>{@link org.apache.juneau.dto.jsonschema.JsonType} - An enum of possible JSON data types. + <li>{@link org.apache.juneau.dto.jsonschema.JsonTypeArray} - An array of <code>JsonType</code> objects. + </ul> + </div> + + + <!-- ======================================================================================================== --> + <a id="Serialize"></a> + <h3 class='topic' onclick='toggle(this)'>1.2 - Creating JSON-Schema documents</h3> + <div class='topic'> + <p> + JSON-Schema documents can be constructed using the Juneau JSON-Schema beans as a document model object. + These beans are defined with fluent-style setters to make constructing documents as easy as possible. + </p> + <p> + The following is an example JSON-Schema document: + </p> + <p class='bcode'> + { + <js>"title"</js>: <js>"Example Schema"</js>, + <js>"type"</js>: <js>"object"</js>, + <js>"properties"</js>: { + <js>"firstName"</js>: { + <js>"type"</js>: <js>"string"</js> + }, + <js>"lastName"</js>: { + <js>"type"</js>: <js>"string"</js> + }, + <js>"age"</js>: { + <js>"description"</js>: <js>"Age in years"</js>, + <js>"type"</js>: <js>"integer"</js>, + <js>"minimum"</js>: 0 + } + }, + <js>"required"</js>: [<js>"firstName"</js>, <js>"lastName"</js>] + } + </p> + <p> + This document can be constructing using the following code: + </p> + <p class='bcode'> + <jc>// Create the document object model</jc> + Schema s = <jk>new</jk> Schema() + .setTitle(<js>"Example Schema"</js>) + .setType(JsonType.<jsf>OBJECT</jsf>) + .addProperties( + <jk>new</jk> SchemaProperty(<js>"firstName"</js>, JsonType.<jsf>STRING</jsf>), + <jk>new</jk> SchemaProperty(<js>"lastName"</js>, JsonType.<jsf>STRING</jsf>), + <jk>new</jk> SchemaProperty(<js>"age"</js>, JsonType.<jsf>INTEGER</jsf>) + .setDescription(<js>"Age in years"</js>) + .setMinimum(0) + ) + .addRequired(<js>"firstName"</js>, <js>"lastName"</js>); + + <jc>// Serialize to JSON</jc> + String json = JsonSerializer.<jsf>DEFAULT_READABLE</jsf>.serialize(s); + </p> + <p> + The following is a more-complex example showing various kinds of constraints. + </p> + <p class='bcode'> + { + <js>"id"</js>: <js>"http://some.site.somewhere/entry-schema#"</js>, + <js>"$schema"</js>: <js>"http://json-schema.org/draft-04/schema#"</js>, + <js>"description"</js>: <js>"schema for an fstab entry"</js>, + <js>"type"</js>: <js>"object"</js>, + <js>"required"</js>: [ <js>"storage"</js> ], + <js>"properties"</js>: { + <js>"storage"</js>: { + <js>"type"</js>: <js>"object"</js>, + <js>"oneOf"</js>: [ + { <js>"$ref"</js>: <js>"#/definitions/diskDevice"</js> }, + { <js>"$ref"</js>: <js>"#/definitions/diskUUID"</js> }, + { <js>"$ref"</js>: <js>"#/definitions/nfs"</js> }, + { <js>"$ref"</js>: <js>"#/definitions/tmpfs"</js> } + ] + }, + <js>"fstype"</js>: { + <js>"enum"</js>: [ <js>"ext3"</js>, <js>"ext4"</js>, <js>"btrfs"</js> ] + }, + <js>"options"</js>: { + <js>"type"</js>: <js>"array"</js>, + <js>"minItems"</js>: 1, + <js>"items"</js>: { <js>"type"</js>: <js>"string"</js> }, + <js>"uniqueItems"</js>: <jk>true</jk> + }, + <js>"readonly"</js>: { <js>"type"</js>: <js>"boolean"</js> } + }, + <js>"definitions"</js>: { + <js>"diskDevice"</js>: {}, + <js>"diskUUID"</js>: {}, + <js>"nfs"</js>: {}, + <js>"tmpfs"</js>: {} + } + } + </p> + <p> + This document can be constructing using the following code: + </p> + <p class='bcode'> + Schema s = <jk>new</jk> Schema() + .setId(<js>"http://some.site.somewhere/entry-schema#"</js>) + .setSchemaVersionId(<js>"http://json-schema.org/draft-04/schema#"</js>) + .setDescription(<js>"schema for an fstab entry"</js>) + .setType(JsonType.<jsf>OBJECT</jsf>) + .addRequired(<js>"storage"</js>) + .addProperties( + <jk>new</jk> SchemaProperty(<js>"storage"</js>) + .setType(JsonType.<jsf>OBJECT</jsf>) + .addOneOf( + <jk>new</jk> SchemaRef(<js>"#/definitions/diskDevice"</js>), + <jk>new</jk> SchemaRef(<js>"#/definitions/diskUUID"</js>), + <jk>new</jk> SchemaRef(<js>"#/definitions/nsf"</js>), + <jk>new</jk> SchemaRef(<js>"#/definitions/tmpfs"</js>) + ), + <jk>new</jk> SchemaProperty(<js>"fstype"</js>) + .addEnum(<js>"ext3"</js>, <js>"ext4"</js>, <js>"btrfs"</js>), + <jk>new</jk> SchemaPropertySimpleArray(<js>"options"</js>, JsonType.<jsf>STRING</jsf>) + .setMinItems(1) + .setUniqueItems(<jk>true</jk>), + <jk>new</jk> SchemaProperty(<js>"readonly"</js>) + .setType(JsonType.<jsf>BOOLEAN</jsf>) + ) + .addDefinition(<js>"diskDevice"</js>, + <jk>new</jk> Schema() + ) + .addDefinition(<js>"diskUUID"</js>, + <jk>new</jk> Schema() + ) + .addDefinition(<js>"nfs"</js>, + <jk>new</jk> Schema() + ) + .addDefinition(<js>"tmpfs"</js>, + <jk>new</jk> Schema() + ); + + <jc>// Serialize to JSON</jc> + String json = JsonSerializer.<jsf>DEFAULT_READABLE</jsf>.serialize(s); + </p> + + + <!-- ======================================================================================================== --> + <a id="SerializeToOther"></a> + <h4 class='topic' onclick='toggle(this)'>1.2.1 - Serializing to other data types</h4> + <div class='topic'> + <p> + Since the JSON-Schema DTOs are simple beans, they can be used to serialize to a variety of other language types as well as JSON. + This also allows JSON-Schema documents to be easily served up using the Juneau REST API. + </p> + <p> + The sample web application includes a REST resource that generates a JSON-Schema document. + We'll use this resource to show what the JSON-Schema document looks like in other languages. + </p> + <p class='bcode'> + <jd>/** + * Sample resource that shows how to serialize JSON-Schema documents. + */</jd> + <ja>@RestResource</ja>( + path=<js>"/jsonSchema"</js>, + messages=<js>"nls/JsonSchemaResource"</js>, + properties={ + <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_title</jsf>, value=<js>"Sample JSON-Schema document"</js>), + <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_links</jsf>, value=<js>"{options:'?method=OPTIONS'}"</js>) + } + ) + <jk>public class</jk> JsonSchemaResource <jk>extends</jk> RestServletJenaDefault { + + <jk>private</jk> Schema <jf>schema</jf>; <jc>// The schema document</jc> + + <jd>/** Servlet initialization */</jd> + <ja>@Override</ja> + <jk>public void</jk> init() { + + <jk>try</jk> { + <jf>schema</jf> = <jk>new</jk> Schema() + .setId(<js>"http://example.com/sample-schema#"</js>) + .setSchemaVersionUri(<js>"http://json-schema.org/draft-04/schema#"</js>) + .setTitle(<js>"Example Schema"</js>) + .setType(JsonType.<jsf>OBJECT</jsf>) + .addProperties( + <jk>new</jk> SchemaProperty(<js>"firstName"</js>, JsonType.<jsf>STRING</jsf>), + <jk>new</jk> SchemaProperty(<js>"lastName"</js>, JsonType.<jsf>STRING</jsf>), + <jk>new</jk> SchemaProperty(<js>"age"</js>, JsonType.<jsf>INTEGER</jsf>) + .setDescription(<js>"Age in years"</js>) + .setMinimum(0) + ) + .addRequired(<js>"firstName"</js>, <js>"lastName"</js>); + } <jk>catch</jk> (Exception e) { + <jk>throw new</jk> RuntimeException(e); + } + } + + <jd>/** GET request handler */</jd> + <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/"</js>) + <jk>public</jk> Schema getSchema() <jk>throws</jk> Exception { + <jk>return</jk> <jf>schema</jf>; + } + + <jd>/** + * PUT request handler. + * Replaces the schema document with the specified content, and then mirrors it as the response. + */</jd> + <ja>@RestMethod</ja>(name=<js>"PUT"</js>, path=<js>"/"</js>) + <jk>public</jk> Schema setSchema(<ja>@Content</ja> Schema schema) <jk>throws</jk> Exception { + <jk>this</jk>.<jf>schema</jf> = schema; + <jk>return</jk> <jk>this</jk>.<jf>schema</jf>; + } + + <jd>/** OPTIONS request handler */</jd> + <ja>@RestMethod</ja>(name=<js>"OPTIONS"</js>, path=<js>"/*"</js>) + <jk>public</jk> ResourceOptions doOptions(RestRequest req) { + <jk>return new</jk> ResourceOptions(<jk>this</jk>, req); + } + } + </p> + <p> + When you point your browser to this resource, the default content type is HTML (since that's what the browser asks for + by default). + </p> + <h6 class='figure'>HTML</h6> + <img class='bordered' src="doc-files/Example_Html.png"> + <p> + The REST API allows you to specify the <code>Accept</code> header as a GET parameter, and the <code>plainText=true</code> + parameter forces the returned <code>Content-Type</code> to be <code>text/plain</code>. + We'll use this to view the JSON-Schema document in other languages. + </p> + + <h6 class='figure'>Normal JSON</h6> + <img class='bordered' src="doc-files/Example_Json.png"> + + <h6 class='figure'>XML</h6> + <img class='bordered' src="doc-files/Example_Xml.png"> + + <h6 class='figure'>URL-Encoded</h6> + <img class='bordered' src="doc-files/Example_UrlEncoded.png"> + + <h6 class='figure'>Abbreviated RDF/XML</h6> + <img class='bordered' src="doc-files/Example_XmlRdfAbbrev.png"> + + <h6 class='figure'>Turtle</h6> + <img class='bordered' src="doc-files/Example_Turtle.png"> + + <p> + The full list of options for this resource can be accessed by the <code>options</code> link on the HTML page. + </p> + + <h6 class='figure'>Resource Options</h6> + <img class='bordered' src="doc-files/Example_Options.png"> + </div> + + <!-- ======================================================================================================== --> + <a id="Parse"></a> + <h3 class='topic' onclick='toggle(this)'>1.3 - Parsing JSON-Schema documents</h3> + <div class='topic'> + <p> + Use the {@link org.apache.juneau.json.JsonParser} to parse JSON-Schema documents into DTOs: + </p> + <p class='bcode'> + <jc>// Use parser to load JSON-Schema document into JSON-Schema DTOs</jc> + Schema schema = JsonParser.<jsf>DEFAULT</jsf>.parse(json, Schema.<jk>class</jk>); + </p> + <p> + Schema objects can also be constructed from the other media types using the appropriate parsers. + </p> + </div> + +</div> +<p align="center"><i><b>*** fÃn ***</b></i></p> +</body> +</html> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/dto/package.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/dto/package.html b/juneau-core/src/main/java/org/apache/juneau/dto/package.html new file mode 100644 index 0000000..4574810 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/dto/package.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<!-- +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + ***************************************************************************************************************************/ + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>Data transfer objects</p> +</body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/encoders/Encoder.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/encoders/Encoder.java b/juneau-core/src/main/java/org/apache/juneau/encoders/Encoder.java new file mode 100644 index 0000000..128148b --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/encoders/Encoder.java @@ -0,0 +1,57 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.encoders; + +import java.io.*; + +/** + * Used for enabling decompression on requests and compression on responses, such as support for GZIP compression. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Used to wrap input and output streams withing compression/decompression streams. + * <p> + * Encoders are registered with <code>RestServlets</code> through the <ja>@RestResource.encoders()</ja> annotation. + * + * + * @author James Bognar ([email protected]) + */ +public abstract class Encoder { + + /** + * Converts the specified compressed input stream into an uncompressed stream. + * + * @param is The compressed stream. + * @return The uncompressed stream. + * @throws IOException If any errors occur, such as on a stream that's not a valid GZIP input stream. + */ + public abstract InputStream getInputStream(InputStream is) throws IOException; + + /** + * Converts the specified uncompressed output stream into an uncompressed stream. + * + * @param os The uncompressed stream. + * @return The compressed stream stream. + * @throws IOException If any errors occur. + */ + public abstract OutputStream getOutputStream(OutputStream os) throws IOException; + + /** + * Returns the codings in <code>Content-Encoding</code> and <code>Accept-Encoding</code> headers + * that this encoder handles (e.g. <js>"gzip"</js>). + * + * @return The codings that this encoder handles. + */ + public abstract String[] getCodings(); +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java new file mode 100644 index 0000000..710f16a --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java @@ -0,0 +1,199 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.encoders; + +import static org.apache.juneau.internal.ArrayUtils.*; + +import java.util.*; + +import org.apache.juneau.*; + +/** + * Represents the group of {@link Encoder encoders} keyed by codings. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Maintains a set of encoders and the codings that they can handle. + * <p> + * The {@link #findMatch(String)} and {@link #getEncoder(String)} methods are then + * used to find appropriate encoders for specific <code>Accept-Encoding</code> + * and <code>Content-Encoding</code> header values. + * + * + * <h6 class='topic'>Match ordering</h6> + * <p> + * Encoders are matched against <code>Accept-Encoding</code> strings in the order they exist in this group. + * <p> + * Adding new entries will cause the entries to be prepended to the group. + * This allows for previous encoders to be overridden through subsequent calls. + * <p> + * For example, calling <code>g.append(E1.<jk>class</jk>,E2.<jk>class</jk>).append(E3.<jk>class</jk>,E4.<jk>class</jk>)</code> + * will result in the order <code>E3, E4, E1, E2</code>. + * + * + * <h6 class='topic'>Example</h6> + * <p class='bcode'> + * <jc>// Create an encoder group with support for gzip compression.</jc> + * EncoderGroup g = <jk>new</jk> EncoderGroup().append(GzipEncoder.<jk>class</jk>); + * + * <jc>// Should return "gzip"</jc> + * String matchedCoding = g.findMatch(<js>"compress;q=1.0, gzip;q=0.8, identity;q=0.5, *;q=0"</js>); + * + * <jc>// Get the encoder</jc> + * IEncoder encoder = g.getEncoder(matchedCoding); + * </p> + * + * + * @author James Bognar ([email protected]) + */ +public final class EncoderGroup { + + private Map<String,EncoderEntry> entryMap = new TreeMap<String,EncoderEntry>(String.CASE_INSENSITIVE_ORDER); + private LinkedList<EncoderEntry> tempEntries = new LinkedList<EncoderEntry>(); + private EncoderEntry[] entries; + + /** + * Returns the coding string for the matching encoder that can handle the specified <code>Accept-Encoding</code> + * or <code>Content-Encoding</code> header value. + * <p> + * Returns <jk>null</jk> if no encoders can handle it. + * <p> + * This method is fully compliant with the RFC2616/14.3 and 14.11 specifications. + * + * @param acceptEncoding The <code>Accept-Encoding</code> or <code>Content-Encoding</code> value. + * @return The coding value (e.g. <js>"gzip"</js>). + */ + public String findMatch(String acceptEncoding) { + if (getEntries().length == 0) + return null; + + MediaRange[] ae = MediaRange.parse(acceptEncoding); + + if (ae.length == 0) + ae = MediaRange.parse("*"); + + for (MediaRange a : ae) + for (EncoderEntry e : getEntries()) + for (MediaRange a2 : e.encodingRanges) + if (a.matches(a2)) + return a2.getType(); + + return null; + } + + /** + * Adds the specified encoders to this group. + * + * @param e The encoders to instantiate and add to this group. + * @return This object (for method chaining). + * @throws Exception If an instantiation error occurred. + */ + public EncoderGroup append(Class<? extends Encoder>...e) throws Exception { + for (Class<? extends Encoder> r : reverse(e)) + append(r.newInstance()); + return this; + } + + /** + * Adds the specified encoders to this group. + * + * @param e The encoder to instantiate and add to this group. + * @return This object (for method chaining). + * @throws Exception If an instantiation error occurred. + */ + public EncoderGroup append(Class<? extends Encoder> e) throws Exception { + append(e.newInstance()); + return this; + } + + /** + * Adds the specified encoders to this group. + * + * @param e The encoders to add to this group. + * @return This object (for method chaining). + */ + public EncoderGroup append(Encoder...e) { + entries = null; + for (Encoder r : reverse(e)) { + EncoderEntry ee = new EncoderEntry(r); + tempEntries.addFirst(ee); + for (String s : ee.encodings) + this.entryMap.put(s, ee); + } + return this; + } + + /** + * Adds the encoders in the specified group to this group. + * + * @param g The group containing the encoders to add to this group. + * @return This object (for method chaining). + */ + public EncoderGroup append(EncoderGroup g) { + for (EncoderEntry e : reverse(g.getEntries())) + append(e.encoder); + return this; + } + + /** + * Returns the encoder registered with the specified coding (e.g. <js>"gzip"</js>). + * + * @param coding The coding string. + * @return The encoder, or <jk>null</jk> if encoder isn't registered with that coding. + */ + public Encoder getEncoder(String coding) { + EncoderEntry e = entryMap.get(coding); + return (e == null ? null : e.encoder); + } + + /** + * Returns the set of codings supported by all encoders in this group. + * + * @return The set of codings supported by all encoders in this group. Never <jk>null</jk>. + */ + public List<String> getSupportedEncodings() { + List<String> l = new ArrayList<String>(); + for (EncoderEntry e : getEntries()) + for (String enc : e.encodings) + if (! l.contains(enc)) + l.add(enc); + return l; + } + + private EncoderEntry[] getEntries() { + if (entries == null) + entries = tempEntries.toArray(new EncoderEntry[tempEntries.size()]); + return entries; + } + + static class EncoderEntry { + Encoder encoder; + MediaRange[] encodingRanges; + String[] encodings; + + EncoderEntry(Encoder e) { + encoder = e; + + encodings = new String[e.getCodings().length]; + int i = 0; + for (String enc : e.getCodings()) + encodings[i++] = enc; + + List<MediaRange> l = new LinkedList<MediaRange>(); + for (i = 0; i < encodings.length; i++) + l.addAll(Arrays.asList(MediaRange.parse(encodings[i]))); + encodingRanges = l.toArray(new MediaRange[l.size()]); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/encoders/GzipEncoder.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/encoders/GzipEncoder.java b/juneau-core/src/main/java/org/apache/juneau/encoders/GzipEncoder.java new file mode 100644 index 0000000..a08ffb7 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/encoders/GzipEncoder.java @@ -0,0 +1,48 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.encoders; + +import java.io.*; +import java.util.zip.*; + +/** + * Encoder for handling <js>"gzip"</js> encoding and decoding. + * + * @author James Bognar ([email protected]) + */ +public class GzipEncoder extends Encoder { + + @Override /* Encoder */ + public OutputStream getOutputStream(OutputStream os) throws IOException { + return new GZIPOutputStream(os) { + @Override /* OutputStream */ + public final void close() throws IOException { + finish(); + super.close(); + } + }; + } + + @Override /* Encoder */ + public InputStream getInputStream(InputStream is) throws IOException { + return new GZIPInputStream(is); + } + + /** + * Returns <code>[<js>"gzip"</js>]</code>. + */ + @Override /* Encoder */ + public String[] getCodings() { + return new String[]{"gzip"}; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/encoders/IdentityEncoder.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/encoders/IdentityEncoder.java b/juneau-core/src/main/java/org/apache/juneau/encoders/IdentityEncoder.java new file mode 100644 index 0000000..0bbb7e5 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/encoders/IdentityEncoder.java @@ -0,0 +1,47 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.encoders; + +import java.io.*; + +/** + * Encoder for handling <js>"identity"</js> encoding and decoding (e.g. no encoding at all). + * + * @author James Bognar ([email protected]) + */ +public class IdentityEncoder extends Encoder { + + /** Singleton */ + public static final IdentityEncoder INSTANCE = new IdentityEncoder(); + + /** Constructor. */ + protected IdentityEncoder() {} + + @Override /* Encoder */ + public InputStream getInputStream(InputStream is) throws IOException { + return is; + } + + @Override /* Encoder */ + public OutputStream getOutputStream(OutputStream os) throws IOException { + return os; + } + + /** + * Returns <code>[<js>"identity"</js>]</code>. + */ + @Override /* Encoder */ + public String[] getCodings() { + return new String[]{"identity"}; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/encoders/package.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/encoders/package.html b/juneau-core/src/main/java/org/apache/juneau/encoders/package.html new file mode 100644 index 0000000..41c1ab1 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/encoders/package.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<!-- +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + ***************************************************************************************************************************/ + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>Encoder API</p> + +<script> + function toggle(x) { + var div = x.nextSibling; + while (div != null && div.nodeType != 1) + div = div.nextSibling; + if (div != null) { + var d = div.style.display; + if (d == 'block' || d == '') { + div.style.display = 'none'; + x.className += " closed"; + } else { + div.style.display = 'block'; + x.className = x.className.replace(/(?:^|\s)closed(?!\S)/g , '' ); + } + } + } +</script> + +</body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java new file mode 100644 index 0000000..f33a880 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java @@ -0,0 +1,90 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.html; + +import org.apache.juneau.*; +import org.apache.juneau.html.annotation.*; + +/** + * Metadata on bean properties specific to the HTML serializers and parsers pulled from the {@link Html @Html} annotation on the bean property. + * + * @author James Bognar ([email protected]) + * @param <T> The bean class. + */ +public class HtmlBeanPropertyMeta<T> { + + private boolean asXml, noTables, noTableHeaders, asPlainText; + + /** + * Constructor. + * + * @param beanPropertyMeta The metadata of the bean property of this additional metadata. + */ + public HtmlBeanPropertyMeta(BeanPropertyMeta<T> beanPropertyMeta) { + if (beanPropertyMeta.getField() != null) + findHtmlInfo(beanPropertyMeta.getField().getAnnotation(Html.class)); + if (beanPropertyMeta.getGetter() != null) + findHtmlInfo(beanPropertyMeta.getGetter().getAnnotation(Html.class)); + if (beanPropertyMeta.getSetter() != null) + findHtmlInfo(beanPropertyMeta.getSetter().getAnnotation(Html.class)); + } + + private void findHtmlInfo(Html html) { + if (html == null) + return; + if (html.asXml()) + asXml = html.asXml(); + if (html.noTables()) + noTables = html.noTables(); + if (html.noTableHeaders()) + noTableHeaders = html.noTableHeaders(); + if (html.asPlainText()) + asPlainText = html.asPlainText(); + } + + /** + * Returns whether this bean property should be serialized as XML instead of HTML. + * + * @return <jk>true</jk> if the the {@link Html} annotation is specified, and {@link Html#asXml()} is <jk>true</jk>. + */ + protected boolean isAsXml() { + return asXml; + } + + /** + * Returns whether this bean property should be serialized as plain text instead of HTML. + * + * @return <jk>true</jk> if the the {@link Html} annotation is specified, and {@link Html#asPlainText()} is <jk>true</jk>. + */ + protected boolean isAsPlainText() { + return asPlainText; + } + + /** + * Returns whether this bean property should not be serialized as an HTML table. + * + * @return <jk>true</jk> if the the {@link Html} annotation is specified, and {@link Html#noTables()} is <jk>true</jk>. + */ + protected boolean isNoTables() { + return noTables; + } + + /** + * Returns whether this bean property should not include table headers when serialized as an HTML table. + * + * @return <jk>true</jk> if the the {@link Html} annotation is specified, and {@link Html#noTableHeaders()} is <jk>true</jk>. + */ + public boolean isNoTableHeaders() { + return noTableHeaders; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/html/HtmlClassMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlClassMeta.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlClassMeta.java new file mode 100644 index 0000000..02dc609 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlClassMeta.java @@ -0,0 +1,92 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.html; + +import org.apache.juneau.html.annotation.*; +import org.apache.juneau.internal.*; + +/** + * Metadata on classes specific to the HTML serializers and parsers pulled from the {@link Html @Html} annotation on the class. + * + * @author James Bognar ([email protected]) + */ +public class HtmlClassMeta { + + private final Html html; + private final boolean asXml, noTables, noTableHeaders, asPlainText; + + /** + * Constructor. + * + * @param c The class that this annotation is defined on. + */ + public HtmlClassMeta(Class<?> c) { + this.html = ReflectionUtils.getAnnotation(Html.class, c); + if (html != null) { + asXml = html.asXml(); + noTables = html.noTables(); + noTableHeaders = html.noTableHeaders(); + asPlainText = html.asPlainText(); + } else { + asXml = false; + noTables = false; + noTableHeaders = false; + asPlainText = false; + } + } + + /** + * Returns the {@link Html} annotation defined on the class. + * + * @return The value of the {@link Html} annotation, or <jk>null</jk> if not specified. + */ + protected Html getAnnotation() { + return html; + } + + /** + * Returns the {@link Html#asXml()} annotation defined on the class. + * + * @return The value of the {@link Html#asXml()} annotation. + */ + protected boolean isAsXml() { + return asXml; + } + + /** + * Returns the {@link Html#asPlainText()} annotation defined on the class. + * + * @return The value of the {@link Html#asPlainText()} annotation. + */ + protected boolean isAsPlainText() { + return asPlainText; + } + + /** + * Returns the {@link Html#noTables()} annotation defined on the class. + * + * @return The value of the {@link Html#noTables()} annotation. + */ + protected boolean isNoTables() { + return noTables; + } + + /** + * Returns the {@link Html#noTableHeaders()} annotation defined on the class. + * + * @return The value of the {@link Html#noTableHeaders()} annotation. + */ + public boolean isNoTableHeaders() { + return noTableHeaders; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java new file mode 100644 index 0000000..2c1a0fe --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java @@ -0,0 +1,168 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.html; + +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.dto.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.serializer.*; + +/** + * Serializes POJOs to HTTP responses as HTML documents. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/html</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/html</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Same as {@link HtmlSerializer}, except wraps the response in <code><xt><html></code>, <code><xt><head></code>, + * and <code><xt><body></code> tags so that it can be rendered in a browser. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link HtmlDocSerializerContext} + * <li>{@link BeanContext} + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@Produces("text/html") +public class HtmlDocSerializer extends HtmlStrippedDocSerializer { + + // Properties defined in RestServletProperties + private static final String + REST_method = "RestServlet.method", + REST_relativeServletURI = "RestServlet.relativeServletURI"; + + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + public HtmlDocSerializerSession createSession(Object output, ObjectMap properties, Method javaMethod) { + return new HtmlDocSerializerSession(getContext(HtmlDocSerializerContext.class), getBeanContext(), output, properties, javaMethod); + } + + @Override /* Serializer */ + protected void doSerialize(SerializerSession session, Object o) throws Exception { + + HtmlDocSerializerSession s = (HtmlDocSerializerSession)session; + HtmlWriter w = s.getWriter(); + + ObjectMap properties = s.getProperties(); + + boolean isOptionsPage = properties.containsKey(REST_method) && properties.getString(REST_method).equalsIgnoreCase("OPTIONS"); + + // Render the header. + w.sTag("html").nl(); + w.sTag("head").nl(); + + String cssUrl = s.getCssUrl(); + if (cssUrl == null) + cssUrl = properties.getString(REST_relativeServletURI) + "/style.css"; + + w.oTag(1, "style") + .attr("type", "text/css") + .appendln(">") + .append(2, "@import ").q().append(cssUrl).q().appendln(";"); + if (s.isNoWrap()) + w.appendln("\n* {white-space:nowrap;}"); + if (s.getCssImports() != null) + for (String cssImport : s.getCssImports()) + w.append(2, "@import ").q().append(cssImport).q().appendln(";"); + w.eTag(1, "style").nl(); + w.eTag("head").nl(); + w.sTag("body").nl(); + // Write the title of the page. + String title = s.getTitle(); + if (title == null && isOptionsPage) + title = "Options"; + String description = s.getDescription(); + if (title != null) + w.oTag(1, "h3").attr("class", "title").append('>').encodeText(title).eTag("h3").nl(); + if (description != null) + w.oTag(1, "h5").attr("class", "description").append('>').encodeText(description).eTag("h5").nl(); + + // Write the action links that render above the results. + List<Link> actions = new LinkedList<Link>(); + + // If this is an OPTIONS request, provide a 'back' link to return to the GET request page. + if (! isOptionsPage) { + Map<String,String> htmlLinks = s.getLinks(); + if (htmlLinks != null) { + for (Map.Entry<String,String> e : htmlLinks.entrySet()) { + String uri = e.getValue(); + if (uri.indexOf("://") == -1 && ! StringUtils.startsWith(uri, '/')) { + StringBuilder sb = new StringBuilder(properties.getString(REST_relativeServletURI)); + if (! (uri.isEmpty() || uri.charAt(0) == '?' || uri.charAt(0) == '/')) + sb.append('/'); + sb.append(uri); + uri = sb.toString(); + } + + actions.add(new Link(e.getKey(), uri)); + } + } + } + + if (actions.size() > 0) { + w.oTag(1, "p").attr("class", "links").append('>').nl(); + for (Iterator<Link> i = actions.iterator(); i.hasNext();) { + Link h = i.next(); + w.oTag(2, "a").attr("class", "link").attr("href", h.getHref(), true).append('>').append(h.getName()).eTag("a").nl(); + if (i.hasNext()) + w.append(3, " - ").nl(); + } + w.eTag(1, "p").nl(); + } + + s.indent = 3; + + // To allow for page formatting using CSS, we encapsulate the data inside two div tags: + // <div class='outerdata'><div class='data' id='data'>...</div></div> + w.oTag(1, "div").attr("class","outerdata").append('>').nl(); + w.oTag(2, "div").attr("class","data").attr("id", "data").append('>').nl(); + if (isEmptyList(o)) + w.oTag(3, "p").append('>').append("no results").eTag("p"); + else + super.doSerialize(s, o); + w.eTag(2, "div").nl(); + w.eTag(1, "div").nl(); + + w.eTag("body").nl().eTag("html").nl(); + } + + private boolean isEmptyList(Object o) { + if (o == null) + return false; + if (o instanceof Collection && ((Collection<?>)o).size() == 0) + return true; + if (o.getClass().isArray() && Array.getLength(o) == 0) + return true; + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java new file mode 100644 index 0000000..53c0742 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java @@ -0,0 +1,199 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.html; + +import java.util.*; + +import org.apache.juneau.*; + +/** + * Properties associated with the {@link HtmlDocSerializer} class. + * <p> + * These are typically specified via <ja>@RestResource.properties()</ja> and <ja>@RestMethod.properties()</ja> annotations, + * although they can also be set programmatically via the <code>RestResponse.setProperty()</code> method. + * + * <h6 class='topic'>Example</h6> + * <p class='bcode'> + * <ja>@RestResource</ja>( + * messages=<js>"nls/AddressBookResource"</js>, + * properties={ + * <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_title</jsf>, value=<js>"$L{title}"</js>), + * <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_description</jsf>, value=<js>"$L{description}"</js>), + * <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_links</jsf>, value=<js>"{options:'?method=OPTIONS',doc:'doc'}"</js>) + * } + * ) + * <jk>public class</jk> AddressBookResource <jk>extends</jk> RestServletJenaDefault { + * </p> + * <p> + * The <code>$L{...}</code> variable represent localized strings pulled from the resource bundle identified by the <code>messages</code> annotation. + * These variables are replaced at runtime based on the HTTP request locale. + * Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables. + * </p> + * + * @author James Bognar ([email protected]) + */ +public final class HtmlDocSerializerContext extends HtmlSerializerContext { + + /** + * Adds a title at the top of a page. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p> + * The <code>AddressBookResource</code> sample class uses this property... + * </p> + * <p class='bcode'> + * <ja>@RestResource</ja>( + * messages=<js>"nls/AddressBookResource"</js>, + * properties={ + * <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_title</jsf>, value=<js>"$L{title}"</js>) + * } + * ) + * <jk>public class</jk> AddressBookResource <jk>extends</jk> RestServletJenaDefault { + * </p> + * <p> + * ...with this property in <code>AddressBookResource.properties</code>... + * </p> + * <p class='bcode'> + * title = <js>AddressBook sample resource</js> + * </p> + * <p> + * ...to produce this title on the HTML page... + * </p> + * <img class='bordered' src='doc-files/HTML_TITLE.png'> + * </dd> + * </dl> + */ + public static final String HTMLDOC_title = "HtmlSerializer.title"; + + /** + * Adds a description right below the title of a page. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p> + * The <code>AddressBookResource</code> sample class uses this property... + * </p> + * <p class='bcode'> + * <ja>@RestResource</ja>( + * messages=<js>"nls/AddressBookResource"</js>, + * properties={ + * <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_description</jsf>, value=<js>"description"</js>, type=<jsf>NLS</jsf>) + * } + * ) + * <jk>public class</jk> AddressBookResource <jk>extends</jk> RestServletJenaDefault { + * </p> + * <p> + * ...with this property in <code>AddressBookResource.properties</code>... + * </p> + * <p class='bcode'> + * description = <js>Simple address book POJO sample resource</js> + * </p> + * <p> + * ...to produce this description on the HTML page... + * </p> + * <img class='bordered' src='doc-files/HTML_DESCRIPTION.png'> + * </dd> + * </dl> + */ + public static final String HTMLDOC_description = "HtmlSerializer.description"; + + /** + * Adds a list of hyperlinks immediately under the title and description but above the content of the page. + * <p> + * This can be used to provide convenient hyperlinks when viewing the REST interface from a browser. + * <p> + * The value is a JSON object string where the keys are anchor text and the values are URLs. + * <p> + * Relative URLs are considered relative to the servlet path. + * For example, if the servlet path is <js>"http://localhost/myContext/myServlet"</js>, and the + * URL is <js>"foo"</js>, the link becomes <js>"http://localhost/myContext/myServlet/foo"</js>. + * Absolute (<js>"/myOtherContext/foo"</js>) and fully-qualified (<js>"http://localhost2/foo"</js>) URLs + * can also be used. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p> + * The <code>AddressBookResource</code> sample class uses this property... + * </p> + * <p class='bcode'> + * <ja>@RestResource</ja>( + * messages=<js>"nls/AddressBookResource"</js>, + * properties={ + * <ja>@Property</ja>(name=HtmlDocSerializerContext.<jsf>HTMLDOC_links</jsf>, value=<js>"{options:'?method=OPTIONS',doc:'doc'}"</js>) + * } + * ) + * <jk>public class</jk> AddressBookResource <jk>extends</jk> RestServletJenaDefault { + * </p> + * <p> + * ...to produce this list of links on the HTML page... + * </p> + * <img class='bordered' src='doc-files/HTML_LINKS.png'> + * </dd> + * </dl> + */ + public static final String HTMLDOC_links = "HtmlDocSerializer.links"; + + /** + * Similar to {@link #HTMLDOC_links} except appends on to the existing list of links. + */ + public static final String HTMLDOC_links_add = "HtmlDocSerializer.links.add"; + + /** + * Adds a link to the specified stylesheet URL (<l>String</l>, default=<jk>null</jk>). + * <p> + * If not specified, defaults to the built-in stylesheet located at <js>"/servletPath/style.css"</js>. + * Note that this stylesheet is controlled by the <code><ja>@RestResource</ja>.style()</code> annotation. + */ + public static final String HTMLDOC_cssUrl = "HtmlDocSerializer.cssUrl"; + + /** + * Imports the specified CSS page URLs into the page (<l>String[]</l>, default=<code>[]</code>). + */ + public static final String HTMLDOC_cssImports = "HtmlDocSerializer.cssImports"; + + /** + * Append to the {@link #HTMLDOC_cssImports} property. + */ + public static final String HTMLDOC_cssImports_add = "HtmlDocSerializer.cssImports.add"; + + /** + * Adds <js>"* {white-space:nowrap}"</js> to the style header to prevent word wrapping. + */ + public static final String HTMLDOC_nowrap = "HtmlDocSerializer.nowrap"; + + final String[] cssImports; + final Map<String,String> links; + final String title, description, cssUrl; + final boolean nowrap; + + /** + * Constructor. + * <p> + * Typically only called from {@link ContextFactory#getContext(Class)}. + * + * @param cf The factory that created this context. + */ + public HtmlDocSerializerContext(ContextFactory cf) { + super(cf); + cssImports = cf.getProperty(HTMLDOC_cssImports, String[].class, new String[0]); + title = cf.getProperty(HTMLDOC_title, String.class, null); + description = cf.getProperty(HTMLDOC_description, String.class, null); + cssUrl = cf.getProperty(HTMLDOC_cssUrl, String.class, null); + nowrap = cf.getProperty(HTMLDOC_nowrap, boolean.class, false); + links = cf.getMap(HTMLDOC_links, String.class, String.class, Collections.<String,String>emptyMap()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java new file mode 100644 index 0000000..476f6c5 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java @@ -0,0 +1,135 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.html; + +import static org.apache.juneau.html.HtmlDocSerializerContext.*; + +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.json.*; +import org.apache.juneau.serializer.*; + +/** + * Context object that lives for the duration of a single serialization of {@link HtmlSerializer} and its subclasses. + * <p> + * See {@link SerializerContext} for details. + * </p> + * <p> + * This class is NOT thread safe. It is meant to be discarded after one-time use. + * </p> + * + * @author James Bognar ([email protected]) + */ +public final class HtmlDocSerializerSession extends HtmlSerializerSession { + + private final String title, description, cssUrl; + private final String[] cssImports; + private final Map<String,String> links; + private final boolean nowrap; + + /** + * Create a new session using properties specified in the context. + * + * @param ctx The context creating this session object. + * The context contains all the configuration settings for this object. + * @param beanContext The bean context being used. + * @param output The output object. See {@link JsonSerializerSession#getWriter()} for valid class types. + * @param op The override properties. + * These override any context properties defined in the context. + * @param javaMethod The java method that called this parser, usually the method in a REST servlet. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected HtmlDocSerializerSession(HtmlDocSerializerContext ctx, BeanContext beanContext, Object output, ObjectMap op, Method javaMethod) { + super(ctx, beanContext, output, op, javaMethod); + if (op == null || op.isEmpty()) { + title = ctx.title; + description = ctx.description; + links = ctx.links; + cssUrl = ctx.cssUrl; + cssImports = ctx.cssImports; + nowrap = ctx.nowrap; + } else { + title = op.getString(HTMLDOC_title, ctx.title); + description = op.getString(HTMLDOC_description, ctx.description); + links = new LinkedHashMap(op.getMap(HTMLDOC_links, ctx.links)); + cssUrl = op.getString(HTMLDOC_cssUrl, ctx.cssUrl); + cssImports = StringUtils.split(op.getString(HTMLDOC_cssImports, null), ','); + nowrap = op.getBoolean(HTMLDOC_cssUrl, ctx.nowrap); + } + } + + /** + * Returns the {@link HtmlDocSerializerContext#HTMLDOC_title} setting value in this context. + * + * @return The {@link HtmlDocSerializerContext#HTMLDOC_title} setting value in this context. + */ + public final String getTitle() { + return title; + } + + /** + * Returns the {@link HtmlDocSerializerContext#HTMLDOC_description} setting value in this context. + * + * @return The {@link HtmlDocSerializerContext#HTMLDOC_description} setting value in this context. + */ + public final String getDescription() { + return description; + } + + /** + * Returns the {@link HtmlDocSerializerContext#HTMLDOC_links} setting value in this context. + * + * @return The {@link HtmlDocSerializerContext#HTMLDOC_links} setting value in this context. + */ + public final Map<String,String> getLinks() { + return links; + } + + /** + * Returns the {@link HtmlDocSerializerContext#HTMLDOC_cssUrl} setting value in this context. + * + * @return The {@link HtmlDocSerializerContext#HTMLDOC_cssUrl} setting value in this context. + */ + public final String getCssUrl() { + return cssUrl; + } + + /** + * Returns the {@link HtmlDocSerializerContext#HTMLDOC_cssImports} setting value in this context. + * + * @return The {@link HtmlDocSerializerContext#HTMLDOC_cssImports} setting value in this context. + */ + public final String[] getCssImports() { + return cssImports; + } + + /** + * Returns the {@link HtmlDocSerializerContext#HTMLDOC_nowrap} setting value in this context. + * + * @return The {@link HtmlDocSerializerContext#HTMLDOC_nowrap} setting value in this context. + */ + public final boolean isNoWrap() { + return nowrap; + } + + @Override /* XmlSerializerSession */ + public HtmlWriter getWriter() throws Exception { + Object output = getOutput(); + if (output instanceof HtmlWriter) + return (HtmlWriter)output; + return new HtmlWriter(super.getWriter(), isUseIndentation(), isTrimStrings(), getQuoteChar(), getRelativeUriBase(), getAbsolutePathUriBase()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/html/HtmlLink.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlLink.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlLink.java new file mode 100644 index 0000000..87f2954 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlLink.java @@ -0,0 +1,49 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.html; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.*; + +/** + * Used in conjunction with the {@link HtmlSerializer} class to define hyperlinks. + * <p> + * This annotation is applied to classes. + * <p> + * Annotation that can be used to specify that a class has a URL associated with it. + * <p> + * When rendered using the {@link org.apache.juneau.html.HtmlSerializer HtmlSerializer} class, this class will get rendered as a hyperlink like so... + * <p class='code'> + * <xt><a</xt> <xa>href</xa>=<xs>'hrefProperty'</xs><xt>></xt>nameProperty<xt></a></xt> + * </p> + * + * @author James Bognar ([email protected]) + */ +@Documented +@Target(TYPE) +@Retention(RUNTIME) +@Inherited +public @interface HtmlLink { + + /** + * The bean property whose value becomes the name in the hyperlink. + */ + String nameProperty() default ""; + + /** + * The bean property whose value becomes the url in the hyperlink. + */ + String hrefProperty() default ""; +}
