Google Safe Browsing API Abfrage mit Java

Wer mit dem Google Chrome Browser im World Wide Web herumsurft wird vielleicht auch schon einmal über folgenden Dialog gestolpert sein.

safe_browsing_mac

Mit diesem Dialog wird der User davor gewarnt dass er auf eine Seite surfen will die entweder mit einer Malware verseucht ist oder es sich um eine Phishing Seite handelt.

Der Dienst der hinter diesem Dialog steckt nennt sich Google Safe Browsing Dienst. Er ist öffentlich zugänglich und kann mit Hilfe von zwei APIs angesprochen werden.

Aktuell benutzen die Browser Chrome, Safari und Mozilla Firefox diesen Dienst um den Benutzer über Malware- und Phishingseiten zu informieren.

Es ist entweder möglich mit der Safe Browsing API v2 eine Datei herunterzuladen um lokal nach verseuchten URLs zu suchen oder mit der Safe Browsing Lookup API eine Anfrage über HTTP an Google zu senden.
Wer oft und viele URLs überprüfen muss wird eher die erste API wählen damit kann die Suche lokal durchgeführt werden. Man muss aber dafür sorgen das die Datei regelmässig aktualisiert wird. Die Lookup API hat den Nachteil dass ein Request über das Internet gesendet werden muss. Dafür ist die abgefragte Datenbasis immer aktuell.

Für das folgende Beispiel beschränken wir uns auf die Lookup API und zeigen wie man sie mit Java anspricht. Als Beispiel bauen wir uns ein kleines Programm das regelmässig eine Liste von URLs überprüft.

Als erstes benötigen wir einen API Key, der auf folgender Seite beantragt werden kann:
https://developers.google.com/safe-browsing/key_signup
Nachdem man die Nutzungsbestimmungen akzeptiert hat erhält man den Key.

Folgende Einschränkungen der Lookup API sollte man beachten: Pro POST Request dürfen maximal 500 URLs abgefragt werden. Innerhalb einer 24 Stunden Periode dürfen maximal 10’000 Requests abgesetzt werden. Diese Einschränkungen sollten kein Problem sein wenn man zum Beispiel plant das Programm einmal pro Stunde zu starten.

Der Zugriff auf die Lookup API geschieht entweder mit einem GET oder einem POST Request. Wir beschränken uns hier auf den POST Request. Eine Beschreibung zum GET Request findet man in der Dokumentation.

Die URL für den Post Request ist wie folgt aufgebaut:

https://sb-ssl.google.com/safebrowsing/api/lookup?client=java&apikey=12345&appver=1.0.0&pver=3.0

  • client: Ist eine Bezeichnung für unser Programm. Dies kann ein beliebiger Name sein.
  • apikey: Hier geben wir den erhaltenen API Key an.
  • appver: Gibt die Version unseres Programmes an.
  • pver: Gibt die Protokoll Version an, die unser Programm unterstützt. Dies ist aktuell immer 3.0

Mit der HTTP Components Client Library von Apache können wir die gewünschte URL sehr einfach mit der Klasse URIBuilder zusammenbasteln:

Im Body des POST Requests werden nun die URLs aufgelistet, die man abfragen möchte. Die erste Zeile enhält die Anzahl URLs. Jede Zeile muss mit einem LF getrennt werden. Es muss mindestens eine URL angegeben werden. Wenn die Anzahl in der ersten Zeile nicht mit der tatsächlichen Anzahl übereinstimmt wird ein Status Code 400 zurückgegeben.

Mit der HTTP Client Library kann der Body wie folgt aufgebaut werden:

Den Request wird nun wie folgt gesendet:

Die Lookup API kann folgende Status Codes zurückgeben:

  • 200: Eine oder mehrere URLs befinden sich auf der Malware/Phishing Liste.
  • 204: Keine der angefragten URLs ist eingetragen.
  • 400: Bad Request. Mögliche Fehler sind fehlende Request Parameter oder falsche Anzahl URLs
  • 401: Nicht authorisiert. Zum Beispiel wenn kein oder ein falscher API Key angegeben wurde.
  • 503: Service nicht verfügbar. Wenn der Google Server einen Fehler hat oder wenn der Client gedrosselt wird, weil er zuviele Requests gesendet hat.

