/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.component.integration.internal;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import de.rcenvironment.core.communication.api.CommunicationService;
import de.rcenvironment.core.communication.common.NodeIdentifierUtils;
import de.rcenvironment.core.component.api.DistributedComponentKnowledgeService;
import de.rcenvironment.core.component.integration.RemoteToolIntegrationService;
import de.rcenvironment.core.component.integration.ToolIntegrationConstants;
import de.rcenvironment.core.component.integration.ToolIntegrationDocumentationService;
import de.rcenvironment.core.component.model.api.ComponentInstallation;
import de.rcenvironment.core.configuration.ConfigurationService;
import de.rcenvironment.core.configuration.ConfigurationService.ConfigurablePathId;
import de.rcenvironment.core.utils.common.CompressingHelper;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.rpc.RemoteOperationException;
/**
* Implementation of {@link ToolIntegrationDocumentationService}.
*
* @author Sascha Zur
*/
public class ToolIntegrationDocumentationServiceImpl implements ToolIntegrationDocumentationService {
protected static DistributedComponentKnowledgeService componentKnowledgeService;
protected static CommunicationService communicationService;
private static final String METADATA_FILE_NAME = ".metadata";
private static final String KEY_HASH = "hash";
private static final String KEY_LAST_USED = "lastUsed";
private static final String KEY_DOCUMENTATION_DIR_NAME = "documentationDir";
private static final String CACHE_NAME = "toolDocCache";
private static final Log LOGGER = LogFactory.getLog(ToolIntegrationDocumentationServiceImpl.class);
private Map<String, Map<String, Map<String, String>>> toolDocumentationCache;
private ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
private ConfigurationService configService;
private final long time90Days = 7776000000L;
@Override
public Map<String, String> getComponentDocumentationList(String identifier) {
Set<ComponentInstallation> componentInstallations = new HashSet<>();
componentInstallations.addAll(componentKnowledgeService.getCurrentComponentKnowledge().getAllInstallations());
Map<String, String> docs = new HashMap<>();
for (ComponentInstallation ci : componentInstallations) {
if (ci.getInstallationId().equals(identifier)
&& !ci.getComponentRevision().getComponentInterface().getDocumentationHash().isEmpty()) {
docs.put(ci.getComponentRevision().getComponentInterface().getDocumentationHash(), ci.getNodeId());
}
}
loadDocumentationCache();
if (toolDocumentationCache.get(identifier) != null) {
for (String nodeIdentifier : toolDocumentationCache.get(identifier).keySet()) {
String hash = toolDocumentationCache.get(identifier).get(nodeIdentifier).get(KEY_HASH);
if (docs.get(hash) != null && !docs.get(hash).isEmpty()) {
docs.put(hash, nodeIdentifier + ToolIntegrationConstants.DOCUMENTATION_CACHED_SUFFIX);
}
}
}
return docs;
}
private File loadDocumentationCache() {
File cacheDir = new File(configService.getConfigurablePath(ConfigurablePathId.PROFILE_INTERNAL_DATA), CACHE_NAME);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
if (toolDocumentationCache == null) {
try {
toolDocumentationCache = new TreeMap<>();
readToolDocumentationCache();
} catch (IOException e) {
LOGGER.error("Could not read documentation cache: ", e);
toolDocumentationCache = new TreeMap<>();
}
}
return cacheDir;
}
@Override
public File getToolDocumentation(String identifier, String nodeId, String hashValue)
throws RemoteOperationException, FileNotFoundException, IOException {
byte[] documentation = null;
File cacheDir = loadDocumentationCache();
if (nodeId.endsWith(ToolIntegrationConstants.DOCUMENTATION_CACHED_SUFFIX)) {
nodeId = nodeId.substring(0, nodeId.length() - 3);
}
if (toolDocumentationCache.get(identifier) != null
&& toolDocumentationCache.get(identifier).get(nodeId) != null
&& toolDocumentationCache.get(identifier).get(nodeId).get(KEY_HASH).equals(hashValue)) {
// documentation in cache
File docuDir = new File(cacheDir, toolDocumentationCache.get(identifier).get(nodeId).get(KEY_DOCUMENTATION_DIR_NAME));
toolDocumentationCache.get(identifier).get(nodeId).put(KEY_LAST_USED, String.valueOf(System.currentTimeMillis()));
return docuDir;
} else {
// documentation not in cache, retrieve
// TODO improve method by passing the id object into it (instead of a node id string)
RemoteToolIntegrationService rtis =
communicationService.getRemotableService(RemoteToolIntegrationService.class,
NodeIdentifierUtils.parseLogicalNodeIdStringWithExceptionWrapping(nodeId));
documentation = rtis.getToolDocumentation(identifier);
if (documentation != null) {
File tempDir = null;
tempDir = findFirstUnusedDirectory(cacheDir);
tempDir.mkdirs();
CompressingHelper.decompressFolderByteArray(documentation, tempDir);
Map<String, Map<String, String>> nodeIDMap = new HashMap<>();
Map<String, String> values = new HashMap<>();
values.put(KEY_DOCUMENTATION_DIR_NAME, tempDir.getName());
values.put(KEY_HASH, hashValue);
values.put(KEY_LAST_USED, String.valueOf(System.currentTimeMillis()));
nodeIDMap.put(nodeId, values);
toolDocumentationCache.put(identifier, nodeIDMap);
mapper.writerWithDefaultPrettyPrinter().writeValue(new File(cacheDir, METADATA_FILE_NAME), toolDocumentationCache);
return tempDir;
}
}
return null;
}
private File findFirstUnusedDirectory(File cacheDir) {
long i = 0;
File tmpFile = new File(cacheDir, String.valueOf(i));
while (tmpFile.exists() && tmpFile.isDirectory()) {
i++;
tmpFile = new File(cacheDir, String.valueOf(i));
}
return tmpFile;
}
private void readToolDocumentationCache() throws JsonParseException, JsonMappingException, IOException {
File cacheDir = new File(configService.getConfigurablePath(ConfigurablePathId.PROFILE_INTERNAL_DATA), CACHE_NAME);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
File metadataFile = new File(cacheDir, METADATA_FILE_NAME);
if (metadataFile.exists()) {
mapper.readValue(metadataFile, toolDocumentationCache.getClass());
// clean up cache
Set<String> toolIdsToRemove = new HashSet<>();
for (String toolID : toolDocumentationCache.keySet()) {
Set<String> nodeIdsToRemove = new HashSet<>();
for (String nodeId : toolDocumentationCache.get(toolID).keySet()) {
checkDocuDirectory(cacheDir, toolID, nodeIdsToRemove, nodeId);
}
for (String id : nodeIdsToRemove) {
toolDocumentationCache.get(toolID).remove(id);
}
if (toolDocumentationCache.get(toolID).isEmpty()) {
toolIdsToRemove.add(toolID);
}
}
for (String id : toolIdsToRemove) {
toolDocumentationCache.remove(id);
}
}
mapper.writerWithDefaultPrettyPrinter().writeValue(metadataFile, toolDocumentationCache);
}
private void checkDocuDirectory(File cacheDir, String toolID, Set<String> nodeIdsToRemove, String nodeId) throws IOException {
if (toolDocumentationCache.get(toolID).get(nodeId).get(KEY_DOCUMENTATION_DIR_NAME) != null) {
File docDir = new File(cacheDir, toolDocumentationCache.get(toolID).get(nodeId).get(KEY_DOCUMENTATION_DIR_NAME));
if (!(docDir.exists() && docDir.isDirectory())) {
nodeIdsToRemove.add(nodeId);
}
if (toolDocumentationCache.get(toolID).get(nodeId).get(KEY_LAST_USED) != null) {
long lastUsed = Long.parseLong(toolDocumentationCache.get(toolID).get(nodeId).get(KEY_LAST_USED));
if (System.currentTimeMillis() - lastUsed > time90Days) {
nodeIdsToRemove.add(nodeId);
if (docDir.exists()) {
FileUtils.deleteDirectory(docDir);
}
}
}
}
}
/**
* Public for testing.
*
* @param incoming service.
*/
public void bindDistributedComponentKnowledgeService(DistributedComponentKnowledgeService incoming) {
componentKnowledgeService = incoming;
}
protected void unbindDistributedComponentKnowledgeService(DistributedComponentKnowledgeService incoming) {
componentKnowledgeService = null;
}
/**
* Public for testing.
*
* @param incoming service.
*/
public void bindCommunicationService(CommunicationService incoming) {
communicationService = incoming;
}
protected void unbindCommunicationService(CommunicationService incoming) {
communicationService = null;
}
/**
* Public for testing.
*
* @param incoming service.
*/
public void bindConfigurationService(ConfigurationService incoming) {
configService = incoming;
}
protected void unbindConfigurationService(ConfigurationService incoming) {
configService = null;
}
}