/**
* Copyright 2014 SAP AG
*
* 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 org.spotter.eclipse.ui.navigator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.swt.widgets.Display;
import org.lpe.common.util.LpeFileUtils;
import org.lpe.common.util.LpeStreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.eclipse.ui.Activator;
import org.spotter.eclipse.ui.ServiceClientWrapper;
import org.spotter.eclipse.ui.jobs.JobsContainer;
import org.spotter.eclipse.ui.util.DialogUtils;
import org.spotter.shared.configuration.FileManager;
/**
* An element that represents the results node.
*
* @author Denis Knoepfle
*
*/
public class SpotterProjectResults extends AbstractProjectElement {
public static final String IMAGE_PATH = "icons/results.gif"; //$NON-NLS-1$
private static final Logger LOGGER = LoggerFactory.getLogger(SpotterProjectResults.class);
private static final String ELEMENT_NAME = "Results";
private static final String EMPTY_SUFFIX = " (empty)";
private static final String PENDING_SUFFIX = " (pending...)";
private ISpotterProjectElement parent;
private boolean initialLoad;
private final Map<Long, SpotterProjectRunResult> jobIdToResultMapping;
/**
* Creates a new instance of this element.
*
* @param parent
* the parent element
*/
public SpotterProjectResults(ISpotterProjectElement parent) {
super(IMAGE_PATH);
this.parent = parent;
this.initialLoad = false;
this.jobIdToResultMapping = new HashMap<>();
}
@Override
public String getText() {
String suffix;
if (children == null) {
initialDeferredLoad();
suffix = PENDING_SUFFIX;
} else {
suffix = hasChildren() ? "" : EMPTY_SUFFIX;
}
return ELEMENT_NAME + suffix;
}
@Override
public ISpotterProjectElement[] getChildren() {
if (children == null) {
initialDeferredLoad();
return SpotterProjectParent.NO_CHILDREN;
}
// else the children are just fine
return children;
}
@Override
public boolean hasChildren() {
if (children == null) {
initialDeferredLoad();
return false;
}
return children.length > 0;
}
@Override
public Object getParent() {
return parent;
}
@Override
public IProject getProject() {
return parent.getProject();
}
/**
* Returns the corresponding run result item for the given job id.
*
* @param jobId
* the job id the requested item refers to
* @return the item corresponding to the given job id
*/
public SpotterProjectRunResult getRunResultForJobId(long jobId) {
return jobIdToResultMapping.get(jobId);
}
private synchronized void initialDeferredLoad() {
if (initialLoad) {
return;
}
initialLoad = true;
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
refreshChildren();
Activator.getDefault().getNavigatorViewer().refresh(SpotterProjectResults.this);
}
});
}
@Override
protected ISpotterProjectElement[] initializeChildren(IProject iProject) {
IFolder resDir = iProject.getFolder(FileManager.DEFAULT_RESULTS_DIR_NAME);
if (!resDir.isSynchronized(IResource.DEPTH_INFINITE)) {
try {
resDir.refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (CoreException e) {
String message = "Failed to refresh results directory. Cannot proceed to create child nodes!";
DialogUtils.handleError(message, e);
return SpotterProjectParent.NO_CHILDREN;
}
}
return synchronizeRunResults(iProject, resDir.getLocation().toString());
}
private ISpotterProjectElement[] synchronizeRunResults(IProject iProject, String resultsLocation) {
jobIdToResultMapping.clear();
File res = new File(resultsLocation);
List<ISpotterProjectElement> elements = new ArrayList<>();
ServiceClientWrapper client = Activator.getDefault().getClient(iProject.getName());
if (!res.isDirectory()) {
DialogUtils.openWarning("The project's '" + FileManager.DEFAULT_RESULTS_DIR_NAME
+ "' folder is missing or corrupted!");
} else {
boolean connected = client.testConnection(false);
JobsContainer jobsContainer = JobsContainer.readJobsContainer(iProject);
Long[] jobIds = jobsContainer.getJobIds();
if (!connected && jobIds.length > 0) {
DialogUtils
.openAsyncWarning("No connection to DS service! New results cannot be fetched from the server.");
}
for (Long jobId : jobIds) {
SpotterProjectRunResult runResult = processJobId(jobId, connected, client, jobsContainer,
resultsLocation, iProject);
if (runResult != null) {
elements.add(runResult);
jobIdToResultMapping.put(jobId, runResult);
}
}
}
return elements.toArray(new ISpotterProjectElement[elements.size()]);
}
private SpotterProjectRunResult processJobId(Long jobId, boolean connected, ServiceClientWrapper client,
JobsContainer jobsContainer, String resultsLocation, IProject project) {
if (connected && client.isRunning(true) && jobId.equals(client.getCurrentJobId())) {
// Ignore this job because it is currently running
return null;
}
Long timestamp = jobsContainer.getTimestamp(jobId);
SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd_HH-mm-ss-SSS");
String formattedTimestamp = dateFormat.format(new Date(timestamp));
String runFolderName = resultsLocation + "/" + formattedTimestamp;
File runFolder = new File(runFolderName);
// if the folder already exists, assume data has already been fetched
boolean success = runFolder.exists();
if (!success && connected) {
// as the data is missing try to fetch it again from the server
InputStream resultsZipStream = fetchResultsZipStream(client, project, jobId);
if (resultsZipStream != null) {
String zipFileName = runFolderName + "/" + jobId + ".tmp";
success = unpackFromInputStream(jobId.toString(), runFolder, zipFileName, resultsZipStream);
}
}
SpotterProjectRunResult runResult = null;
if (success) {
String projectRelativePath = FileManager.DEFAULT_RESULTS_DIR_NAME + "/" + formattedTimestamp;
IFolder folder = project.getFolder(projectRelativePath);
try {
if (!folder.isSynchronized(IResource.DEPTH_INFINITE)) {
folder.refreshLocal(IResource.DEPTH_INFINITE, null);
}
if (folder.members().length != 0) {
runResult = new SpotterProjectRunResult(this, jobId, timestamp, folder);
} else {
// there are no files after successful fetch, so remove job
// id completely
JobsContainer.removeJobId(project, jobId);
}
} catch (CoreException e1) {
LOGGER.warn("An error occured while looking up folder members", e1);
try {
// in this case delete the folder so it can be fetched
// normally again next time
folder.delete(true, null);
} catch (CoreException e2) {
LOGGER.warn("An error occured while deleting folder", e2);
}
}
}
return runResult;
}
/*
* Unpacks the data from the input stream to the given run folder.
*/
private boolean unpackFromInputStream(String jobId, File runFolder, String zipFileName, InputStream resultsZipStream) {
try {
runFolder.mkdir();
FileOutputStream fos = new FileOutputStream(zipFileName);
LpeStreamUtils.pipe(resultsZipStream, fos);
resultsZipStream.close();
File zipFile = new File(zipFileName);
LpeFileUtils.unzip(zipFile, runFolder);
zipFile.delete();
return true;
} catch (Exception e) {
String message = "Error while saving fetched results for job " + jobId + "!";
DialogUtils.handleError(message, e);
LOGGER.error(message, e);
}
return false;
}
/*
* Tries to fetch the input stream from the server, but returns null and
* removes the job if it's empty.
*/
private InputStream fetchResultsZipStream(ServiceClientWrapper client, IProject project, Long jobId) {
InputStream resultsZipStream = client.requestResults(jobId.toString());
boolean isEmptyStream = true;
try {
if (resultsZipStream != null && resultsZipStream.available() > 0) {
isEmptyStream = false;
} else {
String msg = "Received empty input stream for job " + jobId + ", removing job!";
JobsContainer.removeJobId(project, jobId);
DialogUtils.openWarning(msg);
}
} catch (IOException e) {
resultsZipStream = null;
String message = "An error occured while reading from input stream for job " + jobId + ", skipping job!";
DialogUtils.handleError(message, e);
LOGGER.error(message, e);
}
return isEmptyStream ? null : resultsZipStream;
}
}