Android Startup Problem with IntelliJ Idea 11 and Maven

I’ve got my brand new MacBook today, yeah! That’s something good. But as continued to develop an Android project with a fresh installation of Lion and IntelliJ IDEA 11, I’ve got this exception:

[INFO] ------------------------------------------------------------------------
[ERROR] FATAL ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Path "/opt/android-sdk-linux/platforms" is not a directory. Please provide a proper Android SDK directory path as configuration parameter 
... in the plugin . As an alternative, you may add the parameter to commandline: -Dandroid.sdk.path=... or set environment variable ANDROID_HOME.
[INFO] ------------------------------------------------------------------------
[INFO] Trace
com.jayway.maven.plugins.android.InvalidSdkException: Path "/opt/android-sdk-linux/platforms" is not a directory. Please provide a proper Android SDK directory path as configuration parameter 
... in the plugin . As an alternative, you may add the parameter to commandline: -Dandroid.sdk.path=... or set environment variable ANDROID_HOME.
	at com.jayway.maven.plugins.android.AndroidSdk.assertPathIsDirectory(AndroidSdk.java:130)
	at com.jayway.maven.plugins.android.AndroidSdk.getPlatformDirectories(AndroidSdk.java:293)
	at com.jayway.maven.plugins.android.AndroidSdk.findAvailablePlatforms(AndroidSdk.java:268)

I’ve pointed the ANDROID_HOME environment variable to my Android SDK in /Library/Android, but it didn’t work. As I’am too lazy to configure the -Dandroid.sdk.path argument for each run configuration, I was pretty happy to find another, quite simple solution for this problem: The Android plugin expects the SDK to be installed in /opt/android-sdk-linux/platforms. So, just symlink it…

sudo ln -s /Library/Android /opt/android-sdk-linux

März 25th, 2012

Tags:,,,, Posted in Allgemeines No Comments »

Why two GWT applications cannot share the same JavaScript environment?

If you likes GWT as I do then you probably use it often and probably faced the same problem as I did.

The idea

The main advantage of GWT is that is makes dead simple to build very complex UI applications. That means I have to discipline myself to design the GWT application not as a monolithic monster with an initial download size of 2MB or worse (like happened to me in my first GWT project) – but as a lightweight, modular application that fetches only the needed code.

To avoid too large applications I’ve got a brilliant idea: I split the GWT app in different modules/widgets and include them wherever they are needed. In this way I could develop the modules independently from each other and they could communicate through a globally available EventBus.

The main advantage of this idea, as I thought, is that the modules does not have to know each other. And they are not depending on each other.

<html>
  <body>
    <div id="products"></div>
    <div id="basket"></div>
 
    <script type="text/javascript" src="products/products.nocache.js"></script>
    <script type="text/javascript" src="basket/basket.nocache.js"></script>
  </body>
</html>

I tried to implement that and I failed! And I’m happy about that because it’s a bad idea. So a had to start to think about the reason and to find a better way how I could achieve the same result.

Why it’s a bad idea?

Let’s assume we have a in the first GWT application a simple Product class with a sku and a price.

And in the second application we have a class named Basket with a sum and list of sku's.

// First GWT application
class Product {
  private String sku;
  private BigDecimal price;
}
 
// Second GWT application
class Basket {
  private ArrayList<String> skus;
  private BigDecimal sum;
}

The GWT compiler strips down the entire dependency tree and translates each referenced class into a javascript compilation. If those two classes would reside in two independent GWT modules, the String and BigDecimal classes would be compiled twice and would also be loaded twice into the javascript environment.

What is the problem?

If both modules compiled not obfuscated and loaded by the browser, then the String and BigDecimal implementations of the second module would overwrite the String and BigDecimal implementations of the first module. It means the same code is loaded twice.

As a common GWT application is a bit more complex than this example, you could imagine how easy it is to load with this idea the entire JRE stack multiple times. But that’s not the problem! The download size of the application increases, but it will work. The problem is the obfuscated code that we actually want to have because of it’s size.

Simply told, an obfuscater renames our classes into a shorter form. In the first module the Product class depends on the String and BigDecimal classes. String and BigDecimal have no dependencies (kept it simple for this example).

The obfuscater could start and rename the classes in their dependency order:

  1. java.lang.String becames a_A
  2. java.math.BigDecimal becames a_B
  3. de.fivews.Product becames a_C

In the obfuscated code a new Product would be created by executing new a_C()

Ok, lets have a look at the second module. The same thing here, but with one difference:

  1. java.lang.String becames a_A
  2. java.util.ArrayList becames a_B
  3. java.math.BigDecimal becames a_C
  4. de.fivews.Basket becames a_D

As you can see is everything fine if you load the first module. But when the second module is loaded the ArrayList class from the second module will overwrite the BigDecimal class from the first module. Just because of the renamed classes.

Now, what happens if the first module does this: new BigDecimal("2.3")?

Februar 13th, 2012

Tags:,, Posted in GWT No Comments »

Google Places Ergebnisse aus einer AutoCompleteTextView wählen

Google Places Suche in Android


Heute habe ich mich mit einer spannenden Frage beschäftigt: Wie kann ich den Google Places Dienst ansprechen, die Suchvorschläge in einer Auswahl-Box darstellen und dann zu dem ausgewählten Ort auf einer Karte springen?

Nach einwenig hin- und her, habe ich eine ganz brauchbare Lösung gefunden, die ich nachfolgend erkläre. Folgenden Schritte sind zu tun:

  1. Keys besorgen
  2. PlacesActivity implementieren
  3. PlacesAdapter implementieren
  4. PlacesService implementieren