Im Programm kann der Status Code folgendermassen ausgewertet werden:

Wenn uns Google einen Staus von 200 zurückgibt dann ist eine oder mehrere der angefragten URLs in der Malware/Phishingliste eingetragen. Der Body der Response gibt genauer Auskunft welche der URLs betroffen ist und um welche Art von Befall es sich handelt. Die Antwort enthält genau gleich viele Zeilen wie URLs die angefragt wurden. Die erste Zeile bezieht sich dabei auf die erste URL, die zweite Zeile auf die zweite URL und so weiter. Die Antwort enthält entweder “phishing”, “malware”, “phishing,malware” oder “ok”.

Die Response für dieses Beispiel sieht wie folgt aus:

“ok” bezieht sich hier also auf http://www.google.com/ und “malware” auf die URL http://ianfette.org/
Beachten sollte man das der Body leer ist wenn der Dienst einen Status Code 204 zurückliefert. In dem Fall ist alles in Ordnung und keine URL in der Liste eingetragen.

Im Code kann der Response Body wie folgt ausgelesen werden:

Das komplette Programm findet man auf GitHub:
https://github.com/ralscha/playground/tree/master/safebrowse

Lazy Objekte mit Jackson und Hibernate

Wenn man mit Spring MVC, Hibernate/JPA und Jackson Entity Klassen serialisiert, dann kann es passieren dass das Programm eine Exception mit folgendem Text “…failed to lazily initialize a collection of ….” wirft.

Dies passiert immer dann wenn das Programm in der Service Klasse Entitäten lädt, die zum Beispiel @OneToMany Beziehungen haben die Lazy geladen werden. Das Problem ist das Jackson beim Serialisieren des Objektes die get Methoden der einzelnen Felder aufruft und wenn er die get Methode der “Lazy”-Beziehung aufruft einen Datenbankzugriff auslöst. Da dieser Zugriff nun aber ausserhalb der ursprünglichen Service Methode stattfindet hat Jackson keinen Zugriff auf die Hibernate Session oder den JPA EntityManager, was zum Absturz führt.

Für das folgende Beispiel benutzen wir die Entitäten Club und Spieler. Der Club hat eine OneToMany Beziehung zu den Spielern, welche Lazy geladen wird.

Als Service Methode benutzen wir einen Spring MVC @Controller der auf die URL /club gemappt ist und wegen der @ResponseBody Annotation den Return Wert in ein JSON umwandelt.

Wenn man nun den Service via Browser http://…./club aufruft erhält man die oben beschriebene Exception.

com.fasterxml.jackson.databind.JSONMappingException: failed to lazily initialize a collection of role: mypackage.Club.spieler, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->mypackage.Club["spieler"])
com.fasterxml.jackson.databind.JSONMappingException.wrapWithPath(JSONMappingException.java:232)
com.fasterxml.jackson.databind.JSONMappingException.wrapWithPath(JSONMappingException.java:197)

Um dies zu lösen gibt es verschiedene Möglichkeiten.
Man kann in der Service Methode die spieler Collection von jedem Club mit Hibernate.initialize aktiv laden. Auf diese Art sind alle Entitäten geladen wenn die Clubs mit return zurückgegeben werden und Jackson dann die Objekte ins JSON serialisiert.

Oder man konfiguriert das Query so, dass die Spieler Relation zusätzlich geladen wird. Mit Querydsl ruft man dazu nach dem Join die fetch() Methode auf.

Ein erneuter Aufruf im Browser ergibt dann dieses JSON: [{"id":1,"name":"one","spieler":[{"id":1,"vorname":"Urs","nachname":"Meier"}]}]

Dies hat den Nachteil das man solchen oder ähnlichen Code überall einbauen muss wo Beziehungen bestehen die Lazy geladen werden.

Die zweite Möglichkeit ist es das Feld spieler komplett mit der Annotation @JsonIgnore zu ignorieren.
Das JSON sieht dann so aus: [{"id":1,"name":"one"}]

