Archiv für Kategorie Entwicklung

Eclipse: “No persistence.xml file found in project”

Für mein aktuelles Projekt setze ich u.a. JPA/EclipseLink, Maven und Spring. Damit der Build-Prozess von Maven und das automatische Deployen in den Tomcat-Container von Eclipse funktioniert, musste ich ein paar Änderungen an der .settings/org.eclipse.wst.common.component durchführen:

<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
    <wb-module deploy-name="YourProject">
        <wb-resource deploy-path="/" source-path="/src/main/webapp"/>
	<wb-resource deploy-path="/WEB-INF" source-path="/src/main/resources"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>
        <property name="context-root" value="YourProject"/>
        <property name="java-output-path" value="/YourProject/target/classes"/>
    </wb-module>
</project-modules>

Nach dem nächsten Start von Eclipse bekam ich nun folgenden, nichts-sagenden Fehler vom Eclipse-JPA-Plugin: No persistence.xml file found in project.. Pustekuchen, denn unter src/main/resources/META-INF/persistence.xml war die Datei zu finden und auch im Classpath so eingerichtet. Nach einigem hin und her probieren habe ich dann die (absolut bescheuerte) Lösung für das Problem gefunden.

Der Inhalt des ressource-Ordner muss nach /WEB-INF/classes kopiert werden, und zwar an vierter Stelle:

        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>

Die Logik dahinter ist mir schleierhaft, da ja davon eigentlich auszugehen ist, dass das JPA-Plugin die persistence.xml aus dem Classpath anzieht.

, , ,

Keine Kommentare

Debugging von C-Applikationen unter Linux

Da ich gegenwärtig an libopenranked von etqw-openranked arbeite und ich vermute, dass ich meine Erkenntnisse nach einiger Zeit wieder vergesse, gibt es hier die Kurzfassung.

Damit bei einem Segmentation Fault eine Core-Dump erzeugt wird, muss

ulimit -c unlimited

aufgerufen. Damit wird festgelegt, dass der Core-Dump beliebig groß sein darf.

Mit

gdb a.out core
backtrace

lassen sich die letzten Ausführungsschritte der Applikation anzeigen.

Mit

apt-get install valgrind
valgrind --tool=memcheck --leak-check=yes ./a.out

lässt sich überprüfen, welche Funktionen potenzielle Memory Leaks erzeugen, die wiederum zu einem Segmentation Fault führen können.

, , , , , , , , ,

Keine Kommentare

Daily Hack: Manuell aus RSSPopper eine OPML erstellen

Am Mittwoch habe ich meinen Urlaub angetreten und mir meine OPML-Datei, die ich in meinem Firmen-Outlook eingerichtet  habe, per E-Mail zugeschickt, so dass ich auf meinem Privat-Notebook in meiner Urlaubszeit immer noch die News als RSS durchschauen kann.
Heute Abend wollte ich dann die OPML-Datei aus Outlook Web Access in Liferea einspielen. Leider gab es ein Problem: über OWA konnte ich (warum auch immer) den Anhang meiner E-Mail nicht herunterladen. Mein Rechner war auch aus, so dass ich die OPML-Datei nicht erneut erstellen konnte.
Glücklicherweise werden die Feeds von RSSPopper im Profilverzeichnis unter Anwendungsdaten/RSSPopper gespeichert. An mein Server-Profil kam ich leicht heran, kopierte mir dann die Datei feeds.xml und schraubte mir auf die Schnelle ein XPath-Ausdruck, den ich auf der Kommandozeile mit xqilla rsspopper_to_opml.xquery ausführte.

<opml version="1.0">
  <head>
    <title>Feeds</title>
  </head>
  <body>
    <outline text="Feeds">
    {
	let $d := fn:doc("/home/ckl/Desktop/feeds.xml")/feeds
        for $x in $d/feed
            return
		<outline title="{$x/Title}" xmlUrl="{$x/Link}" />
    }
</outline>
</body>
</opml>

, , , , , , ,

Keine Kommentare

Entwickeln mit Scrum/Agilo