Die Keys

Da ich hier unterschiedliche Google Dienste nutze, benötige ich auch unterschiedliche API-Schlüssel. Bei dem ersten handelt es sich um den API-Key für Google Maps und den zweiten benötige ich um Google Places zu nutzen. Den API-Schlüssel bekomme ich aus der Console.

Vorgehensweise

Nachdem die Schlüssel besorgt sind, geht es an die Implementierung. Dabei habe ich mir folgenden Ablauf überlegt:

  1. Die App startet und der Nutzer sieht ein Textfeld und eine Karte
  2. Tippt er einen Ort in das Textfeld, werden mögliche Ergebnisse in einer Liste gezeigt.
  3. Klickt der Benutzer auf ein Ergebnis, springt die Karte zu dem Ort.

PlacesActivity implementieren

Ich erzeuge ein neues Android Projekt mit einer PlacesActivity. Das Layout dieser Activity ist trivial. Es gibt lediglich eine AutoCompleteTextView, die unsere Suche durchführt und die Karte.

<?xml version="1.0" encoding="utf-8"?>
 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="vertical">
 
  <AutoCompleteTextView
          android:id="@+id/searchField"
          android:layout_height="wrap_content"
          android:layout_width="fill_parent"
          android:hint="@string/place_search_hint"/>
 
  <com.google.android.maps.MapView
          android:id="@+id/mapView"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:clickable="true"
          android:enabled="true"
          android:apiKey="MAPS_API_KEY"/>
</LinearLayout>

Die PlacesActivity erbt von der MapActivity und hat die folgenden Aufgaben:

  1. Einen neuen PlacesService erzeugen.
  2. Die AutoCompleteTextView konfigurieren und den PlacesAdapter zuweisen.
  3. Die MapView konfigurieren.

public class PlacesActivity extends MapActivity {
  private static final String TAG = "PlacesActivity";
  private static final String PLACES_API_KEY = "Your places key";
 
  private PlacesService placesService;
  private MapController mapController;
 
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.placesService = new GooglePlacesService(PLACES_API_KEY);
 
    setContentView(R.layout.places);
    initializeSearch();
    initializeMap();
  }
 
  private void initializeSearch() {
    final AutoCompleteTextView autoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.searchField);
    final PlacesAdapter<PlacesService.Result> adapter = new PlacesAdapter<PlacesService.Result>(this, placesService, R.layout.places_search_item);
    autoCompleteTextView.setAdapter(adapter);
    autoCompleteTextView.setThreshold(3);
 
    autoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
        onPlaceSelected(adapter.getItem(pos));
      }
    });
  }
 
  private void onPlaceSelected(PlacesService.Result item) {
    if (item != null) {
      final PlacesService.Place details = placesService.getDetails(item.reference);
 
      if (mapController != null) {
        mapController.animateTo(details.getGeoPoint());
      }
    }
  }
 
  private void initializeMap() {
    final MapView mapView = (MapView) findViewById(R.id.mapView);
    this.mapController = mapView.getController();
    mapView.setBuiltInZoomControls(true);
  }
 
  @Override
  protected boolean isRouteDisplayed() {
    return false;
  }
}

In der PlacesActivity passieren schonmal die interessantesten Dinge:

  • Die AutoCompleteTextView bekommt eine eigene Adapter-Implementierung: der PlacesAdapter<Result>. Der ArrayAdapter funktioniert in diesem Zusammenhang nicht, weil der interne Filter nicht ausgetauscht werden kann. Dieser Filter ist aber der Schlüssel zu der Suche, wie wir gleich sehen werden.
  • Der PlacesAdapter verwaltet Objekte vom Typ Result. Das sind einfache Beans, die den Namen des Suchergebnisses und eine ein eindeutige Referenznummer speichern:

      public static class Result {
        /**
         * The name to display
         */
        public final String name;
     
        /**
         * The key of the place
         */
        public final String reference;
     
        /**
         * Creates a new result with a name and a key
         *
         * @param name The name of the place
         * @param reference The unique key of the place
         */
        public Result(String name, String reference) {
          this.name = name;
          this.reference = reference;
        }
     
        @Override
        public String toString() {
          return name;
        }
      }

  • Wenn der Benutzer eins der Ergebnisse aus der Liste auswählt, lädt der PlacesService die Details zu der übergebenen Referenz und springt mit der Karte zu den Geo-Koordinaten.

PlacesAdapter implementieren

Die Implementierung des Adapters sieht so aus:

public class PlacesAdapter<T> extends BaseAdapter implements Filterable {
  private final List<T> items = new ArrayList<T>();
  private final int textResource;
  private final LayoutInflater inflater;
  private final PlacesService placesService;
  private PlacesFilter filter;
 
  public PlacesAdapter(Context context, PlacesService placesService, int textResource) {
    this.textResource = textResource;
    this.placesService = placesService;
    this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }
 
  @Override
  public int getCount() {
    return items.size();
  }
 
  @Override
  public T getItem(int i) {
    return items.get(i);
  }
 
  @Override
  public long getItemId(int position) {
    return position;
  }
 
  @Override
  public View getView(int position, View currentView, ViewGroup parent) {
    return createViewFromResource(position, currentView, parent, textResource);
  }
 
  private View createViewFromResource(int position, View convertView, ViewGroup parent, int resource) {
    final TextView text = (TextView) inflater.inflate(resource, parent, false);
    text.setText(getItem(position).toString());
    return text;
  }
 