Hat den Nachteil das dies eine statische Konfiguration ist, die man während der Laufzeit nicht mehr ändern kann. Kann zum Problem führen wenn doch ein Service den Club zusammen mit den Spielern zurückgeben möchte. Zusätzlich muss die Annotation überall eingefügt werden, wo ein Lazy Objekt vorhanden ist.

Die dritte Möglichkeit ist es einen OpenEntityManagerInViewFilter oder OpenSessionInView Filter im web.xml einzubauen.

Das Resultat sieht dann gleich aus wie mit der Hibernate.initialize Methode: [{"id":1,"name":"one","spieler":[{"id":1,"vorname":"Ralph","nachname":"Schaer"}]}]

Vorteil ist das die Entitäten und Service Methoden nicht verändert werden müssen. Der Nachteil hier ist aber das nun jedes Lazy geladene Objekt im JSON landet und dieses Verhalten während der Laufzeit nicht verändert werden kann.

Mit der vierten Möglichkeit machen wir nun genau das Umgekehrte als mit dem OpenEntityManagerInViewFilter. Dazu wird das Hibernate4Module im ObjectMapper konfiguriert.

Das Hibernate4Module serialisiert als Default die Lazy Objekte mit null. Wie mit der OpenEntityManagerInViewFilter Methode ist dies eine globale Einstellung die alle Lazy geladene Objekte, die mit Jackson serialisiert werden, betrifft. Auch mit dieser Lösung müssen Entitäten und Service Methoden nicht verändert werden.

Hier ein Beispiel wie man den ObjectMapper und das Hibernate4Module mit Spring’s JavaConfig konfiguriert.

Als zusätzliche Dependency wird die Library jackson-datatype-hibernate4 benötigt. In einem Mavenprojekt konfiguriert man die Dependency wie folgt

Das JSON sieht nun so aus: [{"id":1,"name":"one","spieler":null}]

Der Nachteil hier ist, dass die Angabe für den Spieler im JSON (null) nicht den Tatsachen in der Datenbank entspricht. Der Client müsste also in diesem Beispiel das Feld spieler ignorieren.

Falls doch einmal die spieler Collection im JSON landen soll, kann das Programm die Spieler in der Service Methode mit Hibernate.initialize oder einem entsprechenden Fetch Query laden und erzeugt so dieses JSON: [{"id":1,"name":"one","spieler":[{"id":1,"vorname":"Ralph","nachname":"Schaer"}]}]

Im Gegensatz zu der OpenEntityManagerInViewFilter Konfiguration haben wir also hier die Möglichkeit zur Laufzeit zu entscheiden ob die Collection in das JSON serialisiert werden soll oder nicht.

Anmerkung: Wenn die Spieler immer zusammen mit dem Club geladen werden sollen kann auch der FetchType von LAZY auf EAGER geändert werden.

Das Hibernate4Module ignoriert per Default Felder die mit @Transient gekennzeichnet sind. Wenn solche Felder trotzdem in das JSON serialisiert werden sollen muss das Feature ausgeschaltet werden.

Das Hibernate4Module kann auch angewiesen werden Lazy Loaded Objekte immer zu laden anstatt null auszugeben.

Falls das Module so konfiguriert ist muss auch ein OpenEntityManagerInViewFilter/OpenSessionInViewFilter konfiguriert werden, ansonsten erhält man wieder die am Anfang aufgeführte Exception. Da aber der Filter bereits Lazy Objekte lädt ist das Hibernate4Module hier überflüssig. Ausser man benötigt das Modul zusätzlich für das @Transient Handling.

Zusammenfassend kann man sagen dass wenn Lazy Objekte immer in das JSON geschrieben werden sollen, man den OpenSessionFilter einsetzt. Mit dem Hibernate4Module wird immer null rausgeschrieben, man kann dies aber übersteuern indem man die Lazy Objekte in der Service Methode lädt. Der kleine Nachteil hier ist das Lazy Objekt im JSON den Wert null erhalten, wenn sie nicht geladen werden.

