Skip to content

Commit

Permalink
fixed several issues around FileStore implementations, added option t…
Browse files Browse the repository at this point in the history
…o store MS avatars on disk
  • Loading branch information
albogdano committed Mar 26, 2021
1 parent d4e75ba commit 9c3f250
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 28 deletions.
9 changes: 9 additions & 0 deletions para-core/src/main/java/com/erudika/para/Para.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.erudika.para.queue.Queue;
import com.erudika.para.rest.CustomResourceHandler;
import com.erudika.para.search.Search;
import com.erudika.para.storage.FileStore;
import com.erudika.para.utils.Config;
import com.erudika.para.utils.VersionInfo;
import java.io.File;
Expand Down Expand Up @@ -174,6 +175,14 @@ public static Queue getQueue() {
return CoreUtils.getInstance().getQueue();
}

/**
* @return an instance of the core FileStore class.
* @see FileStore
*/
public static FileStore getFileStore() {
return CoreUtils.getInstance().getFileStore();
}

/**
* Registers a new initialization listener.
*
Expand Down
27 changes: 27 additions & 0 deletions para-core/src/main/java/com/erudika/para/core/utils/CoreUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import com.erudika.para.queue.Queue;
import com.erudika.para.search.MockSearch;
import com.erudika.para.search.Search;
import com.erudika.para.storage.FileStore;
import com.erudika.para.storage.MockFileStore;
import com.erudika.para.utils.Config;
import com.erudika.para.utils.Pager;
import com.erudika.para.utils.Utils;
Expand Down Expand Up @@ -67,13 +69,15 @@ public enum CoreUtils implements InitializeListener {
private transient Search search;
private transient Cache cache;
private transient Queue queue;
private transient FileStore fileStore;
private transient IoTServiceFactory iotFactory;

{
dao = new MockDAO();
search = new MockSearch();
cache = new MockCache();
queue = new MockQueue();
fileStore = new MockFileStore();
logger.debug("Using default impementations - {}, {} and {}.",
dao.getClass().getSimpleName(),
search.getClass().getSimpleName(),
Expand Down Expand Up @@ -143,6 +147,17 @@ public void setQueue(Queue queue) {
this.queue = queue;
}

@Override
public FileStore getFileStore() {
return fileStore;
}

@Inject
@Override
public void setFileStore(FileStore fileStore) {
this.fileStore = fileStore;
}

@Override
public IoTServiceFactory getIotFactory() {
return iotFactory;
Expand Down Expand Up @@ -488,6 +503,18 @@ public static synchronized CoreUtils getInstance() {
*/
public abstract void setQueue(Queue queue);

/**
* Returns the FileStore object.
* @return {@link FileStore} object
*/
public abstract FileStore getFileStore();

/**
* Sets the FileStore object.
* @param fileStore {@link FileStore}
*/
public abstract void setFileStore(FileStore fileStore);

/**
* Returns the default IoT factory.
* @return factory instance
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2013-2021 Erudika. http://erudika.com
*
* 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.
*
* For issues and patches go to: https://github.com/erudika
*/
package com.erudika.para.storage;

import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;