  @Override
  public Filter getFilter() {
    if (filter == null) {
      filter = new PlacesFilter();
    }
 
    return filter;
  }
 
  protected class PlacesFilter extends Filter {
    private final static String TAG = "PlacesFilter";
 
    @Override
    protected FilterResults performFiltering(CharSequence charSequence) {
      final FilterResults results = new FilterResults();
      if (charSequence != null && charSequence.length() >= 3) {
        results.values = placesService.find(String.valueOf(charSequence));
      }
      return results;
    }
 
    @Override
    protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
      notifyDataSetInvalidated();
      @SuppressWarnings({"unchecked"}) final List<T> list = (List<T>) filterResults.values;
      if (list != null && list.size() > 0) {
        items.clear();
        for (T result : list) {
          items.add(result);
        }
 
        Log.i(TAG, "Publish results: " + list);
        notifyDataSetChanged();
      }
    }
  }
}

Was zunächst einmal wichtig zu wissen ist, dass der BaseAdapter von sich aus schonmal asynchron arbeitet. D.h. die Methode performFiltering() wird in einem eigenen Thread aufgerufen. Ich muss mich also nicht darum kümmern, dass mein PlacesService asynchron sucht.

Weil die AutoCompleteTextView einen Adapter erwartet, der das Filterable Interface implementiert, nutze ich meine Chance einen eigenen Filter zu bauen. Dieser Filter sucht anders als der ArrayAdapter.Filter nicht in den aktuellen Elementen, sondern lässt den PlacesService die Suche durchführen und ersetzt die Elemente des PlaceAdapters, wenn neue Ergebnisse vorliegen. Das ist schon der Trick.

PlacesService implementieren

Nun geht es um die Kommunikation mit dem Backend – in diesem Fall den Google Places API Server. Dabei nutze ich eigentlich zwei unterschiedliche Dienste:

  1. Autocomplete API, um mit einer Volltextsuche unterschiedliche Orte zu finden.
  2. Place Details API, um mehr über einen bestimmten Ort zu erfahren. z.B. Geo-Koordinaten.

Der erste Dienst liefert zu jedem Suchergebnis eine Referenznummer. Der zweite Dienst liefert zu jeder Referenznummer genaue Informationen. Und das habe ich als Schnittstelle definiert:

public interface PlacesService<S> {
 
  /**
   * By querying a place api the results will be returned as instances of this class.
   */
  public static class Result {
    /**
     * The name to display
     */
    public final String name;
 
    /**
     * The key of the place
     */
    public final String reference;
 
    /**
     * Creates a new result with a name and a key
     *
     * @param name The name of the place
     * @param reference The unique key of the place
     */
    public Result(String name, String reference) {
      this.name = name;
      this.reference = reference;
    }
 
    @Override
    public String toString() {
      return name;
    }
  }
 
  /**
   * A {@link Place} extends the simple search result and contains
   * latitude and longitude coordinates.
   */
  public static class Place extends Result {
    public final double lat;
    public final double lng;
 
    /**
     * Creates a new place with the given name and reference.
     *
     * @param name The name
     * @param reference The reference
     * @param lat The geo coordinate latitude
     * @param lng The geo coordinate latitude
     */
    public Place(String name, String reference, double lat, double lng) {
      super(name, reference);
      this.lat = lat;
      this.lng = lng;
    }
 
    @Override
    public String toString() {
      final StringBuilder sb = new StringBuilder();
      sb.append("Place");
      sb.append("{name=").append(name);
      sb.append(", lat=").append(lat);
      sb.append(", lng=").append(lng);
      sb.append('}');
      return sb.toString();
    }
 
    public GeoPoint getGeoPoint() {
      return new GeoPoint((int) (1e6 * lat), (int) (1e6 * lng));
    }
  }
 
  /**
   * Will be thrown if there was an error during querying the service.
   */
  public static class QueryException extends RuntimeException {
    public QueryException(String detailMessage) {
      super(detailMessage);
    }
 
    public QueryException(String detailMessage, Throwable throwable) {
      super(detailMessage, throwable);
    }
  }
 
  /**
   * Query the service and returns the results
   *
   * @param query The string query
   * @return The result
   * @throws QueryException If there was an error during querying the service.
   */
  public List<Result> find(String query) throws QueryException;
 
  /**
   * Returns the {@link Place} details for the given reference
   *
   * @param reference The place reference
   * @return The details or null, if there was not such place
   * @throws QueryException If there was an error during querying the service.
   */
  public Place getDetails(String reference) throws QueryException;
 
  /**
   * Returns the settings of this service.
   *
   * @return The current settings
   */
  public S getSettings();
}

Und die passende Implementierung dazu sieht so aus:

public class GooglePlacesService implements PlacesService<GooglePlacesService.Settings> {
  private static final String STATUS = "status";
  private static final String OK = "OK";
  private static final String ZERO_RESULTS = "ZERO_RESULTS";
  private static final String OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT";
  private static final String REQUEST_DENIED = "REQUEST_DENIED";
  private static final String INVALID_REQUEST = "INVALID_REQUEST";
  private static final String PREDICTIONS = "predictions";
  private static final String DESCRIPTION = "description";
  private static final String REFERENCE = "reference";
  private static final String RESULT = "result";
  private static final String NAME = "name";
  private static final String GEOMETRY = "geometry";
  private static final String LOCATION = "location";
  private static final String LAT = "lat";
  private static final String LNG = "lng";
 
