/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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 com.google.samples.apps.iosched.server.schedule.server.cloudstorage;
import com.google.appengine.api.appidentity.AppIdentityService;
import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.tools.cloudstorage.GcsFileMetadata;
import com.google.appengine.tools.cloudstorage.GcsFileOptions;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.appengine.tools.cloudstorage.GcsInputChannel;
import com.google.appengine.tools.cloudstorage.GcsOutputChannel;
import com.google.appengine.tools.cloudstorage.GcsService;
import com.google.appengine.tools.cloudstorage.GcsServiceFactory;
import com.google.appengine.tools.cloudstorage.RetryParams;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.samples.apps.iosched.server.schedule.Config;
import com.google.samples.apps.iosched.server.schedule.input.fetcher.RemoteJsonHelper;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.charset.Charset;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Logger;
/**
* Handle all interaction with GoogleCloudStorage.
*
*/
public class CloudFileManager {
private static final String DEFAULT_CHARSET_NAME = "UTF-8";
private final GcsService gcsService = GcsServiceFactory.createGcsService(
RetryParams.getDefaultInstance());
private final String defaultBucket;
private final GcsFilename productionManifestFile;
private final GcsFilename stagingManifestFile;
public CloudFileManager() {
defaultBucket = Config.CLOUD_STORAGE_BUCKET;
productionManifestFile = new GcsFilename(defaultBucket, Config.MANIFEST_NAME);
stagingManifestFile = new GcsFilename(defaultBucket, Config.MANIFEST_NAME_STAGING);
}
static public byte[] calulateHash(JsonElement contents) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 MessageDigest is not available");
}
OutputStream byteSink = new OutputStream() {
@Override
public void write(int b) throws IOException {
// ignore, since this is only used to calculate MD5
}
};
DigestOutputStream dis = new DigestOutputStream(byteSink, md);
new Gson().toJson(contents, new OutputStreamWriter(dis, Charset.forName(DEFAULT_CHARSET_NAME)));
return dis.getMessageDigest().digest();
}
/**
* Create or update a file in a GCC bucket, using the default ACL for the bucket.
*
* @param filename Name of file to create
* @param contents File contents
* @param shortCache If true, sets cache expiry to 0 sec. Otherwise, cache expiry is set to 6,000 sec.
* @throws IOException
*/
public void createOrUpdate(String filename, JsonElement contents, boolean shortCache)
throws IOException {
GcsFilename file = new GcsFilename(defaultBucket, filename);
GcsFileOptions options = new GcsFileOptions.Builder()
.mimeType("application/json")
.cacheControl("public, max-age="+(shortCache?0:6000))
.build();
GcsOutputChannel writeChannel = null;
try {
writeChannel = gcsService.createOrReplace(file, options);
Writer writer = Channels.newWriter(writeChannel, DEFAULT_CHARSET_NAME);
new Gson().toJson(contents, writer);
writer.flush();
} finally {
if (writeChannel != null) {
writeChannel.close();
}
}
}
public String getBucketName() {
return defaultBucket;
}
public String getProductionManifestURL() {
return productionManifestFile.getBucketName() + "/" + productionManifestFile.getObjectName();
}
public String getStagingManifestURL() {
return stagingManifestFile.getBucketName() + "/" + stagingManifestFile.getObjectName();
}
public JsonObject readProductionManifest() throws IOException {
return readFileAsJsonObject(productionManifestFile);
}
public JsonObject readStagingManifest() throws IOException {
return readFileAsJsonObject(stagingManifestFile);
}
public void createOrUpdateProductionManifest(JsonObject contents) throws IOException {
createOrUpdate(Config.MANIFEST_NAME, contents, true);
}
public void createOrUpdateStagingManifest(JsonObject contents) throws IOException {
createOrUpdate(Config.MANIFEST_NAME_STAGING, contents, true);
}
public JsonObject readFileAsJsonObject(String filename) throws IOException {
return readFileAsJsonObject(new GcsFilename(defaultBucket, filename));
}
public JsonObject readFileAsJsonObject(GcsFilename file) throws IOException {
GcsFileMetadata metadata = gcsService.getMetadata(file);
if (metadata == null) {
if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Development) {
// In the development server, try to fetch files on cloud storage via HTTP
Logger.getAnonymousLogger().info("fetching "+file.getObjectName()+" at "+Config.CLOUD_STORAGE_BASE_URL+file.getObjectName());
return RemoteJsonHelper.fetchJsonFromPublicURL(Config.CLOUD_STORAGE_BASE_URL+file.getObjectName());
}
return null;
}
GcsInputChannel readChannel = null;
try {
readChannel = gcsService.openReadChannel(file, 0);
JsonElement element = new JsonParser().parse(Channels.newReader(readChannel,
DEFAULT_CHARSET_NAME));
return element.getAsJsonObject();
} finally {
if (readChannel != null) {
readChannel.close();
}
}
}
}