Eine weitere Möglichkeit ist es in der Service Methode spezielle Datentransfer Objekte (DTO) zu instanziieren und abzufüllen. Diese haben den Vorteil dass man keine Probleme mit Lazy Objekte hat und genau das JSON erstellt wird dass man benötigt aber den Nachteil das man die Daten von der Entity Klasse in die DTO Klasse “umfüllen” muss. Hier können aber Libraries wie Dozer helfen.

Eine weitere Möglichkeit ist es, wenn die Service Methode Maps zurückgibt. Damit lassen sich komplett dynamische JSON Dokumente zur Laufzeit erzeugen. Die ImmutableMap Klasse aus der Guava Library ist hierbei sehr hilfreich.

Amazon Glacier mit Java

Glacier ist ein Speicherdienst der zu den Amazon Web Services gehört. Der Service wurde im August 2012 lanciert und funktioniert auf den ersten Blick ähnlich wie S3. Glacier erlaubt es Daten in die Amazon Cloud hoch- und wieder herunterzuladen. Glacier bietet das gleiche Service Level Agreement wie S3, ist designed für eine Durability von 99.999999999% (elf neun), eine Verfügbarkeit von 99.99% und soll einen Ausfall von zwei Amazon Rechenzentren verkraften (alles gleich wie bei S3 Standard).

Dieses Video von Amazon zeigt einen Überblick über Glacier: http://www.youtube.com/watch?v=OUTOEX4zbD4

Bei genauerem Hinschauen wird man aber einige Unterschiede zu S3 feststellen. Als erstes fällt auf dass die Kosten viel tiefer liegen als bei S3.

In der US Region kostet 1 GB pro Monat bei S3 Standard 0.095 US-Cent. Mit S3 Reduced Redundancy Storage sind es noch 0.076 Cent. Bei Glacier kostet 1 GB pro Monat nur 1 US-Cent. Für S3 sind das die Kosten für das erste TB. Bei grösseren Datenmengen fallen die Preis pro GB. Bei Glacier zahlt man immer 1 Cent pro GB egal wieviele Daten man speichert. Wenn auf Glacier weniger als 1 GB gespeichert ist, wird das Minimum von 1 Cent fällig.

Ein interessanter Dienst um die Kosten des Cloudcomputing zu berechnen und zu planen ist PlanForCloud.com. In deren Blog findet man einen Artikel der die Kosten von Glacier und S3 vergleicht: http://blog.planforcloud.com/2012/08/cost-comparison-amazon-glacier-vs-s3.html

Bei Glacier muss man beachten dass beim Löschen zusätzliche Kosten von 3 Cent pro GB entstehen, wenn die Dateien jünger als 90 Tage sind (Differenz zwischen Upload- und Löschdatum). Die 3 Cent werden anteilsmässig verrechnet. Wenn Daten im ersten Monat nach dem Upload gelöscht werden, werden die ganzen 3 Cent fällig. Löschen im zweiten Monat kostet noch 2 Cent, im dritten Monat sind es 1 Cent und nach 90 Tagen ist das Löschen kostenlos.

Der reine Upload Datentransfer ist bei beiden Diensten kostenlos. Allerdings kommen Kosten für die Requests dazu. Bei S3 (put,copy,post,list,get) sind das 1 Cent pro Tausend und bei Glacier (upload,retrieval) sind es 5 Cent pro Tausend Requests. Bei Glacier werden die Kosten für die Requests anteilsmässig abgerechnet. Wenn nur 30 Requests (zum Beispiel ein Upload pro Tag) in einem Monat durchgeführt wurden zahlt man 1 Cent.

Die Kosten für den Download ist bei beiden Diensten gleich. Das erste GB pro Monat ist gratis und danach zahlt man 12 Cent pro GB bis 10 TB/Monat. Bei Glacier wird es nun aber kompliziert, da je nachdem wieviel und wie schnell man Daten wieder herunterlädt, kommen zusätzliche, zum Teil erhebliche, Kosten dazu.

Glacier erlaubt es 5% der gespeicherten Daten pro Monat ohne weitere Kosten downzuloaden. Zum Beispiel wenn 250 GB Daten auf Glacier gespeichert sind kann man 0.17% oder 0.417GB pro Tag ohne weitere Kosten herunterladen (hier wird ein Monat mit 30 Tagen angenommen). Über diese Limite hinaus wird es kompliziert und ich verweise hier auf die offizielle FAQ sowie auf mehrere Blogposts.