  /**
   * To control the {@link GooglePlacesService} you have to pass some settings
   */
  public static class Settings {
    public final String apiKey;
    public String host = "https://maps.googleapis.com";
    public String searchPath = "/maps/api/place/autocomplete/json";
    public String detailsPath = "/maps/api/place/details/json";
    public UrlReader reader;
 
    public Settings(String apiKey) {
      this(apiKey, new GullibleUrlReader());
    }
 
    public Settings(String apiKey, UrlReader reader) {
      this.apiKey = apiKey;
      this.reader = reader;
    }
  }
 
  /**
   * The current service {@link Settings}
   */
  private Settings settings;
 
  /**
   * Creates a new {@link GooglePlacesService} with default settings.
   *
   * @param apiKey The google places server api key
   */
  public GooglePlacesService(String apiKey) {
    this(new Settings(apiKey));
  }
 
  /**
   * Creates a new {@link GooglePlacesService} with the given {@link Settings}
   *
   * @param settings The {@link Settings}
   */
  public GooglePlacesService(Settings settings) {
    this.settings = settings;
  }
 
  @Override
  public List<Result> find(String query) throws QueryException {
    final Map<String, String> params = new HashMap<String, String>();
    params.put("sensor", "true");
    params.put("input", query);
    params.put("key", getSettings().apiKey);
 
    try {
      final String result = getSettings().reader.fetch(getSettings().host, getSettings().searchPath, params);
      return convertResultList(result);
    }
    catch (Exception e) {
      throw new QueryException("Could not query google places service", e);
    }
  }
 
  @Override
  public Place getDetails(String reference) throws QueryException {
    final Map<String, String> params = new HashMap<String, String>();
    params.put("sensor", "true");
    params.put("reference", reference);
    params.put("key", getSettings().apiKey);
 
    try {
      final String result = getSettings().reader.fetch(getSettings().host, getSettings().detailsPath, params);
      return convertPlace(result);
    }
    catch (Exception e) {
      throw new QueryException("Could not query google places service", e);
    }
  }
 
  /**
   * Converts the received google places api result into a list of result objects
   *
   * @param json The received JSON string.
   * @return A {@link java.util.List} of converted results
   */
  public List<Result> convertResultList(String json) {
    try {
      final JSONObject object = new JSONObject(json);
      assertResponseStatus(object);
      return convertResultList(object);
    }
    catch (JSONException e) {
      throw new QueryException("invalid json response: " + json);
    }
  }
 
  /**
   * Converts the given json-String to a {@link Place}
   *
   * @param json String json-String
   * @return The converted {@link Place}
   */
  public Place convertPlace(String json) {
    try {
      final JSONObject object = new JSONObject(json);
      assertResponseStatus(object);
      return convertPlace(object);
    }
    catch (JSONException e) {
      throw new QueryException("invalid json response: " + json);
    }
  }
 
  @Override
  public Settings getSettings() {
    return settings;
  }
 
  /**
   * Converts the given {@link JSONObject} to an array of understandable {@link Result} instances
   *
   * @param object The {@link JSONObject}
   * @return A list with objects
   * @throws org.json.JSONException If the json object could not be parsed
   */
  private List<Result> convertResultList(JSONObject object) throws JSONException {
    final ArrayList<Result> list = new ArrayList<Result>();
    // The predictions contains the places
    final JSONArray predictions = object.has(PREDICTIONS) ? object.getJSONArray(PREDICTIONS) : null;
    if (predictions != null && predictions.length() > 0) {
      for (int i = 0; i < predictions.length(); i++) {
        final JSONObject place = predictions.getJSONObject(i);
        // Description and the unique reference are enough for our purpose
        list.add(new Result(place.getString(DESCRIPTION), place.getString(REFERENCE)));
      }
    }
    return list;
  }
 
  /**
   * Converts a {@link JSONObject} to a {@link Place}
   *
   * @param object The {@link JSONObject}
   * @return A {@link Place} or null
   * @throws JSONException If the {@link JSONObject} could not be converted
   */
  private Place convertPlace(JSONObject object) throws JSONException {
    final JSONObject result = object.getJSONObject(RESULT);
    final String name = result.getString(NAME);
    final String reference = result.getString(REFERENCE);
    final JSONObject location = result.getJSONObject(GEOMETRY).getJSONObject(LOCATION);
    final double lat = location.getDouble(LAT);
    final double lng = location.getDouble(LNG);
    return new Place(name, reference, lat, lng);
  }
 
  /**
   * Makes sure the given object is valid. Otherwise an exception will
   * be thrown.
   *
   * @param response The object
   * @throws QueryException         If the status is not {@link GooglePlacesService#OK}
   * @throws JSONException If the json could not be parsed properly
   */
  private void assertResponseStatus(JSONObject response) throws QueryException, JSONException {
    // Is something wrong
    if (!OK.equals(response.getString(STATUS))) {
      // Query limit reached
      if (OVER_QUERY_LIMIT.equals(response.getString(STATUS))) {
        throw new QueryException("query limit reached");
      }
      // Request denied
      else if (REQUEST_DENIED.equals(response.getString(STATUS))) {
        throw new QueryException("request denied");
      }
      // Unknown error
      else if (INVALID_REQUEST.equals(response.getString(STATUS))) {
        throw new QueryException("input missing");
      }
      // Result is empty
      else if (ZERO_RESULTS.equals(response.getString(STATUS))) {
        // That is not problem for us. Just an empty list will be returned
      }
      else {
        throw new QueryException("unknown error: " + response.getString(STATUS));
      }
    }
  }
}

