Logging Done Right on AppEngine for Java

Posted on : 06-04-2011 | By : Sam Edwards | In : App Engine, Frameworks

Tags: , , , , ,

0

I have found that using slf4j is the best bet when logging with App Engine for Java. You log against their API and then you provide a binding to any of the supported logging frameworks at runtime. In the case of App Engine, java.util.logging is used primarily, however is isn’t as flexible as log4j so in order to get around this, I use a slf4j and a log4j binding during development and a jdk (java.util.logging) binding for releases/deploys. This means that all my logs will be accessible through the App Engine administrative panel.

Here is the <profiles> configuration of our Maven pom.xml file in order to setup a defult “development” profile with log4j and a “release” profile for deploys to App Engine.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <properties>
    <slf4j.version>1.6.1</slf4j.version>
    <log4j.version>1.2.16</log4j.version>
  </properties>
  ...
  <profiles>
    <profile>
      <id>development</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <dependencies>
        <!-- LOG4J logging -->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>${slf4j.version}</version>
        </dependency>
        <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>${log4j.version}</version>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>release</id>
      <dependencies>
        <!-- Java Util Logging -->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-jdk14</artifactId>
          <version>${slf4j.version}</version>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
</project>

logging.properties
This should go at /WEB-INF/logging.properties or wherever is specified in your appengine-web.xml file in this line:

<!-- Configure java.util.logging -->
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties" />
</system-properties>
.level = WARNING

com.YOURPACKAGENAME.level=ALL

log4j.properties
This should go in the root of your source directory. So in a maven project in /src/main/java/log4j.properties

# console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-5p: %m  at %C.(%F:%L) on %d{ISO8601}%n

#Get Stuff I want
log4j.category.com.YOURPACKAGENAME=ALL, console

Google IO 2011 Last Call Google Maps/Geo Entry (Tour-IO)

Posted on : 25-03-2011 | By : Sam Edwards | In : App Engine, Events, Geo, Maps, Mobile

Tags: , , , , ,

0

I (@HandstandSam) went ahead and personally entered the Google Maps/Geo Challenge yesterday and hacked together http://tour-io.appspot.com. Hopefully it’ll win me a ticket to Google I/O 2011.

It took a while for me to figure out the “Record” part of the contest (Contest Instructions are below) and therefore I didn’t really get to do much with “recommending”. However, I do allow you to view others tours by category! A note, only “tours” with more than 1 point are listed. I had a lot of “test” tours with 1 point (aka my non moving laptop) and didn’t want those to show up as tours.


The source for this project is available on GitHub and here are the readme contents:

This app was created in less than a day and allows you to record a “tour” via a mobile browser (Android and iPhone). You name the tour and select a category. Once you are finished with the tour, the tour is listed in the directory for others to view.

LIVE VERSION

http://tour-io.appspot.com

TECHNOLOGIES USED

  • Google Maps
  • jQuery
  • Google App Engine for Java
  • Objectify
  • Gson
  • JSON
  • SLF4J

CONTEST INSTRUCTIONS

Congratulations! You have qualified for Round II of the Google Maps/Geo challenge. Starting from now, you will have until 4:00 P.M. PDT on March 24 to complete the following coding challenge.

Walking Tours of San Francisco Build a mobile web app to record and recommend walking tours around the sights of San Francisco for I/O attendees who have never been to the city before

Extra points for incorporating: Street View Showing the elevation profiles for each walk Implementing a Styled Map Using fusion tables

Submission Guidelines: Please ensure that you read the following carefully. Incomplete submissions will be disqualified. Complete the form at http://goo.gl/9YCTI to submit a live implementation of your code (URL) by 4:00 P.M. PDT on March 24. Be sure to include the confirmation number at the top of this email. Email a .zip file of the source code to *********@google.com. Make sure the subject line of the email reads: Google Maps/Geo, Round 2 – [Last Name, First Name]

Your submissions, (1) and (2), must be received within 10 minutes of each other and no changes are permitted to your code after the submission period. We will be using the live implementation of your code for judging and verifying this against the code you emailed to ensure that no changes were made to the code after the contest period.

Good luck!

App Engine, Stop Adding JARs I Do Not Use!

Posted on : 23-03-2011 | By : Sam Edwards | In : App Engine

Tags: ,

0

It looks like you can modify some settings to get rid of enhancing and libs you don’t use by editing these properties. Anyone had any luck with this?

filesCopiedToWebInfLib=appengine-api-1.0-sdk-1.4.2.jar|appengine-api-labs-1.4.2.jar|appengine-jsr107cache-1.4.2.jar|jsr107cache-1.1.jar|datanucleus-appengine-1.0.8.final.jar|datanucleus-core-1.1.5.jar|datanucleus-jpa-1.1.5.jar|geronimo-jpa_3.0_spec-1.1.1.jar|geronimo-jta_1.1_spec-1.1.1.jar|jdo2-api-2.3-eb.jar
ormEnhancementInclusions=src/

Using Amazon S3 with Google App Engine for Java