Kurz gesagt, je mehr Daten und je kürzer der Zeitraum ist, über den die Daten heruntergeladen werden, desto teurer wird es.
Glacier ist wegen dieser Retrievalkosten vorallem für Backups und die Archivierung von Daten gedacht. In diesen Anwendungen werden Daten vorallem hochgeladen und gelagert und selten wieder heruntergeladen. Bei der Archivierung ist es zudem oft möglich Downloads über mehrere Tage zu verteilen was die Downloadkosten zum Teil erheblich senkt. Backups müssen dagegen komplett und in kurzer Zeit wieder heruntergeladen werden können, was zu hohen Retrieval Kosten führen kann. Allerdings sollte dieser Fall selten auftreten und man ist froh überhaupt ein Backup zu haben. Glacier ist hier eine Art Versicherung bei der die Prämie erst im Schadenfall gezahlt werden muss.

Ein weiterer wichtiger Unterschied zu S3 ist der dass bei Glacier die Daten nicht sofort heruntergeladen werden können. Zuerst muss ein Retrievaljob gestartet werden. Dieser läuft zwischen 3 und 5 Stunden und erst wenn dieser Job erfolgreich beendet wurde können die verlangten Dateien heruntergeladen werden. Diese 3-5 Stunden Verzögerung gilt es zu beachten wenn eine Backuplösung mit Glacier aufgebaut wird.

Es sei hier noch darauf hingewiesen dass seit November 2012 Glacier mit S3 verknüpft werden kann. Diese Verknüpfung ermöglicht es Dateien zwischen S3 und Glacier hin- und herzuverschieben. Damit lassen sich Anwendungsfälle umsetzen, wie der dass die Daten zuerst auf S3 hochgeladen und erst wenn man sie eine längere Zeit nicht mehr benötigt auf Glacier verschoben werden. Zum Beispiel könnte man Backups zuerst auf S3 speichern um im Falle eines Disasterrecovery die Daten sofort wieder herunterladen zu können und so die 3-5 Stunden Verzögerung von Glacier umgeht, bei allerdings höheren Storagekosten. Backups die älter als eine Woche oder ein Monat sind würden laufend auf Glacier verschoben um Storagekosten zu sparen. Dies wird mit einer Lifecycle Konfiguration bewerkstelligt, die einmal installiert, automatisch die Dateien verschiebt. Beachten muss man das Files die von S3 auf Glacier verschoben werden, nur mit S3 zurückgespielt und gelöscht werden können.

Die folgenden Beispiele zeigen wie man mit Java und dem AWS SDK Daten direkt in Glacier hoch- und wieder herunterlädt. Die Integration mit S3 lassen wir hier mal aussen vor und sparen uns dies für einen späteren Blogpost auf.

Bevor wir beginnen wird ein Account bei Amazon benötigt. Da bei den Amazon Web Services keine Setupgebühren fällig werden, können wir gratis einen Account erstellen und uns bei den entsprechenden Diensten anmelden. Auch wenn keine Setupgebühren verrechnet werden muss man beachten das die folgenden Beispiele Kosten generieren, wenn man sie ausführt.

Glacier kennt die Begriffe Archiv und Vault. Ein Archiv ist entweder eine einzelne Datei oder mehrere Dateien komprimiert in einem TAR/ZIP oder einem anderen Archivformat. Archive werden in Vaults abgespeichert.

Glacier erlaubt es Archive bis zu einer Grösse von 40TB abzuspeichern. Beachten muss man das pro Uploadrequest nur 4GB gesendet werden können. Grössere Archive müssen aufgeteilt und in mehreren Requests hochgeladen werden. Amazon empfiehlt bereits Dateien grösser als 100MB in mehrere Teile aufzusplitten.

Vault Management