Wegen eines privaten Projekts habe ich in den letzten paar Tagen eine kleine Entwicklungsumgebung aufgebaut. Dazu dient mir auf meinem Notebook eine VirtualBox (Ubuntu 10.04, Tomcat, MySQL 5.2, Apache 2, Trac + Agilo, SVN) als Deployment- und Entwicklungsplattform. Die Installation des ganzen Environments erledigte ich recht schnell anhand von http://wiki.ubuntuusers.de/Trac und http://www.agile42.com/cms/pages/download-install/

Nachdem ich mich mit Agilo ein bißchen auseinandersetzte, stellte ich fest, dass das Anlagen von Teams nicht funktionierte, die Fehlermeldung

 An error occurred while getting None from the database: no such table: agilo_sprint

war mehr als eindeutig: bei der Installation von Agilo wurden in meiner Version drei Tabellen schlichtweg nicht erstellt.
Nach kurzer Suche wurde ich fündig und legte mit sqlite3 trac.db die fehlenden Tabellen für die Benutzer manuell an:

CREATE TABLE agilo_team (
  name text,
  description text,
  UNIQUE (name)
);
CREATE TABLE agilo_team_member (
  name text,
  team text,
  description text,
  ts_mon real,
  ts_tue real,
  ts_wed real,
  ts_thu real,
  ts_fri real,
  ts_sat real,
  ts_sun real,
  UNIQUE (name)
);
CREATE TABLE agilo_calendar_entry (
  date integer,
  teammember text,
  hours real,
  UNIQUE (date,teammember)
);

agilo_calendar_entry wurde ebenfalls nicht angelegt, wie ich später feststellte.

Weiterhin stellte ich fest, dass beim Erstellen eines Sprints der Fehler

AttributeError: 'NoneType' object has no attribute 'toordinal'

auftrat. Google sei dank fixte ich das Problem, indem ich bei den Sprint-Daten Start- und Endzeitpunkt manuell eintrug. Anscheinend funktioniert das Feld Duration in days in der Sprint-Administration noch nicht so ganz.

Soweit dazu, eventuell gibt es ja die ein oder andere Person, die ebenfalls über diese beiden Fehler stolpert.

Für mich als Scrum-Neuling – produktiv hatte ich diese Entwicklungsmethode noch nicht eingesetzt – wurde ich beim näheren Betrachten der Optionen von Agilo erst einmal erschlagen. Deshalb folgt hier eine grobe Auflistung der Begrifflichkeiten und Funktionalitäten, wie sie in der Standard-Installation definiert sind.

  • Ticket-Typen
    • Requirement sind Tickets, die auf die Fragen Welches Problem zu lösen ist und warum es zum Problem wird. Requirements sollen SMART (Simple, Measurable, Achievable, Realistic and Traceable) sein. Nachdem ein Requirement angelegt wurde, kann über “Edit” 0..n User Stories zugewiesen werden.
    • User Story ist ein Ticket, das beschreibt was der Benutzer mit dem System erreichen will und warum (Nutzen dieser Funktionalität). Nachdem eine User-Story angelegt ist, kann über “Edit” auf einen neuen Task verwiesen werden. Eine User Story kann 0..n Tasks besitzen.
    • Tasks sind Tickets, die eine Aufgabe, die von einem Team Member zu erledigen ist, detailiert erklärt. Jeder Task soll ausführlich sein und sich auf eine Aufgabe beziehen.
  • Backlog
    • Das Product Backlog beinhaltet die User Stories und darunter jeweils die Requirements, die einer User Story zugeordnet sind.
    • Das Sprint Backlog beinhaltet die Requirements (und die User Story bzw. Tasks die dem Requirement zugewiesen worden sind) und offene Bugs. Es werden nur die Requirements/Tasks angezeigt, die dem Sprint zugewiesen worden sind.
    • Sprint bezeichnet den Zeitraum, in dem mehrere Tasks/Requirements gelöst werden. Im Administrations-Interface müssen jeweils neue Sprints mit Start- und End-Datum angelegt werden.
    • Milestone bezeichnet einen Zeitraum, der wiederum aus mehreren Sprints besteht