/**
* In-memory FileStore.
* @author Alex Bogdanovski [alex@erudika.com]
*/
public class MockFileStore implements FileStore {

private ConcurrentHashMap<String, InputStream> fs = new ConcurrentHashMap<>();

@Override
public InputStream load(String path) {
if (!StringUtils.isBlank(path)) {
fs.get(path);
}
return null;
}

@Override
public String store(String path, InputStream data) {
if (!StringUtils.isBlank(path) && data != null) {
fs.put(path, data);
}
return path;
}

@Override
public boolean delete(String path) {
if (!StringUtils.isBlank(path)) {
fs.remove(path);
return true;
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.fasterxml.jackson.databind.ObjectReader;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
Expand Down Expand Up @@ -171,20 +172,20 @@ public UserAuthentication getOrCreateUser(App app, String accessToken) throws IO
user = User.readUserForIdentifier(user);
if (user == null) {
//user is new
user = new User();
user = new User(Utils.getNewId());
user.setActive(true);
user.setAppid(getAppid(app));
user.setEmail(StringUtils.isBlank(email) ? Utils.getNewId() + "@windowslive.com" : email);
user.setName(StringUtils.isBlank(name) ? "No Name" : name);
user.setPassword(Utils.generateSecurityToken());
user.setPicture(getPicture(accessToken, email));
user.setPicture(getPicture(getAppid(app), user.getId(), accessToken, email));
user.setIdentifier(Config.MICROSOFT_PREFIX + microsoftId);
String id = user.create();
if (id == null) {
throw new AuthenticationServiceException("Authentication failed: cannot create new user.");
}
} else {
if (updateUserInfo(user, email, name, accessToken)) {
if (updateUserInfo(user, email, name, accessToken, getAppid(app))) {
user.update();
}
}
Expand All @@ -196,8 +197,8 @@ public UserAuthentication getOrCreateUser(App app, String accessToken) throws IO
return SecurityUtils.checkIfActive(userAuth, user, false);
}

private boolean updateUserInfo(User user, String email, String name, String accessToken) throws IOException {
String picture = getPicture(accessToken, email);
private boolean updateUserInfo(User user, String email, String name, String accessToken, String appid) throws IOException {
String picture = getPicture(appid, user.getId(), accessToken, email);
boolean update = false;
if (!StringUtils.equals(user.getPicture(), picture)) {
user.setPicture(picture);
Expand All @@ -214,27 +215,26 @@ private boolean updateUserInfo(User user, String email, String name, String acce
return update;
}

private String getPicture(String accessToken, String email) throws IOException {
String pic = "";
private String getPicture(String appid, String userid, String accessToken, String email) throws IOException {
String pic = getGravatar(email);
if (accessToken != null) {
HttpGet profileGet = new HttpGet(PHOTO_URL);
profileGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
profileGet.setHeader(HttpHeaders.ACCEPT, "application/json");
try (CloseableHttpResponse resp = httpclient.execute(profileGet)) {
HttpEntity respEntity = resp.getEntity();
if (respEntity != null && respEntity.getContentType().getValue().startsWith("image")) {
byte[] bytes = IOUtils.toByteArray(respEntity.getContent());
if (bytes != null && bytes.length > 0) {
if (bytes.length < (200 * 1024)) {
String ctype = respEntity.getContentType().getValue();
if (Config.getConfigBoolean("ms_inline_avatars", true)) {
byte[] bytes = IOUtils.toByteArray(respEntity.getContent());
if (bytes != null && bytes.length > 0) {
byte[] bytes64 = Base64.encodeBase64(bytes);
pic = "data:" + respEntity.getContentType().getValue() + ";base64," + new String(bytes64);
} else {
logger.info("Profile picture too large for user " + email + " - " +
bytes.length / 1024 + "KB, switching to Gravatar.");
pic = getGravatar(email);
pic = "data:" + ctype + ";base64," + new String(bytes64);
}
} else {
pic = getGravatar(email);
pic = Para.getFileStore().store(Optional.
ofNullable(appid).orElse(Config.PARA) + "/" + userid + "." +
StringUtils.substringAfter(ctype, "/"), respEntity.getContent());
}
}
EntityUtils.consumeQuietly(respEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package com.erudika.para.storage;

import com.erudika.para.utils.Config;
import com.erudika.para.utils.Utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
Expand All @@ -32,7 +32,6 @@
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.StorageClass;

/**
* An implementation of the {@link FileStore} interface using AWS S3.
Expand All @@ -41,15 +40,15 @@
public class AWSFileStore implements FileStore {

private static final Logger logger = LoggerFactory.getLogger(AWSFileStore.class);
private static final String S3_URL = "https://s3-{0}.amazonaws.com/{1}/{2}";
private S3Client s3;
private String bucket;

/**
* No-args constructor.
*/
public AWSFileStore() {
this(Config.getConfigParam("para.s3.bucket", "org.paraio"));
this(Config.getConfigParam("para.s3.bucket", "org.paraio." +
new DefaultAwsRegionProviderChain().getRegion().id()));
}

/**
Expand Down Expand Up @@ -93,10 +92,18 @@ public String store(String path, InputStream data) {
PutObjectRequest por = PutObjectRequest.builder().
bucket(bucket).key(path).
metadata(om).
acl(ObjectCannedACL.PUBLIC_READ).
storageClass(StorageClass.REDUCED_REDUNDANCY).build();
s3.putObject(por, RequestBody.fromInputStream(data, data.available())); //.bucket, path, data, om
return Utils.formatMessage(S3_URL, new DefaultAwsRegionProviderChain().getRegion().id(), bucket, path);
acl(ObjectCannedACL.PUBLIC_READ).build();

try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buf = new byte[1024];
int length;
while ((length = data.read(buf)) > 0) {
baos.write(buf, 0, length);
}
s3.putObject(por, RequestBody.fromBytes(baos.toByteArray()));
}
final String key = path;
return s3.utilities().getUrl(b -> b.bucket(bucket).key(key)).toExternalForm();
}
} catch (IOException e) {
logger.error(null, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public InputStream load(String path) {
if (!StringUtils.isBlank(path)) {
FileInputStream fis = null;
try {
File f = new File(folder + File.separator + path);
File f = new File(folder + path);
fis = new FileInputStream(f);
return f.canRead() ? new BufferedInputStream(fis) : null;
} catch (FileNotFoundException ex) {
Expand Down Expand Up @@ -96,7 +96,7 @@ public String store(String path, InputStream data) {
int maxFileSizeMBytes = Config.getConfigInt("para.localstorage.max_filesize_mb", 10);
try {
if (data.available() > 0 && data.available() <= (maxFileSizeMBytes * 1024 * 1024)) {
File f = new File(folder + File.separator + path);
File f = new File(folder + path);
if (f.canWrite()) {
try (FileOutputStream fos = new FileOutputStream(f)) {
try (BufferedOutputStream bos = new BufferedOutputStream(fos)) {
Expand Down Expand Up @@ -128,7 +128,7 @@ public boolean delete(String path) {
path = path.substring(1);
}
if (!StringUtils.isBlank(path)) {
File f = new File(folder + File.separator + path);
File f = new File(folder + path);
return f.canWrite() && f.delete();
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
*/
package com.erudika.para.storage;

import com.erudika.para.Para;
import com.erudika.para.utils.Config;
import com.google.inject.AbstractModule;
import java.util.ServiceLoader;
import org.apache.commons.lang3.StringUtils;

/**
* The default storage module.
Expand All @@ -26,7 +30,47 @@
public class StorageModule extends AbstractModule {

protected void configure() {
bind(FileStore.class).to(AWSFileStore.class);
String selectedFileStore = Config.getConfigParam("fs", "");
if (StringUtils.isBlank(selectedFileStore)) {
if ("embedded".equals(Config.ENVIRONMENT)) {
bindToDefault();
} else {
bind(FileStore.class).to(AWSFileStore.class).asEagerSingleton();
}
} else {
if ("s3".equalsIgnoreCase(selectedFileStore) ||
AWSFileStore.class.getSimpleName().equalsIgnoreCase(selectedFileStore)) {
bind(FileStore.class).to(AWSFileStore.class).asEagerSingleton();
} else {
FileStore fsPlugin = loadExternalFileStore(selectedFileStore);
if (fsPlugin != null) {
bind(FileStore.class).to(fsPlugin.getClass()).asEagerSingleton();
} else {
// default fallback - not implemented!
bindToDefault();
}
}
}
}

void bindToDefault() {
bind(FileStore.class).to(LocalFileStore.class).asEagerSingleton();
}

/**
* Scans the classpath for FileStore implementations, through the
* {@link ServiceLoader} mechanism and returns one.
* @param classSimpleName the name of the class name to look for and load
* @return a FileStore instance if found, or null
*/
final FileStore loadExternalFileStore(String classSimpleName) {
ServiceLoader<FileStore> fsLoader = ServiceLoader.load(FileStore.class, Para.getParaClassLoader());
for (FileStore fs : fsLoader) {
if (fs != null && classSimpleName.equalsIgnoreCase(fs.getClass().getSimpleName())) {
return fs;
}
}
return null;
}

}

0 comments on commit 9c3f250

Please sign in to comment.