Posted on : 15-02-2011 | By : Sam Edwards | In : App Engine, Tips

Tags: , , , , , ,

0

Update: (2011.03.16) – Github Link Updated

Amazon S3 is a fantastic way to store static files, but when you try to use their Java SDK on Google’s App EngineFAIL!

Soo, after a day of hitting my head against the wall, I finally had some success by combining multiple projects. The result: s3-simple-appengine on GitHub

The code is a mix of these libraries:

jets3t and Amazon’s SDK are great but they use the Apache Commons http client which doesn’t work 100% on Google App Engine. This is due to restrictions… namely, no threads, no sockets, and… other restrictions…

The library I’ve thrown together allows me to do “PUT” requests and upload blobs from the datastore, as well as create signed URLs which are the two things I needed currently.

//Create an S3Store Object and Set the Current Bucket
S3Store s3 = new S3Store(Constants.S3_DEFAULT_HOSTNAME, ACCESS_KEY, SECRET_KEY);
s3.setBucket("my-bucket");

//Sign an S3 URL
String signedUrl = s3.createSignedGETUrl(objectKey, validForInSeconds, isHttps);

//Upload Binary Data to S3 (i.e. From the AppEngine Blobstore, etc)
final Map<String, List<String>>() headers = new HashMap<String, List<String>>();
headers.put("Content-Type", contentType);
s3.storeItem(key, bytes, Constants.ACL_PUBLIC_READ, headers);

Secrets of GWT [Slides] – David Chandler

Posted on : 31-01-2011 | By : Sam Edwards | In : App Engine, Events, Tips

Tags: ,

0

David Chandler, a member of the Google Web Tookit (GWT) team at Google, and author of the blog turbomanage.wordpress.com spoke at the National Capital Area Google Technology User Group Meetup on January 18th and spoke about both GWT and Google App Engine.

Here are his slides from the presentation:

Updating App Engine Cron Jobs Manually

Posted on : 28-01-2011 | By : Sam Edwards | In : App Engine, Tips

Tags: , ,

0

I have been having issues updating the cron jobs for http://FoursquareBrands.com and needed to update them. Because the Java update kept giving me problems, went another route. I used the python command to update the application’s cron jobs.

After reading the Python documentation, I found out you can do this by running the following command (after adding in the two files mentioned below in your WEB-INF directory):

Use the terminal to navigate to your WEB-INF directory and run the following command:

appcfg.py update_cron .

This allowed me to update my cron jobs when I couldn’t through Java.

application: 4sqbrands
version: 11
runtime: python
api_version: 1

handlers:
- url: /(.*)
  static_files: static/\1
  upload: static/(.*)
cron:
- description: Follows new Brands when Discovered
  url: /cron/hourly
  schedule: every 1 hours synchronized

- description: Collects Brand Follower Stats Daily at 12:05am Pacific Time (Same as Google Analytics)
  url: /cron/daily
  schedule: every day 00:05
  timezone: America/Los_Angeles

- description: Sends out a Tweet of New Brands at 7:00am
  url: /cron/dotweet
  schedule: every day 7:00
  timezone: America/New_York

- description: Sends out a Tweet of New Brands at 7:00pm
  url: /cron/dotweet
  schedule: every day 19:00
  timezone: America/New_York

Amazon Product Advertising API with Java on App Engine Made Easy

Posted on : 19-01-2011 | By : Sam Edwards | In : App Engine, Tips

Tags: ,

0

Generate Your Java Beans from their WSDL with the Instructions seen here:
http://docs.amazonwebservices.com/AWSECommerceService/latest/GSG/index.html?YourDevelopmentEnvironment.html

Then pick an operation to call:
http://docs.amazonwebservices.com/AWSECommerceService/2010-11-01/DG/index.html?CHAP_OperationListAlphabetical.html

Then sign your requests:
http://associates-amazon.s3.amazonaws.com/signed-requests/helper/index.html

Then Deserialize the XML Response using this method:
http://www.rossbeazley.co.uk/easy-xml-parsing-using-jaxb-annotations/java/

private static final Logger log = Logger.getLogger(AmazonCallRunner.class
		.getName());

private static Unmarshaller um = createUnmarshaller();	