Ich denke nicht, dass es im Detail notwendig ist zu erklären, wie der Service arbeitet. Er ruft den Google Server auf und interpretiert die Antwort als JSON.

Das einzige Detail, auf das ich die Aufmerksamkeit richten möchte ist der UrlReader in den Settings.

public interface UrlReader {
 
  /**
   * Builds an opens an url and returns the response as string.
   * 
   * @param host The host
   * @param path The path
   * @param parameters A {@link Map} with parameters
   * @return The server response
   */
  public String fetch(String host, String path, Map<String, String> parameters);
}

Die Kommunikation findet nämlich mit einem SSL Server statt. Beim Versuch die Verbindung zu öffnen, scheiterte der Code mit einer IOException. Das SSL-Zertifikat der Gegenstelle wurde von Android nicht akzeptiert. Da es sich hier um keine sensiblen Daten handelt, habe ich mich dazu entschieden das Zertifikat zu ignorieren und einen GullibleUrlReader implementiert, der die Verbindung leichtgläubig akzeptiert:

public class GullibleUrlReader implements UrlReader {
  private final X509TrustManager trustManager;
  private final HostnameVerifier hosteNameVerifier;
 
  /**
   * If the backend is a https sever than the identity will not be checked.
   */
  public GullibleUrlReader() {
    this.trustManager = new EverythingTrustingManager();
    this.hosteNameVerifier = new AllowAllHostnameVerifier();
  }
 
  @Override
  public String fetch(String host, String path, Map<String, String> parameters) {
    try {
      // Build the request url
      final URL url = buildUrl(host, path, parameters);
      final URLConnection urlConnection = url.openConnection();
      makeGullible(urlConnection);
      final InputStream inputStream = urlConnection.getInputStream();
 
      // Read server input
      final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      final StringBuilder builder = new StringBuilder();
      String line;
      do {
        line = reader.readLine();
        if (line != null) {
          builder.append(line);
        }
      }
      while (line != null);
      return builder.toString();
    }
    catch (MalformedURLException mue) {
      throw new RuntimeException("Could not build url.", mue);
    }
    catch (IOException ioe) {
      throw new RuntimeException("There is a problem to connect to host: ", ioe);
    }
  }
 
  /**
   * Build and return a new {@link URL}
   *
   * @param host       The host part
   * @param path       The path part
   * @param parameters The parameters (will be URL-encoded)
   * @return The url
   * @throws MalformedURLException If the url could not be build
   */
  public URL buildUrl(String host, String path, Map<String, String> parameters) throws MalformedURLException {
    final StringBuilder builder = new StringBuilder(host);
 
    // Append a trailing slash if not present between the host and path
    if (!host.endsWith("/") && !path.startsWith("/")) {
      builder.append("/");
    }
 
    // Append the path
    builder.append(path);
 
    // Append the params and encode them
    if (parameters != null && parameters.size() > 0) {
      builder.append("?");
 
      for (String key : parameters.keySet()) {
        parameters.put(key, URLEncoder.encode(parameters.get(key)));
      }
 
      builder.append(join(join(parameters, "="), "&"));
    }
 
    // Build new URL from spec
    return new URL(builder.toString());
  }
 
  /**
   * Joins the {@link Map} keys and values:
   * "KEY" + joinString + "VALUE"
   *
   * @param parameters The {@link Map} with keys and values to join
   * @param joinString The delimiter
   * @return A list of joined key-value pairs
   */
  private List<String> join(Map<String, String> parameters, String joinString) {
    final ArrayList<String> result = new ArrayList<String>();
    for (String key : parameters.keySet()) {
      result.add(new StringBuilder(key).append(joinString).append(parameters.get(key)).toString());
    }
    return result;
  }
 
  /**
   * Join the {@link List} elements
   *
   * @param list The {@link List} with elements to join
   * @param joinString The delimiter
   * @return The joined elements of the {@link List}
   */
  private String join(List<String> list, String joinString) {
    final StringBuilder builder = new StringBuilder();
    for (String string : list) {
      if (builder.length() > 0) {
        builder.append(joinString);
      }
      builder.append(string);
    }
    return builder.toString();
  }
 
  /**
   * If the given {@link URLConnection} is a {@link HttpsURLConnection}
   *
   * @param urlConnection The {@link URLConnection} that should be made gullible
   */
  private void makeGullible(URLConnection urlConnection) {
    if (urlConnection instanceof HttpsURLConnection) {
      final HttpsURLConnection httpsConnection = (HttpsURLConnection) urlConnection;
      httpsConnection.setHostnameVerifier(hosteNameVerifier);
 
      try {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[]{ trustManager }, new SecureRandom());
        httpsConnection.setSSLSocketFactory(context.getSocketFactory());
      }
      catch (Exception e) {
        // Ignore...
      }
    }
  }
 
  /**
   * This {@link X509TrustManager} does not check the identity.
   */
  private final class EverythingTrustingManager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public X509Certificate[] getAcceptedIssuers() {
      return new X509Certificate[0];
    }
  }
}

Fertig :)

Januar 18th, 2012

Tags:,,,, Posted in Android No Comments »

Google Picker API für GWT

Google Picker API in action

Vor einigen Wochen wurde ich mit der Implementierung eines GWT Wrappers für die Google Picker API beauftragt.

Mit dieser API ist es als Benutzer möglich über eine Weboberfläche auf Google Inhalte zuzugreifen. Ob es YouTube Videos, eigene Dokumente, die Google Bilder Suche oder sogar Google Maps sind… die API liefert Vieles.