Um überhaupt Daten in Glacier hochzuladen muss ein Vault erstellt werden. Es können bis zu 1000 Vaults pro Amazon Account erstellt werden. Der Name des Vault kann zwischen 1 und 255 Zeichen lang sein, darf die Zeichen a-z, A-Z, 0-9 und die Sonderzeichen _ (Unterstrich), – (Bindestrich) und . (Punkt) enthalten.

Um Requests abzusetzen müssen wir uns bei AWS einloggen. Dazu benötigen wir eine Access Key Id und einen Secret Access Key. Diese findet man, wenn man sich auf der AWS Webseite eingeloggt hat, im Menu MyAccount / Security Credentials. In diesem Beispiel benutzen wir die Klasse BasicAWSCredentials deren Constructor die beiden Keys erwartet. Eine weitere Möglichkeit ist es die Keys mit PropertiesCredentials aus einem Propertyfile einzulesen.

Als nächstes wird eine Instanz des AmazonGlacierClient erstellt über welche alle Requests zu Glacier laufen. Hier müssen wir auch den Endpoint bestimmen mit welchem wir arbeiten möchten. Die Endpoints sind die Rechenzentren von Amazon, die über die Welt verteilt sind. Beachten sollte man das nicht jeder Dienst in jeder Region verfügbar ist. Ausserdem fallen unterschiedliche Kosten in den Regionen an.
Nachdem wir nun eine Instanz des AmazonGlacierClient erstellt haben können wir einen CreateVaultRequest senden. In diesem Beispiel erstellen wir einen Vault mit Namen “myVault”.

Um einen Vault zu löschen muss ein DeleteVaultRequest mit dem Namen des Vaults gesendet werden.

Der Löschrequest ist nur erfolgreich wenn der Vault keine Archive gemäss dem letzten Inventar enthält und wenn keine Schreiboperationen seit dem letzten Inventar durchgeführt wurden. Wenn diese Bedingungen nicht erfüllt sind wirft deleteVault eine Exception. Vault Inventare werden einmal pro Tag erstellt.

Mit einem ListVaultsRequest können wir alle Vaults eines Accounts auflisten. Das Resultat enthält Informationen wie Name des Vault, wann er erstellt wurde, wann das letzte Inventar durchgeführt wurde und die Anzahl Archive die im Vault gespeichert sind. Beachten muss man dass die Anzahl Archive kein Realtime Wert ist sondern nur einmal täglich, wenn der Inventarjob läuft, aktualisiert wird. Wenn also seit dem letzten Inventar Archive hochgeladen wurden sind diese nicht in der Anzahl enthalten.

Archiv Upload

Für den Upload bietet das AWS SDK eine High- und eine Low-Level API an. In diesem Blog beschränken wir uns auf die High-Level API. Weitere Informationen zu der Low-Level API findet man hier:
http://docs.aws.amazon.com/amazonglacier/latest/dev/uploading-an-archive-single-op-using-java.html

Die High-Level API besteht vereinfacht gesagt aus der Klasse ArchiveTransferManager. Diese Klasse sendet kleine Dateien in einem Stück und splittet grosse Dateien auf und sendet sie in mehreren Requests. Mit der Low-Level API müssen wir diese Dinge selber managen, haben aber dafür auch mehr Kontrolle über den Prozess. Zusätzlich sollte man darauf achten das die Daten zwar mit einer SSL Verbindung verschlüsselt über das Internet übertragen, aber dann unverschlüsselt bei Amazon abgespeichert werden. Es empfiehlt sich daher die Daten vor dem Upload zu verschlüsseln. Aktuell (Stand SDK 1.4.1) muss man die Verschlüsselung selber implementieren da keine Klasse für Glacier ähnlich wie AmazonS3EncryptionClient existiert, welche die Verschlüsselung vor dem Upload transparent durchführt.

