@OnReceive annotation can specify method and data class
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 09 May 2013 09:14:10 +0200
changeset 70450a456930cb
parent 69 603f8d95cb51
child 71 59b6a8d5f11b
@OnReceive annotation can specify method and data class
json/src/main/java/net/java/html/json/OnReceive.java
json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java
json/src/main/resources/org/apidesign/html/json/impl/Bundle.properties
json/src/test/java/net/java/html/json/Compile.java
json/src/test/java/net/java/html/json/ModelProcessorTest.java
     1.1 --- a/json/src/main/java/net/java/html/json/OnReceive.java	Tue May 07 16:30:45 2013 +0200
     1.2 +++ b/json/src/main/java/net/java/html/json/OnReceive.java	Thu May 09 09:14:10 2013 +0200
     1.3 @@ -93,4 +93,27 @@
     1.4       *    callback function.
     1.5       */
     1.6      String jsonp() default "";
     1.7 +    
     1.8 +    /** The model class to be send to the server as JSON data.
     1.9 +     * By default no data are sent. However certain {@link #method() transport methods}
    1.10 +     * (like <code>"PUT"</code> and <code>"POST"</code>) require the 
    1.11 +     * data to be specified.
    1.12 +     * 
    1.13 +     * @return name of a class generated using {@link Model @Model} annotation
    1.14 +     * @since 0.3
    1.15 +     */
    1.16 +    Class<?> data() default Object.class;
    1.17 +    
    1.18 +    /** The HTTP transfer method to use. Defaults to <code>"GET"</code>.
    1.19 +     * Other typical methods include <code>"HEAD"</code>, 
    1.20 +     * <code>"DELETE"</code>, <code>"POST"</code>, <code>"PUT"</code>.
    1.21 +     * The last two mentioned methods require {@link #data()} to be specified.
    1.22 +     * <p>
    1.23 +     * When {@link #jsonp() JSONP} transport is requested, the method 
    1.24 +     * has to be <code>"GET"</code>.
    1.25 +     * 
    1.26 +     * @return name of the HTTP transfer method
    1.27 +     * @since 0.3
    1.28 +     */
    1.29 +    String method() default "GET";
    1.30  }
     2.1 --- a/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Tue May 07 16:30:45 2013 +0200
     2.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Thu May 09 09:14:10 2013 +0200
     2.3 @@ -28,6 +28,7 @@
     2.4  import java.lang.annotation.IncompleteAnnotationException;
     2.5  import java.lang.reflect.Method;
     2.6  import java.util.ArrayList;
     2.7 +import java.util.Arrays;
     2.8  import java.util.Collection;
     2.9  import java.util.Collections;
    2.10  import java.util.HashMap;
    2.11 @@ -35,9 +36,13 @@
    2.12  import java.util.LinkedHashSet;
    2.13  import java.util.List;
    2.14  import java.util.Map;
    2.15 +import java.util.ResourceBundle;
    2.16  import java.util.Set;
    2.17  import java.util.WeakHashMap;
    2.18 +import java.util.logging.Logger;
    2.19  import javax.annotation.processing.AbstractProcessor;
    2.20 +import javax.annotation.processing.Completion;
    2.21 +import javax.annotation.processing.Completions;
    2.22  import javax.annotation.processing.ProcessingEnvironment;
    2.23  import javax.annotation.processing.Processor;
    2.24  import javax.annotation.processing.RoundEnvironment;
    2.25 @@ -70,8 +75,8 @@
    2.26  import net.java.html.json.Property;
    2.27  import org.openide.util.lookup.ServiceProvider;
    2.28  
    2.29 -/** Annotation processor to process an XHTML page and generate appropriate 
    2.30 - * "id" file.
    2.31 +/** Annotation processor to process {@link Model @Model} annotations and
    2.32 + * generate appropriate model classes.
    2.33   *
    2.34   * @author Jaroslav Tulach <jtulach@netbeans.org>
    2.35   */
    2.36 @@ -86,6 +91,7 @@
    2.37      "net.java.html.json.Property"
    2.38  })
    2.39  public final class ModelProcessor extends AbstractProcessor {
    2.40 +    private static final Logger LOG = Logger.getLogger(ModelProcessor.class.getName());
    2.41      private final Map<Element,String> models = new WeakHashMap<>();
    2.42      private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
    2.43      @Override
    2.44 @@ -719,6 +725,14 @@
    2.45                  error("@OnReceive method should return void", e);
    2.46                  return false;
    2.47              }
    2.48 +            if ("PUT".equals(onR.method()) && !isDataSpecified(onR)) {
    2.49 +                error("PUT method needs to specify a data() class", e);
    2.50 +                return false;
    2.51 +            }
    2.52 +            if ("POST".equals(onR.method()) && !isDataSpecified(onR)) {
    2.53 +                error("POST method needs to specify a data() class", e);
    2.54 +                return false;
    2.55 +            }
    2.56              String modelClass = null;
    2.57              boolean expectsList = false;
    2.58              List<String> args = new ArrayList<>();
    2.59 @@ -1162,6 +1176,14 @@
    2.60              return s;
    2.61          }
    2.62      }
    2.63 +
    2.64 +    private boolean isDataSpecified(OnReceive onR) {
    2.65 +        try {
    2.66 +            return onR.data() != Object.class;
    2.67 +        } catch (MirroredTypeException ex) {
    2.68 +            return !ex.getTypeMirror().toString().equals("java.lang.Object"); // NOI18N
    2.69 +        }
    2.70 +    }
    2.71      
    2.72      private static class Prprt {
    2.73          private final Element e;
    2.74 @@ -1244,5 +1266,31 @@
    2.75              }
    2.76          }
    2.77      }
    2.78 +
    2.79 +    @Override
    2.80 +    public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
    2.81 +        LOG.info(" element: " + element);
    2.82 +        LOG.info(" annotation: " + annotation);
    2.83 +        LOG.info(" member: " + member);
    2.84 +        LOG.info(" userText: " + userText);
    2.85 +        LOG.info("str: " + annotation.getAnnotationType().toString());
    2.86 +        if (annotation.getAnnotationType().toString().equals(OnReceive.class.getName())) {
    2.87 +            if (member.getSimpleName().contentEquals("method")) {
    2.88 +                return Arrays.asList(
    2.89 +                    methodOf("GET"),
    2.90 +                    methodOf("POST"),
    2.91 +                    methodOf("PUT"),
    2.92 +                    methodOf("DELETE"),
    2.93 +                    methodOf("HEAD")
    2.94 +                );
    2.95 +            }
    2.96 +        }
    2.97 +        
    2.98 +        return super.getCompletions(element, annotation, member, userText);
    2.99 +    }
   2.100      
   2.101 +    private static final Completion methodOf(String method) {
   2.102 +        ResourceBundle rb = ResourceBundle.getBundle("org.apidesign.html.json.impl.Bundle");
   2.103 +        return Completions.of('"' + method + '"', rb.getString("MSG_Completion_" + method));
   2.104 +    }
   2.105  }
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/json/src/main/resources/org/apidesign/html/json/impl/Bundle.properties	Thu May 09 09:14:10 2013 +0200
     3.3 @@ -0,0 +1,75 @@
     3.4 +#
     3.5 +# HTML via Java(tm) Language Bindings
     3.6 +# Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 +#
     3.8 +# This program is free software: you can redistribute it and/or modify
     3.9 +# it under the terms of the GNU General Public License as published by
    3.10 +# the Free Software Foundation, version 2 of the License.
    3.11 +#
    3.12 +# This program is distributed in the hope that it will be useful,
    3.13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 +# GNU General Public License for more details. apidesign.org
    3.16 +# designates this particular file as subject to the
    3.17 +# "Classpath" exception as provided by apidesign.org
    3.18 +# in the License file that accompanied this code.
    3.19 +#
    3.20 +# You should have received a copy of the GNU General Public License
    3.21 +# along with this program. Look for COPYING file in the top folder.
    3.22 +# If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    3.23 +#
    3.24 +
    3.25 +MSG_Completion_GET=The GET method means retrieve whatever information \
    3.26 + (in the form of an entity) is identified by the Request-URI. \
    3.27 + If the Request-URI refers to a data-producing process, \
    3.28 + it is the produced data which shall be returned as the entity in \
    3.29 + the response and not the source text of the process, \
    3.30 + unless that text happens to be the output of the process.
    3.31 +
    3.32 +MSG_Completion_HEAD=The HEAD method is identical to GET except that the server \
    3.33 + MUST NOT return a message-body in the response. The metainformation \
    3.34 + contained in the HTTP headers in response to a HEAD request SHOULD be \
    3.35 + identical to the information sent in response to a GET request. \
    3.36 + This method can be used for obtaining metainformation about the entity implied \
    3.37 + by the request without transferring the entity-body itself. \
    3.38 + This method is often used for testing hypertext links for validity, \
    3.39 + accessibility, and recent modification.
    3.40 +
    3.41 +MSG_Completion_POST=The POST method is used to request that the origin server \
    3.42 + accept the entity enclosed in the request as a new subordinate of the resource \
    3.43 + identified by the Request-URI in the Request-Line. POST is designed to allow \
    3.44 + a uniform method to cover annotation of existing resources,\ 
    3.45 + posting a message to a bulletin board, newsgroup, mailing list, or similar \
    3.46 + group of articles, providing a block of data, such as the result of submitting a \
    3.47 + form, to a data-handling process or extending a database through an append operation. \
    3.48 + The actual function performed by the POST method is determined by the server \
    3.49 + and is usually dependent on the Request-URI. The posted entity is subordinate \
    3.50 + to that URI in the same way that a file is subordinate to a directory containing it, \
    3.51 + a news article is subordinate to a newsgroup to which it is posted, \
    3.52 + or a record is subordinate to a database.
    3.53 +
    3.54 +MSG_Completion_PUT=The PUT method requests that the enclosed entity be stored \
    3.55 + under the supplied Request-URI. If the Request-URI refers to an already \
    3.56 + existing resource, the enclosed entity SHOULD be considered as a modified \
    3.57 + version of the one residing on the origin server. If the Request-URI does \
    3.58 + not point to an existing resource, and that URI is capable of being defined \
    3.59 + as a new resource by the requesting user agent, the origin server can \
    3.60 + create the resource with that URI. If a new resource is created, the origin \
    3.61 + server MUST inform the user agent via the 201 (Created) response. \
    3.62 + If an existing resource is modified, either the 200 (OK) or 204 (No Content) \
    3.63 + response codes SHOULD be sent to indicate successful completion of the request. \
    3.64 + If the resource could not be created or modified with the Request-URI, an \
    3.65 + appropriate error response SHOULD be given that reflects the nature of the problem. \
    3.66 + The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) \
    3.67 + headers that it does not understand or implement and MUST return \
    3.68 + a 501 (Not Implemented) response in such cases.
    3.69 +
    3.70 +MSG_Completion_DELETE=The DELETE method requests that the origin server delete \
    3.71 + the resource identified by the Request-URI. This method MAY be overridden \
    3.72 + by human intervention (or other means) on the origin server. The client \
    3.73 + cannot be guaranteed that the operation has been carried out, even if \
    3.74 + the status code returned from the origin server indicates that the action \
    3.75 + has been completed successfully. However, the server SHOULD NOT indicate \
    3.76 + success unless, at the time the response is given, it intends to delete \
    3.77 + the resource or move it to an inaccessible location.
    3.78 +
     4.1 --- a/json/src/test/java/net/java/html/json/Compile.java	Tue May 07 16:30:45 2013 +0200
     4.2 +++ b/json/src/test/java/net/java/html/json/Compile.java	Thu May 09 09:14:10 2013 +0200
     4.3 @@ -145,26 +145,7 @@
     4.4                      } catch (URISyntaxException ex) {
     4.5                          throw new IOException(ex);
     4.6                      }
     4.7 -                    return new SimpleJavaFileObject(un/*sibling.toUri()*/, kind) {
     4.8 -                        private final ByteArrayOutputStream data = new ByteArrayOutputStream();
     4.9 -                        @Override
    4.10 -                        public OutputStream openOutputStream() throws IOException {
    4.11 -                            return data;
    4.12 -                        }
    4.13 -
    4.14 -                        @Override
    4.15 -                        public String getName() {
    4.16 -                            return n;
    4.17 -                        }
    4.18 -                        
    4.19 -                        
    4.20 -
    4.21 -                        @Override
    4.22 -                        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
    4.23 -                            data.close();
    4.24 -                            return new String(data.toByteArray());
    4.25 -                        }
    4.26 -                    };
    4.27 +                    return new VirtFO(un/*sibling.toUri()*/, kind, n);
    4.28                  }
    4.29                  
    4.30                  throw new IllegalStateException();
    4.31 @@ -180,7 +161,42 @@
    4.32                  
    4.33                  return null;
    4.34              }
    4.35 -            
    4.36 +
    4.37 +            @Override
    4.38 +            public boolean isSameFile(FileObject a, FileObject b) {
    4.39 +                if (a instanceof VirtFO && b instanceof VirtFO) {
    4.40 +                    return ((VirtFO)a).getName().equals(((VirtFO)b).getName());
    4.41 +                }
    4.42 +                
    4.43 +                return super.isSameFile(a, b);
    4.44 +            }
    4.45 +
    4.46 +            class VirtFO extends SimpleJavaFileObject {
    4.47 +
    4.48 +                private final String n;
    4.49 +
    4.50 +                public VirtFO(URI uri, Kind kind, String n) {
    4.51 +                    super(uri, kind);
    4.52 +                    this.n = n;
    4.53 +                }
    4.54 +                private final ByteArrayOutputStream data = new ByteArrayOutputStream();
    4.55 +
    4.56 +                @Override
    4.57 +                public OutputStream openOutputStream() throws IOException {
    4.58 +                    return data;
    4.59 +                }
    4.60 +
    4.61 +                @Override
    4.62 +                public String getName() {
    4.63 +                    return n;
    4.64 +                }
    4.65 +
    4.66 +                @Override
    4.67 +                public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
    4.68 +                    data.close();
    4.69 +                    return new String(data.toByteArray());
    4.70 +                }
    4.71 +            }
    4.72          };
    4.73  
    4.74          ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", sourceLevel, "-target", "1.7"), null, Arrays.asList(file)).call();
     5.1 --- a/json/src/test/java/net/java/html/json/ModelProcessorTest.java	Tue May 07 16:30:45 2013 +0200
     5.2 +++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java	Thu May 09 09:14:10 2013 +0200
     5.3 @@ -77,4 +77,42 @@
     5.4          Compile c = Compile.create(html, code, "1.5");
     5.5          assertTrue(c.getErrors().isEmpty(), "No errors: " + c.getErrors());
     5.6      }
     5.7 +    
     5.8 +    @Test public void putNeedsDataArgument() throws Exception {
     5.9 +        needsAnArg("PUT");
    5.10 +    }
    5.11 +
    5.12 +    @Test public void postNeedsDataArgument() throws Exception {
    5.13 +        needsAnArg("POST");
    5.14 +    }
    5.15 +    
    5.16 +    private void needsAnArg(String method) throws Exception {
    5.17 +        String html = "<html><body>"
    5.18 +            + "</body></html>";
    5.19 +        String code = "package x.y.z;\n"
    5.20 +            + "import net.java.html.json.Model;\n"
    5.21 +            + "import net.java.html.json.Property;\n"
    5.22 +            + "import net.java.html.json.OnReceive;\n"
    5.23 +            + "@Model(className=\"XModel\", properties={\n"
    5.24 +            + "  @Property(name=\"prop\", type=long.class)\n"
    5.25 +            + "})\n"
    5.26 +            + "class X {\n"
    5.27 +            + "  @Model(className=\"PQ\", properties={})\n"
    5.28 +            + "  class PImpl {\n"
    5.29 +            + "  }\n"
    5.30 +            + "  @OnReceive(method=\"" + method + "\", url=\"whereever\")\n"
    5.31 +            + "  static void obtained(XModel m, PQ p) { }\n"
    5.32 +            + "}\n";
    5.33 +        
    5.34 +        Compile c = Compile.create(html, code);
    5.35 +        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
    5.36 +        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
    5.37 +            String msg = diagnostic.getMessage(Locale.ENGLISH);
    5.38 +            if (msg.contains("specify a data()")) {
    5.39 +                return;
    5.40 +            }
    5.41 +        }
    5.42 +        fail("Needs an error message about missing data():\n" + c.getErrors());
    5.43 +        
    5.44 +    }
    5.45  }