Die Rückgabewerte sind Dokumente mit Infos über den ausgewählten Inhalt. Darunter fallen eindeutige ID’s, Verweise auf Thumbnails in verschiedenen Größen und einige Meta-Informationen.

Da es diese API (noch) nicht als GWT Modul gab, war ein Warpper um die JavaScript Bibliothek à la Google API Libraries for GWT sinnvoll.

Aber das Beste an dem Projekt ist, dass die floreysoft GmbH den Quellcode freigegeben und unter der Apache 2.0 Lizenz veröffentlicht hat. Ziemlich cool also! Und hier geht’s zum Projekt.

Dezember 30th, 2011

Tags:,,,, Posted in GWT No Comments »

Mit Grails nach Fotos bei Flickr suchen und durchblättern

Die gestrige Aufgabe hat echt Spass gemacht: Ich habe It’s nice in… um eine Funktion erweitert, mit der ich zu jedem Ort aus der Datenbank Bilder von Flickr betrachten und blättern kann. Eine Demo ist hier zu sehen. Einfach auf einen Marker klicken und den “Bilder” Reiter öffnen.

Mit Ini Fotos vom Great Barrier Reef zeigen lassen

Wie das mit Grails zu realisieren ist, möchte ich nachfolgend erläutern…

Die Flickr API ist einfach zu benutzen. Die Funktion flickr.photos.search kann ich für allerlei Arten von Bildersuche verwenden. Volltextsuche, Suche nach Tags, Geo-Suche, usw.

Ich benötige lediglich die folgenden Komponenten:

  1. Einen API-Schlüssel (Hier zu bekommen)
  2. Eine View
  3. Einen Controller
  4. Ein Template

Die View

Für den Einstieg habe ich mir eine View erzeugt, die alle Stylesheets beherbergt und – ganz wichtig – einen Container beinhaltet, in dem die Fotos später per Ajax paginiert werden:

...
<div id="photo-container">
  <g:include controller="flickr" action="photos" params="${[text: query]}" />
</div>
...

Hier ist auch zu sehen, dass ich kein Template (wie sonst üblich) einbinde, sondern einen Controller-Aufruf initiiere, der den Inhalt des Containers rendert. Ich hätte es über eine TagLib lösen können. Aber den Controller benötige ich für die Ajax-Paginierung sowieso und so kann er gleich die Logik für den API-Zugriff beherbergen.

Der FlickrController

import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.Method.GET
import static groovyx.net.http.ContentType.JSON
 
class FlickrController {
  def grailsApplication
 
  /**
   * Uses the Flickr API to find photos for the given parameters
   */
  def photos = {
    def json = findPhotosBy(params)
    render(template: "photos", model: [photos: json.photos?.photo ?: [], pagination: json.photos, text: params.text])
  }
 
  private def findPhotosBy(attrs = [:]) {
    def query = [
            method: 'flickr.photos.search',
            api_key: config.flickr.key,
            sort: 'relevance',
            per_page: attrs.perPage ?: 14,
            page: attrs.page ?: 1,
            format: 'json',
            text: attrs.text,
            nojsoncallback: 1
    ]
 
    def http = new HTTPBuilder('http://api.flickr.com')
    http.request(GET, JSON) {
      uri.path = '/services/rest/'
      uri.query = query
 
      response.success = { resp, json ->
        json.photos?.photo?.each { map ->
          map.images = [
                  s: createUrlToImage(map + [size: 's']),
                  b: createUrlToImage(map + [size: 'b']),
                  m: createUrlToImage(map + [size: 'm'])]
          map.extern = "http://www.flickr.com/photos/${map.owner}/${map.id}".toString()
          map
        }
        return json
      }
 
      response.failure = { resp ->
        throw new RuntimeException("Error [${resp.statusLine.statusCode}]: ${resp.statusLine.reasonPhrase}")
      }
    }
  }
 
  /**
   * Creates an url to a flickr photo.
   *
   * @param params A map with photo details
   * @return An absolute url
   */
  private def createUrlToImage(params) {
    "http://farm${params.farm}.static.flickr.com/${params.server}/${params.id}_${params.secret}_${params.size}.jpg".toString()
  }
 
  /**
   * @return Application config
   */
  private ConfigObject getConfig() {
    grailsApplication.config
  }
}

Was passiert im Controller?

Schritt 1

Nun, zunächst einmal will ich die Fotos finden. Dazu definiere ich mir einige Parameter für die Suche: Suchtext, Paginierung, Sortierung und der Rückgabewert, den ich gern im JSON-Format hätte.

Schritt 2

Der HTTPBuilder macht den API-Aufruf zu einem Klacks! Dazu übergebe ich den Host, die Parameter und sorge im Fehlerfall für eine Exception. Beachte die statischen Importe!

Schritt 3

Im Erfolgsfall erweitere ich das JSON-Ergebnis, um einige URL’s, die auf die Bilder zeigen. Dafür definiert Flickr einige URL-Pattern für den direkten Zugriff auf die Bilder und andere Quellen. Auf diese Url’s greife ich später im Template zu.

Schritt 4

Rendern des Templates. Es werden die Fotos, die Paginierung und der Suchbegriff übergeben. Das ist wichtig, da ich für die Ajax-Paginierung diese Parameter benötige.

Das Template

Nun wollen die Photos noch auf die Leinwand gebracht werden, und das passiert mit diesem Template:

<%@ page contentType="text/html;charset=UTF-8" %>
<g:setProvider library="jquery" />
 
