JAX-RS webservice using #Path("/") at class level fails on TomEE+ 1.5.1 - cxf

This simple root resource class fails when running on TomEE+ 1.5.1, return is a 404 error "The requested resource is not available." It is packaged as a WAR with and empty web.xml file in WEB-INF.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
#Path("/")
#Produces({ "text/plain", "text/html", "text/xml", "application/xml", "application/json" })
public class BrokenResource {
private static final Log logger = LogFactory.getLog(BrokenResource.class);
public BrokenResource()
{
logger.info("constructed");
}
#GET
#Path("getloggedinguids")
public Response foo(#Context UriInfo uInfo)
{
return Response.ok("\n\n\nrequest URI = " + uInfo.getRequestUri()).build();
}
}
It deploys and I see these lines in the console output:
Mar 22, 2013 4:56:16 PM org.apache.openejb.server.rest.RESTService deployPojo
INFO: REST Service: http://localhost:8080/test//* -> Pojo BrokenResource
I have also tried packaging it with a web.xml that declares:
<servlet>
<servlet-name>test</servlet-name>
<display-name>Test OpenEJBRest Servlet</display-name>
<servlet-class>
org.apache.openejb.server.rest.OpenEJBRestServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Same results as with an empty web.xml file.
I also tried changing the class level #Path annotation to #Path("*") and then I get a different response, in the console output I see the following line:
WARNING: No root resource matching request path /test/getloggedinguids has been found, Relative Path: /getloggedinguids.
Finally... if I change the #Path annotation to something like #Path("/foo") then that works exactly as expected... hitting /test/foo/getloggedinguids returns the request URI..
I've be trying all sorts of variations of servlet mapping versus class level annotations and one thing is consistent... a #Path("/") annotation at the class leve always fails.
I even grabbed an example from the "RESTful Java with JAX-RS book" that uses the #Path("/") at the class level... that also fails.
Is this a bug in the CXF implementation of the JAX-RS standard? My project requires that I use a stock TomEE+ container so I can't switch to another JAX-RS implementation :-(

Instead of adding entries into the web.xml, keep in mind TomEE has native support for JAX-RS if you use the JAX-RS or TomEE+ versions.
Add a class like this and remove your web.xml entries:
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/")
public class NoteTakerApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
return new HashSet<Class<?>>(Arrays.asList(BrokenResource.class, RestExceptionMapper.class, OtherClassesYouMayHave.class));
}
}

Related

Keycloak and Spring Oauth2 - CORS issue [duplicate]

