package ch.elexis.core.data.service.internal;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import org.osgi.service.component.annotations.Component;
import org.slf4j.LoggerFactory;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.lock.types.LockResponse;
import ch.elexis.core.model.IPersistentObject;
import ch.elexis.core.services.IConflictHandler;
import ch.elexis.core.services.IConflictHandler.Result;
import ch.elexis.core.services.ILocalDocumentService;
import ch.rgw.tools.MimeTool;
@Component
public class LocalDocumentService implements ILocalDocumentService {
private HashMap<Object, File> managedFiles = new HashMap<>();
private HashMap<Object, LockResponse> managedLocks = new HashMap<>();
private HashMap<Class<?>, ISaveHandler> registeredSaveHandler = new HashMap<>();
private HashMap<Class<?>, ILoadHandler> registeredLoadHandler = new HashMap<>();
@Override
public Optional<File> add(Object documentSource, IConflictHandler conflictHandler)
throws IllegalStateException{
boolean readOnly = false;
if (documentSource instanceof IPersistentObject) {
LockResponse result =
CoreHub.getLocalLockService().acquireLock((IPersistentObject) documentSource);
if (result.isOk()) {
managedLocks.put(documentSource, result);
} else {
readOnly = true;
}
}
ILoadHandler loadHandler = registeredLoadHandler.get(documentSource.getClass());
if (loadHandler == null) {
throw new IllegalStateException("No load handler for [" + documentSource + "]");
}
String fileName = getFileName(documentSource);
Optional<File> ret =
writeLocalFile(fileName, loadHandler.load(documentSource), conflictHandler, readOnly);
ret.ifPresent(file -> {
managedFiles.put(documentSource, file);
});
return ret;
}
@Override
public void remove(Object documentSource, IConflictHandler conflictHandler){
// try to delete the file
File file = managedFiles.get(documentSource);
if (file != null && file.exists()) {
Path path = Paths.get(file.getAbsolutePath());
boolean deleted = false;
while (!(deleted = tryDelete(path))) {
Result result = conflictHandler.getResult();
if (result == Result.OVERWRITE) {
// try again
} else {
break;
}
}
if (deleted) {
removeManaged(documentSource);
}
} else {
removeManaged(documentSource);
}
}
@Override
public void remove(Object documentSource){
File file = managedFiles.get(documentSource);
if (file != null && file.exists()) {
tryDelete(Paths.get(file.getAbsolutePath()));
}
removeManaged(documentSource);
}
private void removeManaged(Object documentSource){
LockResponse lock = managedLocks.get(documentSource);
if (lock != null) {
CoreHub.getLocalLockService().releaseLock((IPersistentObject) documentSource);
managedLocks.remove(documentSource);
}
managedFiles.remove(documentSource);
}
private boolean tryDelete(Path path){
try {
Files.delete(path);
return true;
} catch (IOException e) {
return false;
}
}
@Override
public boolean contains(Object documentSource){
return managedFiles.containsKey(documentSource);
}
@Override
public Optional<InputStream> getContent(Object documentSource){
File file = managedFiles.get(documentSource);
if (file != null) {
try {
return Optional.of(new ByteArrayInputStream(
Files.readAllBytes(Paths.get(file.getAbsolutePath()))));
} catch (IOException e) {
LoggerFactory.getLogger(getClass()).error("Error reading file", e);
}
}
return Optional.empty();
}
/**
* Write a local file using the filename and the content.
*
* @param content
* @param fileName
* @param conflictHandler
*
* @return
*/
private Optional<File> writeLocalFile(String fileName, InputStream content,
IConflictHandler conflictHandler, boolean readOnly){
Path dirPath = Paths.get(CoreHub.getWritableUserDir().getAbsolutePath(), ".localdoc");
if (!Files.exists(dirPath, new LinkOption[0])) {
try {
Files.createDirectories(dirPath);
} catch (IOException e) {
LoggerFactory.getLogger(getClass()).error("Could not create directory", e);
return Optional.empty();
}
}
Path filePath = Paths.get(CoreHub.getWritableUserDir().getAbsolutePath(),
".localdoc" + File.separator, fileName);
if (Files.exists(filePath)) {
Result result = conflictHandler.getResult();
if (result == Result.ABORT) {
return Optional.empty();
} else if (result == Result.KEEP) {
return Optional.of(filePath.toFile());
} else if (result == Result.OVERWRITE) {
return Optional.ofNullable(writeFile(filePath, content, readOnly));
}
} else {
return Optional.ofNullable(writeFile(filePath, content, readOnly));
}
return Optional.empty();
}
private File writeFile(Path path, InputStream content, boolean readOnly){
try {
Files.deleteIfExists(path);
Path newFile = Files.createFile(path);
Files.copy(content, newFile, StandardCopyOption.REPLACE_EXISTING);
File ret = newFile.toFile();
ret.setWritable(!readOnly);
return ret;
} catch (IOException e) {
LoggerFactory.getLogger(getClass()).error("Error writing file", e);
}
return null;
}
/**
* User reflection to determine a meaningful name.
*
* @param documentSource
* @return
*/
private String getFileName(Object documentSource){
StringBuilder sb = new StringBuilder("_");
try {
Method nameMethod = null;
Method[] methods = documentSource.getClass().getMethods();
for (Method method : methods) {
if (isGetNameMethod(method)) {
nameMethod = method;
break;
}
}
if (nameMethod != null) {
sb.append(nameMethod.invoke(documentSource, new Object[0]));
} else {
sb.append(getDefaultFileName());
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// default
sb.append(getDefaultFileName());
}
try {
Method idMethod = null;
Method[] methods = documentSource.getClass().getMethods();
for (Method method : methods) {
if (isGetIdMethod(method)) {
idMethod = method;
break;
}
}
if (idMethod != null) {
sb.append("[" + idMethod.invoke(documentSource, new Object[0]) + "]");
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// ignore
}
sb.append("_");
try {
Method mimeMethod = null;
Method[] methods = documentSource.getClass().getMethods();
for (Method method : methods) {
if (method.getName().toLowerCase().contains("mime")) {
mimeMethod = method;
break;
}
}
if (mimeMethod != null) {
sb.append(
"." + getFileEnding((String) mimeMethod.invoke(documentSource, new Object[0])));
} else {
sb.append(getDefaultFileEnding());
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// default
sb.append(getDefaultFileEnding());
}
return sb.toString();
}
private boolean isGetIdMethod(Method method){
if (method.getParameterTypes().length > 0) {
return false;
}
String lowerName = method.getName().toLowerCase();
if (lowerName.equals("getid")) {
return true;
}
return false;
}
private boolean isGetNameMethod(Method method){
if (method.getParameterTypes().length > 0) {
return false;
}
String lowerName = method.getName().toLowerCase();
if (lowerName.contains("betreff") || lowerName.contains("titel")
|| lowerName.contains("title")) {
return true;
}
return false;
}
private String getFileEnding(String mime){
if (mime.length() < 5) {
return mime;
} else {
String ret = MimeTool.getExtension(mime);
if (ret.length() > 5) {
return getDefaultFileEnding();
} else {
return ret;
}
}
}
private String getDefaultFileEnding(){
return ".tmp";
}
private Object getDefaultFileName(){
return "localFile" + System.currentTimeMillis();
}
@Override
public List<Object> getAll(){
return new ArrayList<>(managedFiles.keySet());
}
@Override
public void registerSaveHandler(Class<?> clazz, ISaveHandler saveHandler){
registeredSaveHandler.put(clazz, saveHandler);
}
@Override
public void registerLoadHandler(Class<?> clazz, ILoadHandler saveHandler){
registeredLoadHandler.put(clazz, saveHandler);
}
@Override
public boolean save(Object documentSource) throws IllegalStateException{
ISaveHandler saveHandler = registeredSaveHandler.get(documentSource.getClass());
if(saveHandler != null) {
return saveHandler.save(documentSource, this);
}
throw new IllegalStateException("No save handler for [" + documentSource + "]");
}
}