Java tutorial
/* * Copyright 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.morea.handy.android; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.squareup.okhttp.Authenticator; import com.squareup.okhttp.Credentials; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.net.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import static io.morea.handy.android.EventDatabaseOpenHelper.Metadata.Events; /** * Sends all logged events to the configured server. Successfully transferred events will be removed from the local log * afterwards. * * @author Andreas Ahlenstorf */ class SendLogTask implements Runnable { private static final Logger log = Logger.getLogger(SendLogTask.class.getName()); private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8; version=1"); private final Context context; private final EventReporterConfiguration configuration; private final OkHttpClient client; private final Gson gson; public SendLogTask(final Context context, final EventReporterConfiguration configuration) { // Don't make Context a WeakReference, otherwise the reference could get collected before the task is actually // run and the task should be executed in any case. There's also no risk to create a memory leak here because // the task will be thrown away afterwards anyway. this.context = context; this.configuration = configuration; client = new OkHttpClient(); client.setConnectTimeout(configuration.getHttpTimeout(), TimeUnit.MILLISECONDS); client.setWriteTimeout(configuration.getHttpTimeout(), TimeUnit.MILLISECONDS); client.setReadTimeout(configuration.getHttpTimeout(), TimeUnit.MILLISECONDS); client.setAuthenticator(new Authenticator() { @Override public Request authenticate(Proxy proxy, Response response) { String credential = Credentials.basic(configuration.getProbeId(), configuration.getAccessKey()); return response.request().newBuilder().header("Authorization", credential).build(); } @Override public Request authenticateProxy(Proxy proxy, Response response) { return null; } }); GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Date.class, new DateSerializer()); this.gson = gsonBuilder.create(); } @Override public void run() { SQLiteDatabase db = null; try { EventDatabaseOpenHelper helper = new EventDatabaseOpenHelper(this.context); db = helper.getWritableDatabase(); db.beginTransaction(); try { final Set<UUID> sentEvents = new HashSet<>(); final Cursor cursor = db.query(Events.TABLE_NAME, Events.orderedColumns(), null, null, null, null, Events.DATE_RECORDED); try { final List<Event> events = new ArrayList<>(); while (cursor.moveToNext()) { events.add(Event.fromCursor(cursor)); if (events.size() == configuration.getSyncBatchSize()) { log.log(Level.FINE, String.format("Batch size %d reached.", configuration.getSyncBatchSize())); sentEvents.addAll(sendEvents(events)); events.clear(); } } sentEvents.addAll(sendEvents(events)); } finally { Util.closeQuietly(cursor); } removeSentEvents(db, sentEvents); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } catch (Exception e) { log.log(Level.SEVERE, "Failed to send events to server.", e); } finally { Util.closeQuietly(db); } } private Set<UUID> sendEvents(final List<Event> events) throws Exception { final Set<UUID> sentEvents = new HashSet<>(); if (events.isEmpty()) { return sentEvents; } log.log(Level.INFO, String.format("Sending %d events to server %s.", events.size(), configuration.getRemoteEndpoint())); final Request request = new Request.Builder().url(configuration.getRemoteEndpoint()) .post(RequestBody.create(MEDIA_TYPE_JSON, gson.toJson(events))) .addHeader("User-Agent", Constant.USER_AGENT).build(); final Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { log.log(Level.SEVERE, String.format("Failed to post events to server, response status is %d.", response.code())); return sentEvents; } for (Event e : events) { log.log(Level.FINE, String.format("Event %s successfully sent to server.", e.getIdentifier())); sentEvents.add(e.getIdentifier()); } return sentEvents; } private void removeSentEvents(SQLiteDatabase db, Collection<UUID> sentEvents) { for (UUID uuid : sentEvents) { log.log(Level.FINE, String.format("Removing event %s.", uuid)); db.delete(Events.TABLE_NAME, Events.IDENTIFIER + " = ?", new String[] { uuid.toString() }); } } }