private static Unmarshaller createUnmarshaller() {
	JAXBContext jc;
	try {
		jc = JAXBContext
				.newInstance("FULL_PACKAGE_NAME_GOES_HERE");
		Unmarshaller um = jc.createUnmarshaller();
		return um;
	} catch (JAXBException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return null;
}

private static Object deserializeUrlToObject(String requestUrl) {
	try {
		URL url = new URL(requestUrl);
		Object response = um.unmarshal(url);

		Gson gson = new Gson();
		log.info(gson.toJson(response));

		return response;
	} catch (Exception e) {
		System.err.println("Exception with url " + requestUrl);
		e.printStackTrace();
	}

	return null;
}

That’s it!

Restoring Data to App Engine Using The Bulk Loader Tool

Posted on : 14-12-2010 | By : Sam Edwards | In : App Engine

0

As we’ve mentioned previously, App Engine’s Bulk Loader Tool is amazing for backups! But after spending around 4 hours tonight trying to track down how to restore data with numeric (id/Long) primary keys, I am beat.

The autogenerated config file that is created with appcfg.py will not support numeric ids. All I must say is that you are using a String as your primary key, you will be fine, otherwise, you will need to use the magical line (where ‘MyKind’ is your entity type):
import_transform: transform.create_foreign_key(‘MyKind’, key_is_id=True)

Example:

- kind: MyKind
  connector: csv
  connector_options:
    # TODO: Add connector options here--these are specific to each connector.
  property_map:
    - property: __key__
      external_name: key
      export_transform: transform.key_id_or_name_as_string
      import_transform: transform.create_foreign_key('MyKind', key_is_id=True)

Info on the setup this was accomplished with:

  • App Engine Version: 1.3.8
  • Mac OSX: 10.6.4

Sources:
StackOverflow.com

Uploading and Re-Sizing Large Images on App Engine

Posted on : 15-11-2010 | By : Sam Edwards | In : APIs, App Engine, Tips, Tools

Tags: , , ,

2

You may be under the impression that manipulating images larger than 1mb is not be possible on Google’s App Engine. This is not true though (sorta). Because most digital cameras produce images over 1mb in size, and requiring users to resize images before uploading is so 1990, it seems like the handling of large images would be an available feature. If you read the App Engine documentation on Image Manipulation, you may be under the impression, just as many are that you cannot use their image manipulation utilities on files over 1mb in size.

How To:
While it is true that the Image Manipulation Service cannot be used directly with image data over 1mb in size, it can be if it gets the image data from the App Engine Blobstore.  I got pointed in the direction of the Blobstore from this post from Developing in the Dark. By using the Blobstore, it is possible to use their Image manipulation API for files over 1mb in size.

Here is an example in Java and JSP that will allow the uploading, serving, and use of App Engine’s Image Manipulation service. This example is a modified version of the examples found in the App Engine Documentation. (Blobstore Example). (Image Manipulation with the Blobstore Example).

<%@ page import="com.google.appengine.api.blobstore.BlobstoreServiceFactory" %>
<%@ page import="com.google.appengine.api.blobstore.BlobstoreService" %>

<%
    BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
%>

<html>
    <head>
        <title>Upload Test</title>
    </head>
    <body>
        <form action="<%= blobstoreService.createUploadUrl("/upload") %>" method="post" enctype="multipart/form-data">
            <input type="text" name="foo">
            <input type="file" name="myFile">
            <input type="submit" value="Submit">
        </form>
    </body>
</html>
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
   http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

  <servlet>
    <servlet-name>Upload</servlet-name>
    <servlet-class>Upload</servlet-class>
  </servlet>

  <servlet>
    <servlet-name>Serve</servlet-name>
    <servlet-class>Serve</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Upload</servlet-name>
    <url-pattern>/upload</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>Serve</servlet-name>
    <url-pattern>/serve</url-pattern>
  </servlet-mapping>

</web-app>
import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;

public class Upload extends HttpServlet {
    private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        Map blobs = blobstoreService.getUploadedBlobs(req);
        BlobKey blobKey = blobs.get("myFile");

        if (blobKey == null) {
            res.sendRedirect("/");
        } else {
            res.sendRedirect("/serve?blob-key=" + blobKey.getKeyString());
        }
    }
}
import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;

public class Serve extends HttpServlet {
    private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws IOException {
        BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));
        blobstoreService.serve(blobKey, res);
    }
}
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.Image.Format;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Transform;

public class Resize extends HttpServlet {

	/**
	 * Default Serialization UID
	 */
	private static final long serialVersionUID = 1L;

	private BlobstoreService blobstoreService = BlobstoreServiceFactory
			.getBlobstoreService();

	public void doGet(HttpServletRequest req, HttpServletResponse res)
			throws IOException {
		//Get the Blob Key and grab the Image from the Blobstore
		BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));
        Image oldImage = ImagesServiceFactory.makeImageFromBlob(blobKey);	

		//Create the Image Service Factory and the Resize Transform
		ImagesService imagesService = ImagesServiceFactory.getImagesService();
        Transform resize = ImagesServiceFactory.makeResize(300, 200);

        //Resize The Image using the transform created above
        Image resizedImage = imagesService.applyTransform(resize, oldImage);

        //Serve the newly transformed image
        byte[] resizedImageData = resizedImage.getImageData();
        OutputStream outputStream = res.getOutputStream();
        outputStream.write(resizedImageData);
	}
}

Using JSTL on App Engine

Posted on : 30-09-2010 | By : Sam Edwards | In : App Engine, Frameworks, Tips

Tags: , ,

0

You need to add one line to your JSP page in order to enable the user of the JSTL tag libraries on an AppEngine server:

<%@ page isELIgnored="false" language="java" contentType="text/html;charset=UTF-8" %>