Die Frage nach dem “Wie gehe ich nun vor?” ist über oben die dargestellte Struktur eigentlich relativ klar:

  • Anlegen der User Stories (WELCHE Funktionalität wird benötigt um einen Business Value zu erreichen?)
  • Erstellen der Requirements (WELCHE Probleme einer User Story gilt es zu lösen?) und Zuweisen der Requirements an eine User Story
  • Erstellen der zugehörigen Tasks (WAS ist bei einer User Story technisch zu tun?), die einer User Story angehören
  • Erstellen eines Meilensteins. Requirements müssen dem Meilenstein zugewiesen werden
  • Erstellen von ein oder mehreren Sprints. Tasks, User Stories und Bugs müssen den Sprints zugewiesen werden

Eventuell werde ich die nächsten Tage noch den ein oder anderen Blog-Eintrag zu Agilo verfassen. Danke fürs Lesen,

, , , , ,

2 Kommentare

mod_auth_sspi: Patch für SSPIUsernameFormat eingereicht

Hat etwas gedauert (*räusper*) bis ich den Patch für mod_auth_sspi veröffentlicht habe. Günter ist noch nicht dazu gekommen, den Patch ins CVS zu mergen, deshalb habe ich das gepatchte Modul inkl. Patches für die Sourcen bei sourceforge.net hochgeladen.

Der SSPIUsernameFormat-Patch steht nun also offiziell zum Download bereit.

, ,

Keine Kommentare

How-To: Module des Apache Webservers unter Windows mit Visual Studio kompilieren und debuggen

Aus aktuellem Anlass musste ich mal wieder ein Apache-Modul gerade biegen. Diesmal war es mod_auth_ldap. mod_auth_ldap sollte als Modul zur Authentifizierung und Autorisierung von LDAP-Benutzern (Active Directory, eDirectory, OpenLDAP etc.) vielen Leuten ein Begriff sein.

Diese Anleitung zeigt in wenigen Schritten, wie man aus den Sourcen des Apache Webserver 2.0.63 ein Modul patcht, kompiliert und debuggt. Voraussetzung für die Kompilierung ist ein Visual Studio Express bzw. Visual C++. Ich verwende auf meinem Rechner ein (relativ altes) Visual Studio 2005 Professional.

Auf meinem Arbeitsrechner schaut es so aus, dass ich unter c:\ckl\dev\srv\web\apache\2.0.63 ($DIR_BIN) die installierte und kompilierte Version des Apache Webservers habe und unter c:\ckl\dev\projects\app\apache\2.0.63 ($DIR_SRC) die zugehörigen Sourcen liegen.

Zuerst müssen von apache.org die aktuellen Sourcen für Windows heruntergeladen und auf der Festplatte ($DIR_SRC) entpackt werden. Wenn man zusätzlich noch durch die Sourcen des Apache Webservers debuggen will, werden weiterhin die Symbols benötigt. Diese müssen in das Hauptverzeichnis des kompilierten Apache Webservers ($DIR_BIN) entpackt werden.

Die Datei $DIR_SRC\Apache.dsw enthält alle Teilprojekte des Webservers und muss mit Visual Studio geöffnet werden. Falls beim Öffnen die Frage nach einer Konvertierung der Daten kommt, kann/muss dies mit Ja beantwortet werden.

Ausgehend davon, dass wir nur mod_auth_ldap patchen wollen, muss nun im Solutions Explorer das Teilprojekt mod_auth_ldap ausgewählt und die jeweiligen Änderungen in die mod_auth_ldap.c eingetragen werden. Mit einem Rechtsklick auf mod_auth_ldap > Build wird nun das Modul erstellt. Die .so-Datei lässt sich jetzt unter $DIR_SRC\modules\experimental\[Debug|Release]\mod_auth_ldap.so ($MOD_AL) finden. Weiterhin ist die $DIR_SRC\modules\experimental\Debug\mod_auth_ldap.pdb ($DEBUG_AL) wichtig.

