I use Objectify 6.0.5, Ktor 1.2.6, com.google.appengine:appengine:1.9.60
I set up web.xml, bootstrapper by tutorial https://github.com/objectify/objectify/wiki/Setup
web.xml
...
<listener>
<listener-class>com.group.Bootstrapper</listener-class>
</listener>
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
Boostrapper
class Bootstrapper : ServletContextListener {
override fun contextInitialized(sce: ServletContextEvent?) {
println(" contextInitialized")
ObjectifyService.init(
ObjectifyFactory()
)
ObjectifyService.register(User::class.java)
}
override fun contextDestroyed(sce: ServletContextEvent?) {}
}
When I call this method
fun save(entity: T) {
ofy().save().entity(entity)
}
I catch error
2020-01-05 17:55:09 ERROR Application:104 - Unhandled: GET - /test
java.lang.IllegalStateException: You must call ObjectifyService.init() before using Objectify
at com.google.common.base.Preconditions.checkState(Preconditions.java:511) ~[guava-28.1-android.jar:?]
at com.googlecode.objectify.ObjectifyService.factory(ObjectifyService.java:34) ~[objectify-6.0.5.jar:?]
at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:51) ~[objectify-6.0.5.jar:?]
at com.group.dao.BaseDao.listAll(BaseDao.kt:15) ~[classes/:?]
at com.group.ApplicationKt$module$2$2.invokeSuspend(Application.kt:50) ~[classes/:?]
at com.group.ApplicationKt$module$2$2.invoke(Application.kt) ~[classes/:?]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:268) ~[ktor-utils-jvm-1.2.6.jar:1.2.6]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141) ~[ktor-utils-jvm-1.2.6.jar:1.2.6]
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161) ~[ktor-utils-jvm-1.2.6.jar:1.2.6]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27) ~[ktor-utils-jvm-1.2.6.jar:1.2.6]
at io.ktor.routing.Routing.executeResult(Routing.kt:147) ~[ktor-server-core-1.2.6.jar:1.2.6]
...
I run datastore using 'gcloud beta emulators datastore start'. It definitely run. May be is not connect to it?
When I run app use appengineRun, datastore also is run?
The Problem is that ktor uses coroutines. Coroutines run in a thread pool by default and the ObjectifyFilter is only active for the thread receiving the request. I use this workaround until I find something better:
get(path) {
withContext(Dispatchers.Unconfined) {
val content = page.doGet()
call.respond(content)
}
}
The Unconfined dispatcher runs the coroutine in the same thread who started it, at least until the first suspend.
Related
My spring boot app uses logback to log messages in json format. The app is configured to use consolelogappender (stdout).When the logs appear in stackdriver, they appear as textPayload instead of jsonPayload. Is it possible to write message to jsonPayload field in stackdriver using logback? If not, what are my options to log in json format?
Based on this Github Link it seems the issue all log entries are seen as textpayload. It has been added as a Feature Request but we do not have an ETA on when it will be available.
I'm not entirely sure if an alternative exist as Logback seems to be giving extensive log information, but if you are able to use the Stackdriver Logging Client instead, you could format the entry in order to get your object as a JsonPayLoad, although you will have specify most of the log categories yourself which can be an extra amount of work.
The easy way to do this, is to implements the transformation of TextPayload(JSON Format) to JSONPayload on the LoggingEnhacer
Check this answer How to use Stackdriver Structured Logging in App Engine Flex Java environment
It is possible via google-cloud-logging-logback library.
However, please note following (from https://cloud.google.com/logging/docs/structured-logging):
Note: message is saved as textPayload if it is the only field remaining
after the Logging agent moves the other special-purpose fields and
detect_json wasn't enabled; otherwise message remains in jsonPayload.
detect_json is not applicable to managed logging environments like
Google Kubernetes Engine.
To add more data to json add an enhancer. Example:
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.google.cloud.logging.LogEntry;
import com.google.cloud.logging.Payload;
import com.google.cloud.logging.logback.LoggingEventEnhancer;
import java.util.HashMap;
public class EventEnhancer implements LoggingEventEnhancer {
#Override
public void enhanceLogEntry(
LogEntry.Builder logEntry,
ILoggingEvent e
) {
HashMap<String, Object> map = new HashMap<>();
map.put("thread", e.getThreadName());
map.put("context", e.getLoggerContextVO().getName());
map.put("logger", e.getLoggerName());
Payload.JsonPayload payload = logEntry.build().getPayload();
map.putAll(payload.getDataAsMap());
logEntry.setPayload(
Payload.JsonPayload.of(map)
);
}
}
Configuration:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration scan="true">
<appender name="CLOUD" class="com.google.cloud.logging.logback.LoggingAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<log>application.log</log>
<redirectToStdout>true</redirectToStdout>
<resourceType>gae_app</resourceType>
<loggingEventEnhancer>EventEnhancer</loggingEventEnhancer>
<flushLevel>INFO</flushLevel>
</appender>
<root level="INFO">
<appender-ref ref="CLOUD"/>
</root>
</configuration>
I've got some Objectify test code running in JUnit and I'm getting this error:
java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method.
at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
at com.googlecode.objectify.impl.ref.LiveRef.<init>(LiveRef.java:31)
at com.googlecode.objectify.Ref.create(Ref.java:26)
at com.googlecode.objectify.Ref.create(Ref.java:32)
at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImpl.create(DownloadTaskRepositoryImpl.java:35)
at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImplTest.setUp(DownloadTaskRepositoryImplTest.java:45)
How do I resolve this for test code?
Jeff Schnitzer answered this here: https://groups.google.com/forum/#!topic/objectify-appengine/8HinahG7irg. That link points to https://groups.google.com/forum/#!topic/objectify-appengine/O4FHC_i7EGk where Jeff suggests the following quick and dirty workaround:
My #BeforeMethod starts an objectify context (ObjectifyService.begin())
My #AfterMethod closes the objectify context
Jeff suggests we use ObjectifyService.run() instead but admits it's more work.
Here's how my implementation looks:
public class DownloadTaskRepositoryImplTest {
// maximum eventual consistency (see https://cloud.google.com/appengine/docs/java/tools/localunittesting)
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
.setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
private Closeable closeable;
#Before
public void setUp() {
helper.setUp();
ObjectifyRegistrar.registerDataModel();
closeable = ObjectifyService.begin();
}
#After
public void tearDown() {
closeable.close();
helper.tearDown();
}
I also had this issue and noticed that I had not added the ObjectifyFilter to my web.xml
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I also had to include Objectify and guava jars in my WEB-INF>lib directory and include them in my build path.
I was facing the same error and this solusion worked for me
I have an app based on Endpoints that uses Objectify. When I leave it with the default/automatic scaling, everything works great. Once I enable basic scaling, though, I get the following exception when executing the endpoint method:
[INFO] java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method.
[INFO] at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
[INFO] at com.myco.myapp.dao.datastore.OfyService.ofy(OfyService.java:62)
The good news is that this goes away when you enable RequestDispatcher
support in the web.xml file like so. I think this is a documentation
issue, then, but I didn't know if everyone would agree if I edited the
Wiki page directly. Here is the proposed web.xml entry, which worked
for me:
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Improving michael-osofsky answer, I add this to my ofy helper class
public static void registerDataModel() {
try {
factory().register(Profile.class);
} catch (Exception e){
e.printStackTrace();
}
}
and remplace
ObjectifyRegistrar.registerDataModel();
for this
OfyService.registerDataModel();
OfyService.java
public static void registerDataModel() {
try {
factory().register(Profile.class);
} catch (Exception e){
e.printStackTrace();
}
}
As Jeff Schnitzer says in the link provided by Michael Osofsky:
In your tests you should have some notion of a 'request' even if it is just conceptual. If "each test is a request by itself", then you can use #Before/#After in conjunction with ObjectifyService.begin() to demarcate the requests. However, this is probably not actually how your tests work - it isn't how my tests work.
He then goes on to say:
This would be prettier with JDK8 closures but the idea is straightforward - you're wrapping some unit of work in a context which represents a request. It would probably be smart to add even more context like authentication in that wrapper too.
I came up with the following implementation of his idea. With the solution below, you can ensure each call to a servlet handler gets a fresh Objectify session while still making your servlet handler calls in a single line of code. It also decouples your tests from explicitly worrying about Objectify, and allows you to add additional non-Objectify context around your servlet handlers.
My solution below works with Objectify 5.1.22. I tried using Objectify 6+, but I had problems that seem to be related to this.
First, define a custom Supplier that is able to capture the exceptions thrown by a servlet handler.
#FunctionalInterface
public interface ServletSupplier<T> {
T get()
throws ServletException, IOException;
}
Next, define a wrapper method that accepts your new custom Supplier as an input, and wrap the call to ServletSupplier.get() in a try-with-resources block that calls ObjectifyService.begin(). You must also register your entity classes before calling ServletSupplier.get().
public <T> T runInServletContext(ServletSupplier<T> servletMethod)
throws ServletException, IOException {
try (Closeable session = ObjectifyService.begin()) {
ObjectifyService.register(MyObj.class);
return servletMethod.get();
}
}
Finally, anywhere in your tests that you call the servlet handler you should do so using the wrapper method.
MyObj myObjPost = runInServletContext(() -> getServlet().doPost(request, response));
// Assert results of doPost call.
MyObj myObjGet = runInServletContext(() -> getServlet().doGet(request, response));
// Assert results of doGet call.
Just in case someone ends up here (as I originally did) looking up the same problem but for the ktor "main.kt" server instead of unit tests...
After looking at the ObjectifyFilter source code, I added
val closer = ObjectifyService.begin()
... real service here ...
closer.close()
around my actual servlet code and that fixed the problem.
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));
}
}
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
My problem is that the error handler code I register does not work on all errors. In my web.xml I registered my default error handler:
<servlet>
<servlet-name>ShowErrorHandlerServlet</servlet-name>
<servlet-class>tools.ErrorHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ShowErrorHandlerServlet</servlet-name>
<url-pattern>/showerror</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/showerror</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/showerror</location>
</error-page>
<error-page>
<location>/showerror</location>
</error-page>
I am using Jersey to map rest request, so e.g. something like this:
#GET
#Path("/throwError")
public void testThrowUnauthException() {
// String s = null;
// s.toString();
// /*
// * this nullpointer exception would be caught correctly because of
// * <exception-type>java.lang.Exception</exception-type> in the web.xml
// */
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
The WebApplicationException is not caught and the default jetty 401 error page is shown:
HTTP ERROR 401
Problem accessing /v1/locations/throwError. Reason:
UNAUTHORIZED
Powered by Jetty
Registering
<error-page>
<error-code>401</error-code>
<location>/showerror</location>
</error-page>
does not work, but it works for 404 errors..
Does someone have an idea why this is happening? I need to catch all errors with the ErrorHandlerServlet to convert them to json error messages.
I think Jersey is overriding Jetty behaviour, so it is not taking the value on the XML. One way to solve it (maybe not the most elegant):
#Context HttpServletResponse res;
#GET
#Produces(MediaType.TEXT_HTML)
public String doFoo(){
try {
//Code that causes the error
} catch (Exception e) {
res.sendRedirect("/showError");
}
return "";
}
Another idea would be extending WebApplicationException and manage the responses yourself (probably the best)