Die upload Methode des ArchiveTransferManager erwartet als Parameter den Namen des Vaults, eine Beschreibung des Archivs und die Datei selber. In diesem Beispiel laden wir eine Textdatei mit Namen test.txt hoch. Mit Glacier werden Dateien nicht mit ihrem Namen angesprochen sondern mit einer Archiv Id, welche wir nach erfolgreichem Upload erhalten. Dank diesem Mapping von Datei zu Id können Archive nicht irrtümlicherweise überschrieben werden. Wenn wir das Programm mehrmals ausführen dann werden auf Glacier mehrere Archive erstellt, auch wenn der Inhalt des Archivs immer der gleiche ist. Dieses Verfahren hat aber auch den Nachteil das ein Archiv nicht aktualisiert werden kann. Man muss das aktualisierte Archiv uploaden und das zu ersetzende Archiv löschen.

Es wird empfohlen die Verknüpfung zwischen Dateien und Archive Id in einem eigenen System zu verwalten. Wir können diese Informationen zum Beispiel in einer simplen Textdatei oder einer Datenbank speichern. Denkbar wäre es auch diese ID bei Amazon selber in SimpleDB, RDS oder DynamoDB zu speichern.

Die Archive Ids lassen sich auch von Glacier selber mit einem Vault Inventory Retrieval Job auflisten. Hier muss man beachten dass auch dieser Job, wie der Archive Retrieval Job, das Ergebnis erst nach 3 bis 5 Stunden liefert. Dies ist auch einer der Gründe wieso man die Archive Id selber managen sollte. Wenn man die Id nicht kennt, kann es bis zu 10 Stunden dauern bis man eine Datei herunterladen kann. 3-5 Stunden für den Vault Inventory Retrieval Job und dann nochmals 3-5 Stunden für den Archive Retrieval Job.

Das folgende Beispiel zeigt wie ein Vault Inventory heruntergeladen werden kann.

Als erstes muss ein “inventory-retrieval” Job mit der Klasse InitiateJobRequest erstellt werden. Diese Klasse erwartet den Vault Name als Argument. Zusätzlich kann angegeben werden in welchem Format man den Output erwartet. Möglich sind JSON und CSV. Nachdem man den Job mit initiateJob gestartet hat erhält man eine Job Id. Mit Hilfe dieser Job Id kann der aktuelle Status des Jobs mit einem DescribeJobRequest abgefragt werden. Wenn der Job erfolgreich beendet wurde, wird mit einem GetJobOutputRequest der Output heruntergeladen. In diesem Beispiel erhalten wir den Output im CSV Format, der wie folgt aussieht:

Dieses Beispiel benutzt Polling um den Jobstatus abzufragen. Dies ist zwar sehr einfach umzusetzen aber im ungünstigsten Fall warten wir bis zu 30 Minuten länger als wir müssten. Amazon empfiehlt in der Dokumentation einen anderen Ablauf mit SNS und SQS. Ein Code Beispiel findet man in der Dokumentation: http://docs.aws.amazon.com/amazonglacier/latest/dev/retrieving-vault-inventory-java.html

Archiv Retrieval

Wie beim Archiv Upload gibt es auch beim Download eine High- und eine Low-Level API. In der High-Level API wird mit Hilfe des ArchiveTransferManager ein Archiv heruntergeladen. Diese Klasse benötigt neben dem AmazonGlacierClient auch eine Instanz des AmazonSQSClient und des AmazonSNSClient.

Der ArchiveTransferManager startet den “archive-retrieval” Job, konfiguriert den Job so dass der Output via SNS/SQS verschickt wird. Danach wartet das Programm bis der Output auf SQS eintrifft. Die Archivdaten werden dann mit GetJobOutputRequest heruntergeladen.

Mehr Kontrolle über den Retrieval Prozess bietet die Low-Level API an. Zum Beispiel lassen sich bestimmte Teile eines Archives herunterzuladen. Dazu kann ein Byte Bereich angegeben werden (zum Beispiel: bytes=0-1048575 um das erste MB herunterzuladen). Dies wird dann wichtig wenn die Retrievalkosten möglichst tief gehalten werden sollen indem man den Download über mehrere Stunden/Tage verteilt.

Beispiele mit der Low-Level API findet man hier:
http://docs.aws.amazon.com/amazonglacier/latest/dev/downloading-an-archive-using-java.html

Archive löschen

Ein Archiv wird mit einem DeleteArchiveRequest gelöscht. Der Request erwartet als Argument den Namen des Vaults und die Archiv Id.