{"id":1871,"date":"2012-01-14T21:43:55","date_gmt":"2012-01-14T21:43:55","guid":{"rendered":"http:\/\/dalelane.co.uk\/blog\/?p=1871"},"modified":"2012-01-17T20:45:39","modified_gmt":"2012-01-17T20:45:39","slug":"generating-a-list-of-rest-apis-in-jax-rs","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=1871","title":{"rendered":"Generating a list of REST APIs in JAX-RS"},"content":{"rendered":"<p><strong>Overview<\/strong> <\/p>\n<p>Using Java Reflection to generate a list of REST endpoints defined in JAX-RS code<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/dalelane.co.uk\/blog\/post-images\/120114-datatables.jpg\" vspace=\"10\"\/><\/p>\n<p><strong>Background &#8211; JAX-RS<\/strong><\/p>\n<p>I&#8217;ve been working on a project that uses <a href=\"http:\/\/en.wikipedia.org\/wiki\/JAX-RS\">JAX-RS<\/a> &#8211; the Java API for RESTful web services. If you don&#8217;t know JAX-RS, you write web services in Java using annotations to specify what REST endpoint a Java method implements. <\/p>\n<p>For example, you can use <code>@Path<\/code> annotations on a class to define the root URI for methods in the class, and then use annotations like <code>@GET<\/code>, <code>@Produces(MediaType.APPLICATION_JSON)<\/code> and <code>@Path<\/code> on the individual class methods to define the endpoints that they implement. <\/p>\n<p><strong>The problem?<\/strong><\/p>\n<p>Reading from code to the web service is straightforward enough. By which I mean, if I&#8217;m looking at a Java method, it&#8217;s easy enough to look at it and know what endpoint it is implementing. <\/p>\n<p>Going the other way can be a little trickier. <\/p>\n<p>Once a project gets bigger, you can have REST endpoints spread around a large number of classes. And methods can inherit attributes from other classes than the one they&#8217;re in, through annotations like <code>@Parent<\/code>.<\/p>\n<p>What if I&#8217;m using one of the project&#8217;s REST APIs, and want to look at the source for the method that&#8217;s handling it, whether to extend it or fix a bug? How can I remember which method in which class is responsible for the REST endpoint I&#8217;m using? <\/p>\n<p><strong>Using Reflection<\/strong><\/p>\n<p>Documentation is one way. As I develop the code, maintain a list of the mapping of Java methods to web services endpoints. And keep that up-to-date as I make any changes to the code. <\/p>\n<p>But that&#8217;s very manual, and doesn&#8217;t seem very smart. <\/p>\n<p>This got me thinking yesterday evening. I&#8217;d not used Java Reflection before, but thought it must be possible to work it out from the Java annotations in the same way that my JAX-RS provider must. <\/p>\n<p>So I spent a bit of time trying it out and thought it might be useful to share what I came up with. It&#8217;s not terribly elegant or efficient. It&#8217;s the result of a few hours tinkering. But it shows the basic idea, and that seems useful enough to warrant sharing.<\/p>\n<p><!--more--><strong>What I came up with<\/strong><\/p>\n<p>I&#8217;ve shared the source below, but in short, I&#8217;ve written a small stand-alone Java app that uses Reflection to search through my code for methods with annotations indicating a web services endpoint. <\/p>\n<p>Once it finds them all, it creates a document with the basic information about them &#8211; as a sortable, searchable HTML table, using the <a href=\"http:\/\/datatables.net\/\">DataTables<\/a> jQuery plugin. <\/p>\n<p>(If you&#8217;ve not used DataTables before, it&#8217;s simple and very cool. It lets you turn a static HTML table into something that supports filtering and sorting without needing anything server-side. I&#8217;m creating a table modelled on <a href=\"http:\/\/datatables.net\/release-datatables\/extras\/FixedColumns\/row_grouping_height.html\">one of their examples<\/a>. You should give it a try.)<\/p>\n<p><strong>What can I do with it<\/strong><\/p>\n<p>This gives me a complete list of all the endpoints in my project, together with the Java class and method name that implements them. <\/p>\n<p><img decoding=\"async\" src=\"http:\/\/dalelane.co.uk\/blog\/post-images\/120114-all.jpg\" vspace=\"10\"\/><\/p>\n<p>Creating the list in an HTML table using jQuery and DataTables gave me a few extra tricks:<\/p>\n<p>It&#8217;s sortable &#8211; I can click on a column heading to sort by URI, or by the REST method, or by Java class, etc. \u00e2\u20ac\u00a6<\/p>\n<p>It&#8217;s searchable &#8211; typing a query into the search box above the table will filter the contents of the table. <\/p>\n<p>For example, I can type in the URI that I&#8217;m interested in, and everything vanishes apart from the relevant endpoints. <\/p>\n<p><img decoding=\"async\" src=\"http:\/\/dalelane.co.uk\/blog\/post-images\/120114-searchforapi.jpg\" vspace=\"10\"\/><\/p>\n<p>Or I can type in DELETE to see just the DELETE endpoints I&#8217;ve implemented. <\/p>\n<p><img decoding=\"async\" src=\"http:\/\/dalelane.co.uk\/blog\/post-images\/120114-searchfordelete.jpg\" vspace=\"10\"\/><\/p>\n<p>Or, because I&#8217;ve included stuff like payload and the query and path parameters in the table, I can type in something like <code>sortOptions<\/code> to filter the table to show the endpoints that support a sortOptions parameter &#8211; to show me which of my web services APIs support searching. <\/p>\n<p><img decoding=\"async\" src=\"http:\/\/dalelane.co.uk\/blog\/post-images\/120114-searchforsortable.jpg\" vspace=\"10\"\/><br \/>\nYou get the idea. <\/p>\n<p>Not sure how useful it&#8217;ll turn out to be, but it was an interesting thing to play with.<\/p>\n<p><strong>The source<\/strong><\/p>\n<pre style=\"border: thin solid silver; background-color: #eeeeee; padding: 0.7em; font-size: 1.1em; overflow: auto;\">import java.io.BufferedWriter;\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.FileWriter;\r\nimport java.io.IOException;\r\nimport java.lang.annotation.Annotation;\r\nimport java.lang.reflect.Method;\r\nimport java.net.URL;\r\nimport java.util.ArrayList;\r\nimport java.util.Enumeration;\r\nimport java.util.List;\r\n\r\nimport javax.ws.rs.DELETE;\r\nimport javax.ws.rs.DefaultValue;\r\nimport javax.ws.rs.GET;\r\nimport javax.ws.rs.POST;\r\nimport javax.ws.rs.PUT;\r\nimport javax.ws.rs.Path;\r\nimport javax.ws.rs.PathParam;\r\nimport javax.ws.rs.QueryParam;\r\nimport javax.ws.rs.core.Request;\r\n\r\nimport org.apache.wink.common.annotations.Parent;\r\n\r\n\/**\r\n * Explores the Java classes in a given package, looking for annotations \r\n *  indicating REST endpoints. These are written to an HTML table, documenting\r\n *  basic information about all the known endpoints.\r\n * \r\n * @author Dale Lane (dale.lane@gmail.com)\r\n *\/\r\npublic class RESTEndpointsDocumenter {\r\n\r\n    public static void main(String[] args){\r\n        RESTEndpointsDocumenter red = new RESTEndpointsDocumenter();\r\n        try {\r\n            \/\/ the root package where Java classes implementing web services\r\n            \/\/  endpoints can be found - the place to start the search from\r\n            String packagename = \"uk.co.dalelane.myproject.rest\";\r\n            \r\n            \/\/ the file location where the HTML table listing endpoints \r\n            \/\/  information will be written\r\n            \/\/ this should be in a directory that already exists, and contains\r\n            \/\/  the unzipped contents of \r\n            \/\/  http:\/\/dalelane.co.uk\/files\/120114-datatables-assets.zip\r\n            String destinationHtmlPath = \"\/path\/to\/my\/doc\/folder\/index.html\";\r\n            \r\n            List&lt;Endpoint&gt; endpoints = red.findRESTEndpoints(packagename);\r\n            File endpointsDoc = red.outputEndpointsTable(endpoints, destinationHtmlPath);\r\n            System.out.println(\"Complete. Written to \" + endpointsDoc.getAbsolutePath());\r\n        } \r\n        catch (IOException e) {\r\n            e.printStackTrace();\r\n        } \r\n        catch (ClassNotFoundException e) {\r\n            e.printStackTrace();\r\n        }\r\n    }\r\n    \r\n    \r\n    \/**\r\n     * Writes the provided REST endpoints to an HTML file.\r\n     *\/\r\n    public File outputEndpointsTable(List&lt;Endpoint&gt; endpoints, String htmlpath) throws IOException {\r\n        File docfile = new File(htmlpath);\r\n        checkHtmlAssetFiles(docfile.getAbsoluteFile().getParentFile());\r\n        \r\n        FileWriter fstream = new FileWriter(docfile);\r\n        BufferedWriter out = new BufferedWriter(fstream);\r\n        \r\n        out.write(\"&lt;!DOCTYPE HTML PUBLIC \\\"-\/\/W3C\/\/DTD HTML 4.01\/\/EN\\\" \\\"http:\/\/www.w3.org\/TR\/html4\/strict.dtd\\\"&gt;\" + NEWLINE);\r\n        out.write(\"&lt;html&gt;\");\r\n        out.write(\"&lt;head&gt;\");\r\n        out.write(\"&lt;style type=\\\"text\/css\\\"&gt;\" + NEWLINE);\r\n        out.write(\"@import \\\"demo_page.css\\\";\" + NEWLINE);\r\n        out.write(\"@import \\\"header.ccss\\\";\" + NEWLINE);\r\n        out.write(\"@import \\\"demo_table.css\\\";\" + NEWLINE);\r\n        out.write(\"&lt;\/style&gt;\" + NEWLINE);\r\n        out.write(\"&lt;script type=\\\"text\/javascript\\\" charset=\\\"utf-8\\\" src=\\\"jquery.js\\\"&gt;&lt;\/script&gt;\" + NEWLINE);\r\n        out.write(\"&lt;script type=\\\"text\/javascript\\\" charset=\\\"utf-8\\\" src=\\\"jquery.dataTables.js\\\"&gt;&lt;\/script&gt;\" + NEWLINE);\r\n        out.write(\"&lt;script type=\\\"text\/javascript\\\" charset=\\\"utf-8\\\" src=\\\"FixedColumns.js\\\"&gt;&lt;\/script&gt;\" + NEWLINE);\r\n        out.write(\"&lt;script type=\\\"text\/javascript\\\" charset=\\\"utf-8\\\" src=\\\"RowGroupingWithFixedColumn.js\\\"&gt;&lt;\/script&gt;\" + NEWLINE);\r\n        out.write(\"&lt;\/head&gt;\" + NEWLINE);\r\n        out.write(\"&lt;body id=\\\"dt_example\\\"&gt;\" + NEWLINE);\r\n        out.write(\"&lt;table cellpadding=\\\"0\\\" cellspacing=\\\"0\\\" border=\\\"0\\\" class=\\\"display\\\" id=\\\"endpoints\\\"&gt;\" + NEWLINE);\r\n        out.write(\"&lt;thead&gt;&lt;tr&gt;&lt;th&gt;URI&lt;\/th&gt;&lt;th&gt;REST&lt;\/th&gt;&lt;th&gt;java class&lt;\/th&gt;&lt;th&gt;method&lt;\/th&gt;&lt;th&gt;parameters&lt;\/th&gt;&lt;\/tr&gt;&lt;\/thead&gt;\" + NEWLINE);\r\n        out.write(\"&lt;tbody&gt;\" + NEWLINE);\r\n        for (Endpoint endpoint : endpoints) {\r\n            switch(endpoint.method){\r\n                case GET:    out.write(\"&lt;tr class='gradeA'&gt;\"); break;\r\n                case PUT:    out.write(\"&lt;tr class='gradeC'&gt;\"); break;\r\n                case POST:   out.write(\"&lt;tr class='gradeU'&gt;\"); break;\r\n                case DELETE: out.write(\"&lt;tr class='gradeX'&gt;\"); break;\r\n                default: out.write(\"&lt;tr&gt;\");\r\n            }\r\n            out.write(\"&lt;td&gt;\" + endpoint.uri + \"&lt;\/td&gt;\");\r\n            out.write(\"&lt;td&gt;\" + endpoint.method + \"&lt;\/td&gt;\");\r\n            out.write(\"&lt;td&gt;\" + endpoint.javaClass + \"&lt;\/td&gt;\");\r\n            out.write(\"&lt;td&gt;\" + endpoint.javaMethodName + \"&lt;\/td&gt;\");\r\n            out.write(\"&lt;td&gt;\");\r\n            for (EndpointParameter parameter : endpoint.pathParameters) {\r\n                out.write(\"path {\" + parameter.name + \"}  (\" + parameter.javaType + \")&lt;br\/&gt;\");                \r\n            }\r\n            for (EndpointParameter parameter : endpoint.queryParameters) {\r\n                out.write(\"query: \" + parameter.name + \" (\" + parameter.javaType + \") \");\r\n                if (parameter.defaultValue != null){\r\n                    out.write(\"default = \\\"\" + parameter.defaultValue + \"\\\"\");\r\n                }\r\n                out.write(\"&lt;br\/&gt;\");\r\n            }\r\n            for (EndpointParameter parameter : endpoint.payloadParameters) {\r\n                out.write(\"payload : \" + parameter.javaType + \"&lt;br\/&gt;\");\r\n            }\r\n            out.write(\"&lt;\/td&gt;\");\r\n            out.write(\"&lt;\/tr&gt;\" + NEWLINE);\r\n        }\r\n        out.write(\"&lt;\/tbody&gt;\");\r\n        out.write(\"&lt;\/table&gt;\");\r\n        out.write(\"&lt;\/body&gt;&lt;\/html&gt;\");\r\n        \r\n        out.close();\r\n        fstream.close();\r\n        return docfile;\r\n    }\r\n    \r\n    \r\n    \/**\r\n     * Verifies that the JS and CSS files required by the HTML table are present.\r\n     *\/\r\n    private void checkHtmlAssetFiles(File directory) throws FileNotFoundException {\r\n        if (directory.exists() == false){\r\n            throw new FileNotFoundException(directory.getAbsolutePath());\r\n        }\r\n        File rowgroupingjs = new File(directory, \"RowGroupingWithFixedColumn.js\");\r\n        if (rowgroupingjs.exists() == false){\r\n            throw new FileNotFoundException(rowgroupingjs.getAbsolutePath());\r\n        }\r\n        File fixedcolumnsplugin = new File(directory, \"FixedColumns.js\");\r\n        if (fixedcolumnsplugin.exists() == false){\r\n            throw new FileNotFoundException(fixedcolumnsplugin.getAbsolutePath());\r\n        }\r\n        File pagecss = new File(directory, \"demo_page.css\");\r\n        if (pagecss.exists() == false){\r\n            throw new FileNotFoundException(pagecss.getAbsolutePath());\r\n        }\r\n        File tablecss = new File(directory, \"demo_table.css\");\r\n        if (tablecss.exists() == false){\r\n            throw new FileNotFoundException(tablecss.getAbsolutePath());\r\n        }\r\n        File headercss = new File(directory, \"header.ccss\");\r\n        if (headercss.exists() == false){\r\n            throw new FileNotFoundException(headercss.getAbsolutePath());\r\n        }\r\n        File datatablesjs = new File(directory, \"jquery.dataTables.js\");\r\n        if (datatablesjs.exists() == false){\r\n            throw new FileNotFoundException(datatablesjs.getAbsolutePath());\r\n        }\r\n        File jqueryjs = new File(directory, \"jquery.js\");\r\n        if (jqueryjs.exists() == false){\r\n            throw new FileNotFoundException(jqueryjs.getAbsolutePath());\r\n        }\r\n    }\r\n    \r\n    \/**\r\n     * Returns REST endpoints defined in Java classes in the specified package.\r\n     *\/\r\n    @SuppressWarnings(\"rawtypes\")\r\n    public List&lt;Endpoint&gt; findRESTEndpoints(String basepackage) throws IOException, ClassNotFoundException {\r\n        List&lt;Endpoint&gt; endpoints = new ArrayList&lt;Endpoint&gt;();\r\n        \r\n        List&lt;Class&gt; classes = getClasses(basepackage);\r\n\r\n        for (Class&lt;?&gt; clazz : classes) {\r\n            Annotation annotation = clazz.getAnnotation(Path.class);\r\n            if (annotation != null) {\r\n                \r\n                String basePath = getRESTEndpointPath(clazz);                    \r\n                Method[] methods = clazz.getMethods();\r\n                for (Method method : methods) {\r\n                    if (method.isAnnotationPresent(GET.class)){\r\n                        endpoints.add(createEndpoint(method, MethodEnum.GET, clazz, basePath));\r\n                    }\r\n                    else if (method.isAnnotationPresent(PUT.class)){\r\n                        endpoints.add(createEndpoint(method, MethodEnum.PUT, clazz, basePath));                          \r\n                    }\r\n                    else if (method.isAnnotationPresent(POST.class)){\r\n                        endpoints.add(createEndpoint(method, MethodEnum.POST, clazz, basePath));                           \r\n                    }\r\n                    else if (method.isAnnotationPresent(DELETE.class)){\r\n                        endpoints.add(createEndpoint(method, MethodEnum.DELETE, clazz, basePath));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        \r\n        return endpoints;\r\n    }\r\n    \r\n    \r\n    \/**\r\n     * Create an endpoint object to represent the REST endpoint defined in the \r\n     *  specified Java method.\r\n     *\/\r\n    private Endpoint createEndpoint(Method javaMethod, MethodEnum restMethod, Class&lt;?&gt; clazz, String classUri){\r\n        Endpoint newEndpoint = new Endpoint();\r\n        newEndpoint.method = restMethod;\r\n        newEndpoint.javaMethodName = javaMethod.getName();\r\n        newEndpoint.javaClass = clazz.getName();\r\n        \r\n        Path path = javaMethod.getAnnotation(Path.class);\r\n        if (path != null){\r\n            newEndpoint.uri = classUri + path.value();\r\n        }\r\n        else {\r\n            newEndpoint.uri = classUri;\r\n        }\r\n        discoverParameters(javaMethod, newEndpoint);\r\n        return newEndpoint;\r\n    }\r\n    \r\n    \/**\r\n     * Get the parameters for the specified endpoint from the provided java method.\r\n     *\/\r\n    @SuppressWarnings(\"rawtypes\")\r\n    private void discoverParameters(Method method, Endpoint endpoint){\r\n\r\n        Annotation[][] annotations = method.getParameterAnnotations();\r\n        Class[] parameterTypes = method.getParameterTypes();\r\n\r\n        for (int i=0; i &lt; parameterTypes.length; i++){\r\n            Class parameter = parameterTypes[i];\r\n            \r\n            \/\/ ignore parameters used to access context\r\n            if ((parameter == Request.class) || \r\n                (parameter == javax.servlet.http.HttpServletResponse.class) ||\r\n                (parameter == javax.servlet.http.HttpServletRequest.class)){\r\n                continue;\r\n            }\r\n            \r\n            EndpointParameter nextParameter = new EndpointParameter();\r\n            nextParameter.javaType = parameter.getName();\r\n            \r\n            Annotation[] parameterAnnotations = annotations[i];\r\n            for (Annotation annotation : parameterAnnotations) {\r\n                if (annotation instanceof PathParam){\r\n                    nextParameter.parameterType = ParameterType.PATH;\r\n                    PathParam pathparam = (PathParam)annotation;\r\n                    nextParameter.name = pathparam.value();\r\n                }\r\n                else if (annotation instanceof QueryParam) {\r\n                    nextParameter.parameterType = ParameterType.QUERY;\r\n                    QueryParam queryparam = (QueryParam)annotation;\r\n                    nextParameter.name = queryparam.value();\r\n                }\r\n                else if (annotation instanceof DefaultValue) {\r\n                    DefaultValue defaultvalue = (DefaultValue)annotation;\r\n                    nextParameter.defaultValue = defaultvalue.value();\r\n                }\r\n            }\r\n            \r\n            switch (nextParameter.parameterType){\r\n                case PATH:\r\n                    endpoint.pathParameters.add(nextParameter);\r\n                    break;\r\n                case QUERY:\r\n                    endpoint.queryParameters.add(nextParameter);\r\n                    break;\r\n                case PAYLOAD:\r\n                    endpoint.payloadParameters.add(nextParameter);\r\n                    break;\r\n            }\r\n        }\r\n    }\r\n    \r\n    \/**\r\n     * Get the REST endpoint path for the specified class. This involves \r\n     *  (recursively) looking for @Parent annotations and getting the path for\r\n     *  that class before appending the location in the @Path annotation.\r\n     *\/\r\n    private String getRESTEndpointPath(Class&lt;?&gt; clazz){\r\n        String path = \"\";\r\n        while (clazz != null){            \r\n            Annotation annotation = clazz.getAnnotation(Path.class);\r\n            if (annotation != null){\r\n                path = ((Path)annotation).value() + path;\r\n            }\r\n            \r\n            Annotation parent = clazz.getAnnotation(Parent.class);\r\n            if (parent != null){\r\n                clazz = ((Parent)parent).value();\r\n            }\r\n            else {\r\n                clazz = null;\r\n            }\r\n        }\r\n        if (path.endsWith(\"\/\") == false){\r\n            path = path + \"\/\";\r\n        }\r\n        return path;\r\n    }\r\n    \r\n    \r\n    \/**\r\n     * Returns all of the classes in the specified package (including sub-packages).\r\n     *\/\r\n    @SuppressWarnings(\"rawtypes\")\r\n    private List&lt;Class&gt; getClasses(String pkg) throws IOException, ClassNotFoundException {\r\n        ClassLoader classloader = Thread.currentThread().getContextClassLoader();\r\n        \/\/ turn package into the folder equivalent\r\n        String path = pkg.replace('.', '\/');\r\n        Enumeration&lt;URL&gt; resources = classloader.getResources(path);\r\n        List&lt;File&gt; dirs = new ArrayList&lt;File&gt;();\r\n        while (resources.hasMoreElements()) {\r\n            URL resource = resources.nextElement();\r\n            dirs.add(new File(resource.getFile()));\r\n        }\r\n        ArrayList&lt;Class&gt; classes = new ArrayList&lt;Class&gt;();\r\n        for (File directory : dirs) {\r\n            classes.addAll(getClasses(directory, pkg));\r\n        }\r\n        return classes;\r\n    }\r\n\r\n    \/**\r\n     * Returns a list of all the classes from the package in the specified\r\n     *  directory. Calls itself recursively until no more directories are found. \r\n     *\/\r\n    @SuppressWarnings(\"rawtypes\")\r\n    private List&lt;Class&gt; getClasses(File dir, String pkg) throws ClassNotFoundException {\r\n        List&lt;Class&gt; classes = new ArrayList&lt;Class&gt;();\r\n        if (!dir.exists()) {\r\n            return classes;\r\n        }\r\n        File[] files = dir.listFiles();\r\n        for (File file : files) {\r\n            if (file.isDirectory()) {\r\n                classes.addAll(getClasses(file, pkg + \".\" + file.getName()));\r\n            } else if (file.getName().endsWith(\".class\")) {\r\n                classes.add(Class.forName(pkg + '.' + file.getName().substring(0, file.getName().length() - 6)));\r\n            }\r\n        }\r\n        return classes;\r\n    }\r\n    \r\n    \r\n    \r\n    \/\/\r\n    \/\/ used to store the collection of attributes for a web services endpoint\r\n    \/\/\r\n    \r\n    static final String NEWLINE = System.getProperty(\"line.separator\");\r\n    \r\n    enum MethodEnum { PUT, POST, GET, DELETE }\r\n    enum ParameterType { QUERY, PATH, PAYLOAD }\r\n    \r\n    public class Endpoint {\r\n        String uri;\r\n        MethodEnum method;\r\n        \r\n        String javaClass;\r\n        String javaMethodName;\r\n        \r\n        List&lt;EndpointParameter&gt; queryParameters = new ArrayList&lt;RESTEndpointsDocumenter.EndpointParameter&gt;();\r\n        List&lt;EndpointParameter&gt; pathParameters = new ArrayList&lt;RESTEndpointsDocumenter.EndpointParameter&gt;();\r\n        List&lt;EndpointParameter&gt; payloadParameters = new ArrayList&lt;RESTEndpointsDocumenter.EndpointParameter&gt;();        \r\n    }\r\n\r\n    public class EndpointParameter {\r\n        ParameterType parameterType = ParameterType.PAYLOAD;\r\n        String javaType;\r\n        String defaultValue;\r\n        String name;\r\n    }\r\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Overview Using Java Reflection to generate a list of REST endpoints defined in JAX-RS code Background &#8211; JAX-RS I&#8217;ve been working on a project that uses JAX-RS &#8211; the Java API for RESTful web services. If you don&#8217;t know JAX-RS, you write web services in Java using annotations to specify what REST endpoint a Java [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[510,219,508,509],"class_list":["post-1871","post","type-post","status-publish","format-standard","hentry","category-code","tag-apache-wink","tag-java","tag-jax-rs","tag-reflection"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1871","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1871"}],"version-history":[{"count":0,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1871\/revisions"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1871"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1871"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}