<div class="pagination-wrapper">
  <a href="http://www.flickr.com" target="_blank">
    <small>Powered by</small>
    <img src="<g:resource dir="images/layout" file="flickr.png" />" alt="powered by Flickr" />
  </a>
 
  <div class="pagination">
    <g:if test="${pagination.page > 1}">
      <div class="box">
        <g:remoteLink class="prev" controller="flickr" action="photos" params="${[text: text, page: pagination.page - 1]}" update="photo-container"><<</g:remoteLink>
      </div>
    </g:if>
    <div class="box">${pagination.page}</div>
    <g:if test="${pagination.page < pagination.pages}">
      <div class="box">
        <g:remoteLink class="next" controller="flickr" action="photos" params="${[text: text, page: pagination.page + 1]}" update="photo-container">>></g:remoteLink>
      </div>
    </g:if>
  </div>
</div>
 
<ul class="hoverbox">
  <g:each var="photo" in="${photos}" status="index">
    <li>
      <a href="${photo.extern}" target="_blank" title="Photo in Flickr öffnen">
        <img src="${photo.images.m}" alt="${photo.title}" class="preview" />
        <img src="${photo.images.s}" title="${photo.title}" class="thumb" alt="${photo.title}" />
      </a>
    </li>
  </g:each>
</ul>
<div class="clear"></div>

Fertig ist die Flickr-Foto-Paginierung :)

Dezember 29th, 2011

Tags:,,,,, Posted in Grails No Comments »

Erfahre, wann wettertechnisch der beste Reisemonat ist.

Die Grafik zeigt den besten Reisemonat

Bester Reisemomat für den chin. Süd-Westen


Heute habe ich INI um eine Funktion erweitert, die mir endlich zeigt, wann ich nach Koh Phangan (oder einen anderen Ort der Erde) fliegen sollte und wann lieber nicht.

Wenn man auf die Karte klickt, bekommt man ein Fenster zu sehen, das die besten Reisemonate mit den jeweiligen Werten zeigt und einen Chart – der die Temperatur zu dem Niederschlag in Verhältnis setzt. Die Berechnungsgrundlage ist im Moment noch fest hinterlegt und besagt, dass ich eine Temperatur von 25° und sehr wenig Regen mag.

In der nächsten Version, wird der Nutzer einstellen können, ob ihm die Temperatur oder der Regen vor Ort wichtiger ist und welche Werte er als “ideal” empfindet. Aber für heute reicht es…

November 29th, 2011

Tags:,,, Posted in Ideen 1 Comment »

It’s nice in… Ja, wo eigentlich?

Ini's Oberfläche


…ein Bekannter von mir und ich haben uns diese Frage vor ca. einem Jahr gestellt. Wir waren zuvor beide in Thailand; er zwei Wochen später als ich. Obwohl wir unseren Urlaub auf der selben Insel verbracht haben, hatte er deutlich schlechteres Wetter erwischt als ich. Ich wusste z.B. nicht, dass Anfang September dort die Regenzeit losgeht.

Und das ist auf Koh Phangan nicht lustig, weil nur wenige Strasse befestigt sind und in der Regenzeit schonmal das eine oder andere Dörfchen von dem Rest der Insel abgeschnitten ist.

Also haben wir uns It’s nice in… (Ini) ausgedacht. Die Anwendung ist noch nicht fertig, zeigt aber schonmal die Richtung, in die es gehen soll. Sie beantwortet nämlich die Frage: Wenn ich z.B. im Januar verreisen will, wo ist das Wetter angenehm genug für mich?

Dafür greift Ini auf die Wetterdaten der Vergangenheit zurück und erstellt für jeden Monat zwei Arten von Karten:

  1. Temeperatur Karte (blau = kalt, rot = heiß)
  2. Karte vom Niederschlag (rot = gar nicht, blau = viel)

Will ich also wissen wann es in Thailand am wenigsten regnet, wähle ich die Karte mit dem Niederschlag aus und schiebe den Monats-Regler solange hin und her, bis ich das wenigste blau sehe. Oder, wenn ich es nicht so heiß mag aber nach Australien möchte, dann den Button mit der Sonnenbrille drücken und den Monats-Regler bewegen, bis es nicht mehr ganz so rot ist.

Ich bin mit dem Projekt noch nicht fertig und werde noch 1-2 Features umsetzen, die mir unter den Fingernägeln brennen. Oder hast du eine Funktion im Kopf, die Ini unbedingt haben muss ? ;)

November 27th, 2011

Tags:,,,,, Posted in Ideen,Myplant No Comments »

Maven führt Grails Tests doppelt aus

Neulich hat sich mein TeamCity-Server mit einem java.lang.OutOfMemoryError Fehler bei mir gemeldet – und das trotz meiner großzügigen Erlaubnis etwas mehr Speicher (XX:MaxPermSize=256m -Xmx1024m) für die Builds nutzen zu dürfen.

Der Fehler trat auf, als TeamCity mein Grails-Plugin mit einem mvn install in das lokale Maven Repository installiert sollte. Im Log sah ich dann, dass die Tests zweimal unmittelbar nacheinander ausgeführt wurden und auch die Test-Reports wurden zwei mal geschrieben. Meine Recherche ergab bis auf diesen Blog-Eintrag nicht viel. Den Grund, weshalb die Tests doppelt ausgeführt werden, habe ich noch nicht gefunden. Aber eine Lösung, die das Problem löst:

Statt einem mvn install führe ich nun mvn grails:test-app install -Dmaven.test.skip=true aus. maven.test.skip=true bewirkt, dass Maven die Test-Phase überspringt.

November 11th, 2011