Der Apache muss nun als Dienst beendet werden (net stop apache2), danach muss $MOD_AL und $DEBUG_AL nach $DIR_BIN\modules kopiert (vorher Sicherung des Originals erstellen!) und Apache auf der Kommandozeile ($DIR_BIN\bin\apache.exe) gestartet werden. Dies ist nötig, damit Visual Studio sich an den Apache-Prozess hängen kann. Wird der Apache als Dienst ausgeführt, ist dies nicht ohne Weiteres möglich.

Sobald der Webserver läuft, kann im Visual Studio unter Debug > Attach to Process die Apache-Prozesse ausgewählt werden. Beim Hit eines Breakpoints in der mod_auth_ldap.c stoppt der Apache die Ausführung.

Hier die Hinweise im Überblick:

  • $DIR_SRC\Apache.dsw als Projekt öffnen und nicht $DIR_SRC\modules\experimental\mod_auth_ldap.dsw, da man bei letzterem zu viele Einstellungen ändern muss.
  • Apache.exe als normalen Prozess und nicht als Dienst starten, da sich sonst der Debugger nicht nutzen lässt.
  • Neben der .so-Datei muss auch die zugehörige .pdb-Datei in $DIR_BIN\modules kopiert werden.

, , , ,

Keine Kommentare

Web Application goes Desktop

Heute habe ich bei Golem gelesen, dass Titanium den Appcelerator entwickelt hat. An sich nichts Neues, schließlich werden jeden Tag zu hauf’ neue Applikationen veröffentlicht.
Das Interessante ist, dass ich erst beim Lesen des Artikels die Tragweite von RIAs für die Zukunft erfasst habe.
Kurz zum Appcelerator: Das Framework stellt ein Browser-Umgebung – bisher nur für MacOS X und Windows, Linux folgt später – bereit. Dieses  erlaubt es den Entwicklern, ihre Anwendung mehr oder weniger nativ auf dem Desktop laufen zu lassen. Dabei wird in JavaScript + XHTML entwickelt. Appcelerator bietet einige eigene JavaScript-Namespaces an, die unter anderem das Abspielen von Sounds (MP3, WAV) oder das Speichern von relationalen Daten erlauben.

Appcelerator reiht sich in die Reihe der RIA-Frameworks ein. Silverlight von Microsoft und Flex von Adobe sind sicherlich wesentlich bekannter – haben allerdings den Nachteil, dass sie eben nicht JavaScript und XHTMl für die Entwicklung nutzen. Ich persönlich zähle mittlerweile auch das GWT als RIA-Framwork.

Wann kann aber ein RIA-Framework erfolgreich sein? Meiner Meinung nach ist es wichtig, dass der Entwickler -also ich- sich nicht in eine neue Technologie umständlich einarbeiten muss, sondern stattdessen bestehende Technologien benutzt werden. Eindeutig ein Plus für Appcelerator (JavaScript, CSS + XHTML) und GWT (Java + CSS).

Personen, die bereits mit WPF und XAML entwickelt haben, werden sicherlich Silverlight den Vorzug geben.
Aber nicht nur die Um- oder Eingewöhnung in das neue Environment sind wichtig, sondern auch die Features. Meine Wunschliste sieht folgendermaßen aus:

  • Abspielen von Medien – sowohl Video als auch Audio
  • Zugriff auf das Tray-Icon
  • Speicherung von Benutzerdaten in einer relationalen Datenbank (bspw. SQLite)
  • Asynchrone Verbindungen
  • JSON und XML
  • SOAP inkl. WSDL-Security
  • Netzwerkzugriff auf Layer >= 4 um beispielsweise POP3 oder IMAP zu benutzen, Gameserver anzufragen. Hiermit liessen sich etwa die Spielergebnsse eine Counter-Strike-Matches automatisch in das Blog eintragen
  • Plattformunabhängigkeit (Windows, Windows Mobile, MacOS X, Linux, Symbian)

Für mich besonders wichtig ist die Unterstützung von SOAP/Security. Denn damit können RIAs vernünftig an Service-orientierten Architekturen angebunden werden und Sessions lassen sich darüber realisieren.

