/*
* (C) Copyright 2013 Scoop IT SAS (http://scoop.it/) and others.
*
* 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.
*
* Contributors:
* Philippe GASSMANN
* Jean-Baptiste BELLET
*/
package com.scoopit.weedfs.client;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scoopit.weedfs.client.caching.LookupCache;
import com.scoopit.weedfs.client.net.AssignResult;
import com.scoopit.weedfs.client.net.LookupResult;
import com.scoopit.weedfs.client.net.WriteResult;
import com.scoopit.weedfs.client.status.MasterStatus;
import com.scoopit.weedfs.client.status.VolumeStatus;
class WeedFSClientImpl implements WeedFSClient {
final URL masterURL;
final HttpClient httpClient;
final LookupCache lookupCache;
WeedFSClientImpl(URL masterURL, HttpClient httpClient, LookupCache lookupCache) {
this.masterURL = masterURL;
this.httpClient = httpClient;
this.lookupCache = lookupCache;
}
@Override
public Assignation assign(AssignParams params) throws IOException, WeedFSException {
StringBuilder url = new StringBuilder(new URL(masterURL, "/dir/assign").toExternalForm());
url.append("?count=");
url.append(params.versionCount);
if (params.replicationStrategy != null) {
url.append("&replication=");
url.append(params.replicationStrategy.parameterValue);
}
if (params.collection != null) {
url.append("&collection=");
url.append(params.collection);
}
HttpGet get = new HttpGet(url.toString());
try {
HttpResponse response = httpClient.execute(get);
ObjectMapper mapper = new ObjectMapper();
try {
AssignResult result = mapper.readValue(response.getEntity().getContent(), AssignResult.class);
if (result.error != null) {
throw new WeedFSException(result.error);
}
return new Assignation(result);
} catch (JsonMappingException | JsonParseException e) {
throw new WeedFSException("Unable to parse JSON from weed-fs", e);
}
} finally {
get.abort();
}
}
@Override
public void delete(WeedFSFile file, Location location) throws IOException, WeedFSException {
StringBuilder url = new StringBuilder();
if (!location.publicUrl.contains("http")) {
url.append("http://");
}
url.append(location.publicUrl);
url.append("/");
url.append(file.fid);
HttpDelete delete = new HttpDelete(url.toString());
try {
HttpResponse response = httpClient.execute(delete);
StatusLine line = response.getStatusLine();
if (line.getStatusCode() < 200 || line.getStatusCode() > 299) {
throw new WeedFSException("Error deleting file " + file.fid + " on " + location.publicUrl + ": " + line.getStatusCode() + " "
+ line.getReasonPhrase());
}
} finally {
delete.abort();
}
}
@Override
public List<Location> lookup(long volumeId) throws IOException, WeedFSException {
if (lookupCache != null) {
List<Location> ret = lookupCache.lookup(volumeId);
if (ret != null) {
return ret;
}
}
StringBuilder url = new StringBuilder(new URL(masterURL, "/dir/lookup").toExternalForm());
url.append("?volumeId=");
url.append(volumeId);
HttpGet get = new HttpGet(url.toString());
try {
HttpResponse response = httpClient.execute(get);
ObjectMapper mapper = new ObjectMapper();
try {
LookupResult result = mapper.readValue(response.getEntity().getContent(), LookupResult.class);
if (result.error != null) {
throw new WeedFSException(result.error);
}
if(lookupCache != null && result.locations != null && result.locations.size() > 0) {
lookupCache.setLocation(volumeId, result.locations);
}
return result.locations;
} catch (JsonMappingException | JsonParseException e) {
throw new WeedFSException("Unable to parse JSON from weed-fs", e);
}
} finally {
get.abort();
}
}
@Override
public int write(WeedFSFile file, Location location, File fileToUpload) throws IOException, WeedFSException {
if (fileToUpload.length() == 0) {
throw new WeedFSException("Cannot write a 0-length file");
}
return write(file, location, fileToUpload, null, null, null);
}
@Override
public int write(WeedFSFile file, Location location, byte[] dataToUpload, String fileName) throws IOException, WeedFSException {
if (dataToUpload.length == 0) {
throw new WeedFSException("Cannot write a 0-length data");
}
return write(file, location, null, dataToUpload, null, fileName);
}
@Override
public int write(WeedFSFile file, Location location, InputStream inputToUpload, String fileName) throws IOException, WeedFSException {
return write(file, location, null, null, inputToUpload, fileName);
}
private String sanitizeFileName(String fileName) {
if (StringUtils.isBlank(fileName)) {
return "file";
} else if (fileName.length() > 256) {
return fileName.substring(0, 255);
}
return fileName;
}
private int write(WeedFSFile file, Location location, File fileToUpload, byte[] dataToUpload, InputStream inputToUpload, String fileName)
throws IOException, WeedFSException {
StringBuilder url = new StringBuilder();
if (!location.publicUrl.contains("http")) {
url.append("http://");
}
url.append(location.publicUrl);
url.append('/');
url.append(file.fid);
if (file.version > 0) {
url.append('_');
url.append(file.version);
}
HttpPost post = new HttpPost(url.toString());
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
if (fileToUpload != null) {
if (fileName == null) {
fileName = fileToUpload.getName();
}
multipartEntityBuilder.addBinaryBody("file", fileToUpload, ContentType.APPLICATION_OCTET_STREAM, sanitizeFileName(fileName));
} else if (dataToUpload != null) {
multipartEntityBuilder.addBinaryBody("file", dataToUpload, ContentType.APPLICATION_OCTET_STREAM, sanitizeFileName(fileName));
} else {
multipartEntityBuilder.addBinaryBody("file", inputToUpload, ContentType.APPLICATION_OCTET_STREAM, sanitizeFileName(fileName));
}
post.setEntity(multipartEntityBuilder.build());
try {
HttpResponse response = httpClient.execute(post);
ObjectMapper mapper = new ObjectMapper();
try {
WriteResult result = mapper.readValue(response.getEntity().getContent(), WriteResult.class);
if (result.error != null) {
throw new WeedFSException(result.error);
}
return result.size;
} catch (JsonMappingException | JsonParseException e) {
throw new WeedFSException("Unable to parse JSON from weed-fs", e);
}
} finally {
post.abort();
}
}
@Override
public InputStream read(WeedFSFile file, Location location) throws IOException, WeedFSException, WeedFSFileNotFoundException {
StringBuilder url = new StringBuilder();
if (!location.publicUrl.contains("http")) {
url.append("http://");
}
url.append(location.publicUrl);
url.append('/');
url.append(file.fid);
if (file.version > 0) {
url.append('_');
url.append(file.version);
}
HttpGet get = new HttpGet(url.toString());
HttpResponse response = httpClient.execute(get);
StatusLine line = response.getStatusLine();
if (line.getStatusCode() == 404) {
get.abort();
throw new WeedFSFileNotFoundException(file, location);
}
if (line.getStatusCode() != 200) {
get.abort();
throw new WeedFSException("Error reading file " + file.fid + " on " + location.publicUrl + ": " + line.getStatusCode() + " "
+ line.getReasonPhrase());
}
return response.getEntity().getContent();
}
@Override
public MasterStatus getMasterStatus() throws IOException {
URL url = new URL(masterURL, "/dir/status");
HttpGet get = new HttpGet(url.toString());
try {
HttpResponse response = httpClient.execute(get);
StatusLine line = response.getStatusLine();
if (line.getStatusCode() != 200) {
throw new IOException("Not 200 status recieved for master status url: " + url.toExternalForm());
}
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(response.getEntity().getContent(), MasterStatus.class);
} catch (JsonMappingException | JsonParseException e) {
throw new WeedFSException("Unable to parse JSON from weed-fs", e);
}
} finally {
get.abort();
}
}
@Override
public VolumeStatus getVolumeStatus(Location location) throws IOException {
StringBuilder url = new StringBuilder();
if (!location.publicUrl.contains("http")) {
url.append("http://");
}
url.append(location.publicUrl);
url.append("/status");
HttpGet get = new HttpGet(url.toString());
try {
HttpResponse response = httpClient.execute(get);
StatusLine line = response.getStatusLine();
if (line.getStatusCode() != 200) {
throw new IOException("Not 200 status recieved for master status url: " + url.toString());
}
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(response.getEntity().getContent(), VolumeStatus.class);
} catch (JsonMappingException | JsonParseException e) {
throw new WeedFSException("Unable to parse JSON from weed-fs", e);
}
} finally {
get.abort();
}
}
}