Tags:,, Posted in Grails No Comments »

DomainClass.afterUpdate und StaleObjectstateException im Service

Heute hat mich eine StaleObjectstateException: Row was updated or deleted by another transaction den halben Tag gekostet. Ich hatte das folgende Problem: Jedes mal, wenn meine Domain-Klasse gespeichert oder aktualisiert wurde und über eine ID verfügt, möchte ich in meinem Service die Inhalte einer bestimmten Variable in das Dateisystem schreiben. Ich habe mir überlegt das Problem über die GORM Callbacks afterInsert & afterUpdate zu lösen.

class CmsNode {
  ...
  def afterInsert = {
    cmsService.writeNodeToFilesystem(this.id)
  }
  def afterUpdate = {
    cmsService.writeNodeToFilesystem(this.id)
  }
 
  def afterDelete = {
    cmsService.deleteNodeFromFilesystem(this.id)
  }
 
  static def getCmsService() {
    if (!service) { 
      service = ApplicationHolder.application.mainContext.getBean('cartyCmsService')  
    }
 
    service
  }
  ...
}

Sobald ich im Service die referenzierte ID angefasst habe, wurde die StaleObjectstateException geworfen.

Was ich nicht bedacht habe ist, dass um die Methode des Dienstes eine Transaktionsklammer liegt und Hibernate auf Autoflush (grails.gorm.autoFlush = true) konfiguriert war.

Es passiert nämlich folgendes:

  • Die Callbacks (afterInsert, afterUpdate, afterDelete) werden dann erst aufgerufen, wenn die Änderungen in der Hibernate-Session in die Datenbank überführt werden – also beim flush. In dem Aufruf der Callbacks ist der Prozess aber noch nicht abgeschlossen, sondern mitten drin.
  • Rufe ich aus dem Callback heraus eine transaktionsgeschützte Methode eines Dienstes auf und führe einen lesenden Zugriff auf eine Domain Klasse durch, greift das Autoflush und Hibernate versucht die Session erneut zu flushen, was die Exception auslöst.

Ich habe dieses Problem gelöst, indem ich die Transaktionsklammer um die Methode des Dienstes weggelassen und stattdessen mit einem Transaktionsblock (DomainClass.withTransaction {...}) gearbeitet habe.

class CartyCmsService implements DisposableBean {
  static transactional = false
  def threadPool
 
  CartyCmsService() {
    threadPool = Executors.newFixedThreadPool(2)
  }
 
  public void writeNodeToFilesystem(long nodeId) {
    threadPool.submit({
      CmsNode.withTransaction {
        def node = CmsNode.get(nodeId)
        def file = new File(...)
        ...
        file.write(node.content)
        ...
      }
    })
  }
  ...
  void destroy() {
    if (!threadPool.isShutdown()) {
      threadPool.shutdown()
    }
  }
}
November 10th, 2011

Tags:,,,,,, Posted in Grails No Comments »

Grails-Plugin als *.ZIP im Maven-Repo ablegen.

Die Arbeit mit Maven und Grails macht wirklich Laune. Währen da bloß nicht auch solche Tage, an denen ich dieses Pärchen überhaupt nicht sehen mag – wie z.B. heute.

Wenn ich mit Grails ein Plugin schreibe, möchte ich es in ein Maven-Repo installieren und anderswo nutzen können. Schön wäre da eine Goal-Abfolge wie mvn grails:package-plugin install. Geht aber nicht, daher habe ich mir bisher mit dieser Lösung ausgeholfen.

Weil ich das aber recht nervig finde, habe ich heute noch mal geforscht, und siehe da, es geht doch – und zwar so:

  1. Grails Plugin erzeugen und “mavenifizieren”.
  2. Den Nachfolger des Maven-Publisher-Plugins installieren => Release-Plugin.
  3. In der pom.xml grails-plugin als Packaging eintragen: <packaging>grails-plugin</packaging>
  4. mvn install ausführen.

Im Resultat wird das Plugin gepackt und ein *.zip-Archiv erstellt, welches anschließend in das lokale Maven-Repo kopiert wird.

Damit ist es aber noch nicht ganz erledigt. Möchte ich in einer anderen Grails-Anwendung das Plugin nutzen, so trage ich in der application.properties die Abhängigkeit dazu ein. In meinem Fall heißt das Plugin Carty:

app.grails.version=1.3.7
app.name=XXX
app.servlet.version=2.4
app.version=0.1
plugins.carty=0.0.1
plugins.hibernate=1.3.7
plugins.tomcat=1.3.7

Grails versucht das Plugin zu finden, indem es diverse Quellen anzapft. U.a. auch den lokalen Maven Cache:

==== localMavenResolver: tried
	  /Users/hellboy/.m2/repository/org/grails/plugins/carty/0.0.1/carty-0.0.1.pom

Bloß, unter dem Standard Grails-Namespace findet er nichts, da meine Packages anders benannt sind. Ich helfe ihm einwenig nach und trage in der BuildConfig.groovy den entscheidenden Hinweis ein:

grails.project.dependency.resolution = {
  ...
  repositories {
    ...
    mavenLocal()
    ...
  }
  plugins {
    runtime 'de.fivews.carty:carty:latest.integration'
  }
}

Auf diese Weise kann ich Grails-Plugins bauen, sie in ein Maven-Repo installieren und anderswo als Abhängigkeit nutzen. Besonders bei der Verwendung eines Continuous Integration Servers ist das sehr nützlich.

Oktober 19th, 2011

Tags:,,,, Posted in Grails,Groovy No Comments »