Für mich steht eines fest: Im Laufe der nächsten zwei Jahre werden sich RIA-Frameworks vor allem in Business-Anwendungen durchsetzen können. Denn – und das habe ich bei der Xtopia oft genug gehört – : “Die User-Experience ist wichtig – vielleicht sogar wichtiger als die Funktionalität”.

Keine Kommentare

UML2 to Hibernate für Lau

Über http://www.mda4eclipse.com/2007/05/acceleo-20-free-module-for-uml2-to.html bin ich darüber gestolpert, dass Accelo in den nächsten Monaten sein UML2Hibernate-Plugin für Eclipse neben der kommerziellen Lizenz ebenfalls unter einer Open Source-Lizenz freigeben wird.

Keine Kommentare

Blog-Empfehlung: Ralph Westphal

Der Blog von Ralph Westphal behandelt Themen rund um Software Architektur – definitiv lesenswert!

2 Kommentare

API für Google Mobile Maps

Nach einigem Suchen bin ich endlich fündig geworden. Neil Young hat die Pakete mit Wireshark analysiert und heraus kam folgender C#-Code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Diagnostics;

/*
 * Sample code to obtain geo codes from a cell info
 * "GSM/UMTS" setting revealed by smuraro, thanks!
 */

/* (c) "Neil Young" (neil.young@freenet.de)
 *
 * This script/program is provided "as is".
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * GNU General Public License, see <http://www.gnu.org/licenses/>.
 */

namespace GMM {
    class Program {
        static byte[] PostData(int MCC, int MNC, int LAC, int CID, bool shortCID) {
            /* The shortCID parameter follows heuristic experiences:
             * Sometimes UMTS CIDs are build up from the original GSM CID (lower 4 hex digits)
             * and the RNC-ID left shifted into the upper 4 digits.
             */
            byte[] pd = new byte[] {
                0x00, 0x0e,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00,
                0x00, 0x00,
                0x00, 0x00,

                0x1b,
                0x00, 0x00, 0x00, 0x00, // Offset 0x11
                0x00, 0x00, 0x00, 0x00, // Offset 0x15
                0x00, 0x00, 0x00, 0x00, // Offset 0x19
                0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, // Offset 0x1f
                0x00, 0x00, 0x00, 0x00, // Offset 0x23
                0x00, 0x00, 0x00, 0x00, // Offset 0x27
                0x00, 0x00, 0x00, 0x00, // Offset 0x2b
                0xff, 0xff, 0xff, 0xff,
                0x00, 0x00, 0x00, 0x00
            };

            bool isUMTSCell = ((Int64)CID > 65535);

            if (isUMTSCell)
                Console.WriteLine("UMTS CID. {0}", shortCID ? "Using short CID to resolve." : "");
            else
                Console.WriteLine("GSM CID given.");

            if (shortCID)
                CID &= 0xFFFF;      /* Attempt to resolve the cell using the GSM CID part */

            if ((Int64)CID > 65536) /* GSM: 4 hex digits, UTMS: 6 hex digits */
                pd[0x1c] = 5;
            else
                pd[0x1c] = 3;

            pd[0x11] = (byte)((MNC >> 24) & 0xFF);
            pd[0x12] = (byte)((MNC >> 16) & 0xFF);
            pd[0x13] = (byte)((MNC >> 8) & 0xFF);
            pd[0x14] = (byte)((MNC >> 0) & 0xFF);

            pd[0x15] = (byte)((MCC >> 24) & 0xFF);
            pd[0x16] = (byte)((MCC >> 16) & 0xFF);
            pd[0x17] = (byte)((MCC >> 8) & 0xFF);
            pd[0x18] = (byte)((MCC >> 0) & 0xFF);

            pd[0x27] = (byte)((MNC >> 24) & 0xFF);
            pd[0x28] = (byte)((MNC >> 16) & 0xFF);
            pd[0x29] = (byte)((MNC >> 8) & 0xFF);
            pd[0x2a] = (byte)((MNC >> 0) & 0xFF);

            pd[0x2b] = (byte)((MCC >> 24) & 0xFF);
            pd[0x2c] = (byte)((MCC >> 16) & 0xFF);
            pd[0x2d] = (byte)((MCC >> 8) & 0xFF);
            pd[0x2e] = (byte)((MCC >> 0) & 0xFF);

            pd[0x1f] = (byte)((CID >> 24) & 0xFF);
            pd[0x20] = (byte)((CID >> 16) & 0xFF);
            pd[0x21] = (byte)((CID >> 8) & 0xFF);
            pd[0x22] = (byte)((CID >> 0) & 0xFF);

            pd[0x23] = (byte)((LAC >> 24) & 0xFF);
            pd[0x24] = (byte)((LAC >> 16) & 0xFF);
            pd[0x25] = (byte)((LAC >> 8) & 0xFF);
            pd[0x26] = (byte)((LAC >> 0) & 0xFF);

            return pd;
        }