I am using keycloak to secure my rest service. I am refering to the tutorial given here. I created the rest and front end. Now when I add keycloak on the backend I get CORS error when my front end makes api call.
Application.java file in spring boot looks like
#SpringBootApplication
public class Application
{
public static void main( String[] args )
{
SpringApplication.run(Application.class, args);
}
#Bean
public WebMvcConfigurer corsConfiguration() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/*")
.allowedMethods(HttpMethod.GET.toString(), HttpMethod.POST.toString(),
HttpMethod.PUT.toString(), HttpMethod.DELETE.toString(), HttpMethod.OPTIONS.toString())
.allowedOrigins("*");
}
};
}
}
The keycloak properties in the application.properties file look like
keycloak.realm = demo
keycloak.auth-server-url = http://localhost:8080/auth
keycloak.ssl-required = external
keycloak.resource = tutorial-backend
keycloak.bearer-only = true
keycloak.credentials.secret = 123123-1231231-123123-1231
keycloak.cors = true
keycloak.securityConstraints[0].securityCollections[0].name = spring secured api
keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = admin
keycloak.securityConstraints[0].securityCollections[0].authRoles[1] = user
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /api/*
The sample REST API that I am calling
#RestController
public class SampleController {
#RequestMapping(value ="/api/getSample",method=RequestMethod.GET)
public string home() {
return new string("demo");
}
}
the front end keycloak.json properties include
{
"realm": "demo",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "tutorial-frontend",
"public-client": true
}
The CORS error that I get
XMLHttpRequest cannot load http://localhost:8090/api/getSample. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access. The response had HTTP status code 401.
I know.. the Problem is quite Old.
But if you've Problems with the local development with Spring Boot + Keycloak you can use the Config
keycloak.cors=true
in your application.properties.
Cheers :)
Try creating your CORS bean like my example. I recently went through the same thing (getting CORS to work) and it was a nightmare because the SpringBoot CORS support is currently not as robust or straightforward as the MVC CORS.
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
This is how I set it up to accept any origin application-wide, but if you change a few of the parameters you should be able to replicate what you want. ie. if you wanted to add only the methods you mentioned, chain some addAllowedMethod(). Allowed origins would be the same, and then your addMapping("/api/*") would become source.registerCorsConfiguration("/api/*", config);.
Edit:
Spring Data Rest and Cors
Take a look at this. Sebastian is on the Spring engineering team so this is about as good as you're going to get for an official answer.
I came here with the same problem and fix it ommiting authentication for OPTIONS method only, like this:
keycloak.securityConstraints[0].security-collections[0].omitted-methods[0]=OPTIONS
It worked for me because the OPTIONS request Keycloack does, does not include Authentication header.
UPDATE
There was something with my browser's cache so I could not see the real impact of a change in my backend code. It looks like what really worked for me was enabling all CORS origins at #RestController level, like this:
#CrossOrigin(origins = "*")
#RestController
public class UsersApi {...}
I don't have access to code examples, but based on the code configurations you have included, it looks like a missing configuration is causing spring to exclude CORS headers.
J. West's response is similar to recent issues I encountered with Spring and CORS, I would however caution you to look into which implementation a spring example references, because there are two. Spring Security and Spring MVC Annotations. Both of these implementations work independent of each other, and can not be combined.
When using the filter based approach as you are (even boiled down), the key was to set allow credentials to true, in order for the authentication headers to be sent by the browser across domains. I would also advise using the full code method proposed above, as this will allow you to create a far more configurable web application for deployment across multiple domains or environments by property injection or a service registry.
Access-Control-Allow-Origin header is supposed to be set by the server application basis the Origin request header provided in the request to the server application. Usually browsers set the Origin header in request whenever they sense a cross origin request being made. And they expect a Access-Control-Allow-Origin header in response to allow it.
Now, for keycloak, I struggled with the same issue. Looking at this, it seems like keycloak does not add Access-Control-Allow-Origin header in case of error response. However, for me it was not adding this header in the response even in case of success response.
Looking into the code and adding breakpoints, I noticed that the webOrigin for client object was not getting populated from the Origin header even if passed and hence CORS was not adding the access control response header.
I was able to get it working by adding the following line of code just before the CORS build call:
client.addWebOrigin(headers.getRequestHeader("Origin").get(0));
before:
Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
Once I built the code with this change and started the server, I started getting the three access control response headers:
Access-Control-Expose-Headers: Access-Control-Allow-Methods
Access-Control-Allow-Origin: http://localhost:9000
Access-Control-Allow-Credentials: true
I am using client credentials grant type; hence i added it only in the buildClientCredentialsGrant at TokenEndpoint.java#L473.
I still need to do some more code diving in order to say for sure that it is a bug for success responses at well and to find a better place to set this on the client object in keycloak code (like where client object is being constructed)
You are welcome to give it a try.
UPDATE:
I take this back. I re-registered my client in keycloak with Root URL as http://localhost:9000 (which is my front-end's application port) and i started getting the proper access control response headers. Hope this helps you.
I know the problem is too old but, I found better solution.
Read more at official documentation
Inside your application.yml file
keycloak:
auth-server-url: http://localhost:8180/auth
realm: CollageERP
resource: collage-erp-web
public-client: true
use-resource-role-mappings: true
cors: true
cors-max-age: 0
principal-attribute: preferred_username
cors-allowed-methods: POST, PUT, DELETE, GET
cors-allowed-headers: X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers
or you can config using application.properties file
keycloak.auth-server-url= http://localhost:8180/auth
keycloak.realm= CollageERP
keycloak.resource= collage-erp-web
keycloak.public-client= true
keycloak.use-resource-role-mappings= true
keycloak.cors= true
keycloak.cors-max-age= 0
keycloak.principal-attribute= preferred_username
keycloak.cors-allowed-methods= POST, PUT, DELETE, GET
keycloak.cors-allowed-headers= X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers
and my java adaper class
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import javax.ws.rs.HttpMethod;
#KeycloakConfiguration
#EnableGlobalMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors().and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/api/**")
.authenticated()
.anyRequest().permitAll();
http.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
I want to share with you the solution that worked for me hoping to help whoever is facing the same issue. I am going to give you two solutions actually.
Spring reactive:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Autowired
private ReactiveClientRegistrationRepository clientRegistrationRepository;
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
CorsConfiguration cors_config = new CorsConfiguration();
cors_config.setAllowCredentials(true);
cors_config.applyPermitDefaultValues();
cors_config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "null"));
cors_config.setAllowedMethods(List.of("GET","POST","OPTIONS","DELETE"));
cors_config.setAllowedHeaders(List.of("*"));
http.cors().configurationSource(source -> cors_config)
.and()
.csrf().disable()
.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
.oauth2Login()//Setting Oauth2Login
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("")).and()
.logout(logout -> logout //Setting Oauth2Logout
.logoutHandler(logoutHandler())
.logoutSuccessHandler(oidcLogoutSuccessHandler()));
return http.build();
}
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
// Sets the location that the End-User's User Agent will be redirected to
// after the logout has been performed at the Provider
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("");
return oidcLogoutSuccessHandler;
}
private DelegatingServerLogoutHandler logoutHandler() {
//Invalidate session on logout
return new DelegatingServerLogoutHandler(
new SecurityContextServerLogoutHandler(), new WebSessionServerLogoutHandler());
}
}
Spring MVC:
#Configuration
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
CorsConfiguration cors_config = new CorsConfiguration();
cors_config.setAllowCredentials(true);
cors_config.applyPermitDefaultValues();
cors_config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "null"));
cors_config.setAllowedMethods(List.of("GET","POST","OPTIONS","DELETE"));
cors_config.setAllowedHeaders(List.of("*"));
http.cors().configurationSource(source -> cors_config).and()...
return http.build();
}
}
Be sure to have cors enabled on Keycloak too, navigate to
realm->clients->settings->weborigins
and submit your permitted origins.
If you are sending credentials or cookies in your requests, be sure to configure it, for example, if you are using ReactJS:
const httpConfig = { withCredentials: true };
axios.get('YourUrl', httpConfig)
.then(response => {})
.catch(error => {})
.finally(() => {});
When your client is sending an Authentication header, you cannot use
allowedOrigins("*"). You must configure a specific origin URL.
Since you have set the property keycloak.cors = true in your application.properties file, you have to mention the CORS enabled origins in the Keycloak server. To do that follow the below steps.
Go to Clients -> Select the client (Token owner) -> Settings -> Web Origins
Add origins one by one or add * to allow all.
After doing this you have to get a new token. (If you decode the token you will see your origins as allowed-origins": ["*"])
Setting the property keycloak.cors = false is another option. But this completely disables CORS.

Unitils EasyMock and JUnit #Rule from JUnit 4.10 to 4.11

I have the Problem that when I use the #RuleAnnotation from JUnit for my TemporaryFolder and want to use Mocks from unitils.easymock at the same time I get an IlleagalStateException in JUnit 4.11, whereas in JUnit 4.10 it still works.
So the following test runs under JUnit 4.10 and throws the IllegalStateException in 4.11:
import java.io.File;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.unitils.UnitilsJUnit4;
public class MyTest extends UnitilsJUnit4 {
#Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
#Test
public void testSomething() throws IOException {
File newFile = temporaryFolder.newFile();
}
}
Even if I use the Annotation for the Mocking capability instead of extends UnitilsJUnit4 it doesn't work in JUnit 4.11:
#RunWith(UnitilsJUnit4TestClassRunner.class)
public class MyTest {
...
}
The error message when testing this code is:
java.lang.IllegalStateException: the temporary folder has not yet been created
Something new I just additionally found out: In JUnit 4.10 I can also enforce the same error when passing a String in the newFile() call:
File newFile = temporaryFolder.newFile("");
My question:
What is the proper way to make TemporaryFolders or #Rules in general work together with unitils.easymock.annotation.Mocks in JUnit 4.11?
Or is Mocking with the easymock #Mock annotation and #Rules at the same time simply not possible?
Versions:
easymock 3.4
unitils 3.4.3

GWT Upload fails to App Engine

I want to provide a file upload to Google App Engine with the "GWT Upload" (https://code.google.com/p/gwtupload/). During the upload I get an error. As UploadAction servlet I use the build in: gwtupload.server.gae.AppEngineUploadAction
The servlet is configured in the web.xml in the following way:
<context-param>
<!-- max size of the upload request -->
<param-name>maxSize</param-name>
<param-value>3145728</param-value>
</context-param>
<context-param>
<!-- Useful in development mode to slow down the uploads in fast networks.
Put the number of milliseconds to sleep in each block received in the server.
false or 0, means don't use slow uploads -->
<param-name>slowUploads</param-name>
<param-value>200</param-value>
</context-param>
<servlet>
<servlet-name>uploadServlet</servlet-name>
<!-- This is the default servlet, it puts files in session -->
<servlet-class>gwtupload.server.gae.AppEngineUploadAction</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>uploadServlet</servlet-name>
<url-pattern>*.gupld</url-pattern>
</servlet-mapping>
During upload the progress bar progresses some percentages and then shows the following error:
But there are no more details in the logs.
The error message shows the class gwtupload.server.gae.MemCacheFileItemFactory$CacheableFileItem with the method setHeader(). That's strange because I can't find the method in that class. What's happening here?
Edit:
This is basically all the custom code i use. On the server side i use the build in gwtupload.server.gae.AppEngineUploadAction servlet.
package com.uploadtest.client;
import gwtupload.client.IUploadStatus.Status;
import gwtupload.client.IUploader;
import gwtupload.client.IUploader.UploadedInfo;
import gwtupload.client.MultiUploader;
import gwtupload.client.PreloadedImage;
import gwtupload.client.PreloadedImage.OnLoadPreloadedImageHandler;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RootPanel;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class GWTUploadTest2 implements EntryPoint {
// A panel where the thumbnails of uploaded images will be shown
private FlowPanel panelImages = new FlowPanel();
public void onModuleLoad() {
// Attach the image viewer to the document
RootPanel.get("thumbnails").add(panelImages);
// Create a new uploader panel and attach it to the document
MultiUploader defaultUploader = new MultiUploader();
RootPanel.get("default").add(defaultUploader);
// Add a finish handler which will load the image once the upload finishes
defaultUploader.addOnFinishUploadHandler(onFinishUploaderHandler);
}
// Load the image in the document and in the case of success attach it to the viewer
private IUploader.OnFinishUploaderHandler onFinishUploaderHandler = new IUploader.OnFinishUploaderHandler() {
public void onFinish(IUploader uploader) {
if (uploader.getStatus() == Status.SUCCESS) {
new PreloadedImage(uploader.fileUrl(), showImage);
// The server sends useful information to the client by default
UploadedInfo info = uploader.getServerInfo();
System.out.println("File name " + info.name);
System.out.println("File content-type " + info.ctype);
System.out.println("File size " + info.size);
// You can send any customized message and parse it
System.out.println("Server message " + info.message);
}
}
};
// Attach an image to the pictures viewer
private OnLoadPreloadedImageHandler showImage = new OnLoadPreloadedImageHandler() {
public void onLoad(PreloadedImage image) {
image.setWidth("75px");
panelImages.add(image);
}
};
}
In addition to that i added the following jars to my clath path:
log4j-1.2.17.jar
gwtupload-gae-0.6.6.jar
gwtupload-0.6.6.jar
commons-fileupload-1.3.jar
commons-io-2.4.jar
Also zipped my whole sample project and uploaded it here:
https://skydrive.live.com/redir?resid=60B826E451F52B4D!118&authkey=!ALa1n2mL2sRR0wU
Edit 2:
Like Manolo pointed out: I was using "commons-fileupload-1.3.jar" instead of "commons-fileupload-1.2.1.jar". Changing the jar fixed my problem!
The problem is in the version of the commons-fileupload you are using, change it to the version 1.2.1, which is the one pointed in the gwtupload documentation.
It should work with 1.2.2 as well, but to use 1.3 requires new methods (setHeaders) which are not in the UploadListeners provided with gwtupload.
You should change in your project the target java (JDK compliance) to 1.6, since it is the last one supported in GWT to avoid problems, although it runs in 1.7.

Selenium how to open a web browser to run a selenium script

I exported a script from selenium IDE 1.9.0 as Java/TestNG/RemoteControl.
I would like to run this script using TestNG within Eclipse and I want to see the script play back in Firefox browser.
I did some search online, but I could not make it work. I need some instructions and guidance on how to make the code work. Your help is greatly appreciated.
Here is my code:
import java.util.List;
import org.testng.annotations.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import static org.testng.Assert.*;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.AfterTest;
public class search_donor_suzy_ng //extends SeleneseTestNgHelper {
#BeforeTest
public void setUp() throws Exception {
//initizlize Firefoxbrowser:
WebDriver ffoxdriver = new FirefoxDriver();
String baseUrl = "www.google.com"; //sample URL
}
#Test
public void testSearch_donor_suzy_ng() throws Exception {
// set overall speed of the test case
selenium.setSpeed("4000");
selenium.open("/?html=openid");
selenium.click("css=input[type=\"submit\"]");
selenium.waitForPageToLoad("30000");
Thread.sleep(4000);
selenium.type("id=edit-name", "jeffshu");
selenium.type("id=edit-pass", "tEgvz9xsaNjnwe4Y");
selenium.click("id=edit-submit");
selenium.waitForPageToLoad("30000");
Thread.sleep(4000);
selenium.click("id=cmp_admin");
selenium.waitForPageToLoad("30000");
selenium.click("id=quicksearch_anchor");
selenium.click("css=img[alt=\"Member\"]");
selenium.waitForPageToLoad("30000");
selenium.type("id=search_name", "suzy");
selenium.click("css=input[type=\"image\"]");
selenium.click("link=Balagia, Suzy");
selenium.waitForPageToLoad("30000");
}
#AfterTest
public void tearDown() throws Exception {
ffoxdriver.quit();
}
}
For starters, you do need to refer to the documentation # http://docs.seleniumhq.org/docs/03_webdriver.jsp
In your code, you are intializing the driver object in your beforetest method, which you are not using. Driver is Webdriver's way of launching your browser, whereas in your testmethod you are using selenium and selenium 1 commands. As a quick step, you can replace your selenium with driver (put your declaration of driver in class scope and method scope). SeleneseTestngHelper is also selenium1.
Make sure you have the necessary webdriver jars in your project.
Certain commands in webdriver are different than selenium 1 and you might see compile erros when you do the replacement. Look at the methods available with driver. and use corresponding commands eg. Use get() of webdriver instead of open(). You can refer the javadocs for this or use your ide to get to know these.

What is the URL to access a Hello-World Google Cloud Endpoint service?

I've generated a Google Endpoint AppEngine project in Eclipse by using the Generate AppEngine BackEnd as described in this blog post. What that post does not describe however, and which the official Google Docs describe poorly as well, is which URL I can access that service with locally?
The service generated has one generated endpoint called DeviceInfoEndpoint. The code is shown below as well as the code in web.xml. Which URL should I access listDeviceInfo() with given that I'm hosting on port 8888 locally? I've tried the following:
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/listDeviceInfo => 404
http://localhost:8888/_ah/spi/deviceinfoendpoint/v1/listDeviceInfo => 405 GET not supported
http://localhost:8888/_ah/spi/deviceinfoendpoint/v1/DeviceInfo => 405 GET (...)
http://localhost:8888/_ah/spi/v1/deviceinfoendpoint/listDeviceInfo = > 405 GET(...)
Exerpt of DeviceInfoEndpoint.java:
#Api(name = "deviceinfoendpoint")
public class DeviceInfoEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method.
*
* #return List of all entities persisted.
*/
#SuppressWarnings({ "cast", "unchecked" })
public List<DeviceInfo> listDeviceInfo() {
EntityManager mgr = getEntityManager();
List<DeviceInfo> result = new ArrayList<DeviceInfo>();
try {
Query query = mgr
.createQuery("select from DeviceInfo as DeviceInfo");
for (Object obj : (List<Object>) query.getResultList()) {
result.add(((DeviceInfo) obj));
}
} finally {
mgr.close();
}
return result;
}
}
Web.xml:
<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>SystemServiceServlet</servlet-name>
<servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.example.dummyandroidapp.DeviceInfoEndpoint</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SystemServiceServlet</servlet-name>
<url-pattern>/_ah/spi/*</url-pattern>
</servlet-mapping>
</web-app>
API request paths should generally conform to the following:
http(s)://{API_HOST}:{PORT}/_ah/api/{API_NAME}/{VERSION}/
If you're interested in fetching/updating/deleting a specific resource, add an ID to the end. In your example, that suggests you should be querying:
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/
(which maps to list when you're making a GET request).
In general, the APIs Explorer available at /_ah/_api/explorer makes it easy to discover and query these URLs.
You can controle the path by use:
#ApiMethod(path="listDeviceInfo", httpMethod = HttpMethod.GET)
public List<DeviceInfo> listDeviceInfo(){
//... definition
}
Then you can call that from you client as:
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/listDeviceInfo
If you like send parameters then:
#ApiMethod(path="listDeviceInfo", httpMethod = HttpMethod.GET)
public List<DeviceInfo> listDeviceInfo(#Named("info") String info){
//... definition
}
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/listDeviceInfo?info=holamundo

Resources