        static void Main(string[] args) {

            if (args.Length < 4) {
                Console.WriteLine("Usage: gmm MCC MNC LAC CID [\"shortcid\"]");
                return;
            }
            string shortCID = "";   /* Default, no change at all */
            if (args.Length == 5)
                 shortCID = args[4].ToLower();

            try {
                String url = "http://www.google.com/glm/mmap";
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(url));
                req.Method = "POST";

                int MCC = Convert.ToInt32(args[0]);
                int MNC = Convert.ToInt32(args[1]);
                int LAC = Convert.ToInt32(args[2]);
                int CID = Convert.ToInt32(args[3]);
                byte[] pd = PostData(MCC, MNC, LAC, CID, shortCID == "shortcid");

                req.ContentLength = pd.Length;
                req.ContentType = "application/binary";
                Stream outputStream = req.GetRequestStream();
                outputStream.Write(pd, 0, pd.Length);
                outputStream.Close();

                HttpWebResponse res = (HttpWebResponse)req.GetResponse();
                byte[] ps = new byte[res.ContentLength];
                int totalBytesRead = 0;
                while (totalBytesRead < ps.Length) {
                    totalBytesRead += res.GetResponseStream().Read(ps, totalBytesRead, ps.Length - totalBytesRead);
                }

                if (res.StatusCode == HttpStatusCode.OK) {
                    short opcode1 = (short)(ps[0] << 8 | ps[1]);
                    byte opcode2 = ps[2];
                    System.Diagnostics.Debug.Assert(opcode1 == 0x0e);
                    System.Diagnostics.Debug.Assert(opcode2 == 0x1b);
                    int ret_code = (int)((ps[3] << 24) | (ps[4] << 16) | (ps[5] << 8) | (ps[6]));

                    if (ret_code == 0) {
                        double lat = ((double)((ps[7] << 24) | (ps[8] << 16) | (ps[9] << 8) | (ps[10]))) / 1000000;
                        double lon = ((double)((ps[11] << 24) | (ps[12] << 16) | (ps[13] << 8) | (ps[14]))) / 1000000;
                        Console.WriteLine("Latitude: {0}, Longitude: {1}", lat, lon);

                        Process p = new Process();
                        p.StartInfo.FileName = "iexplore";

                        Console.WriteLine("\nClose map window to exit\n");

                        p.StartInfo.Arguments = String.Format(
                            "http://maps.google.de/maps?f=q&hl=de&q={0},{1}&ie=UTF8&z=15",
                            lat.ToString().Replace(',','.'), lon.ToString().Replace(',','.'));
                        p.Start();
                        p.WaitForExit();
                    }
                    else
                        Console.WriteLine("Error {0}", ret_code);
                }
                else
                    Console.WriteLine("HTTP Status {0} {1}", res.StatusCode, res.StatusDescription);
            }
            catch (Exception) {
                throw;
            }
        }
    }
}

Sehr gute Arbeit!
Die Beschreibung des Hexdumps gibt es unter http://maps.alphadex.de/datafiles/fct0e1b117823ccc1a.txt, der Blog von Neil ist unter http://foreverneilyoung.blogspot.com/ zu finden.

Weitere Infos: Anleitung zum Schreiben einer eigenen Anwendung mit GMM-API in Java
Google Maps Mobile API in Python.

Keine Kommentare