/*
* Copyright 2015 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.robotframework.ide.eclipse.main.plugin.project.build.validation;
import static com.google.common.collect.Lists.newArrayList;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.rf.ide.core.executor.SuiteExecutor;
import org.rf.ide.core.project.RobotProjectConfig;
import org.rf.ide.core.project.RobotProjectConfig.ExcludedFolderPath;
import org.rf.ide.core.project.RobotProjectConfig.LibraryType;
import org.rf.ide.core.project.RobotProjectConfig.ReferencedLibrary;
import org.rf.ide.core.project.RobotProjectConfig.ReferencedVariableFile;
import org.rf.ide.core.project.RobotProjectConfig.RemoteLocation;
import org.rf.ide.core.project.RobotProjectConfig.SearchPath;
import org.rf.ide.core.project.RobotProjectConfigReader.CannotReadProjectConfigurationException;
import org.rf.ide.core.project.RobotProjectConfigReader.RobotProjectConfigWithLines;
import org.rf.ide.core.validation.ProblemPosition;
import org.robotframework.ide.eclipse.main.plugin.RedWorkspace;
import org.robotframework.ide.eclipse.main.plugin.model.RobotProject;
import org.robotframework.ide.eclipse.main.plugin.project.RedEclipseProjectConfig;
import org.robotframework.ide.eclipse.main.plugin.project.RedEclipseProjectConfig.PathResolvingException;
import org.robotframework.ide.eclipse.main.plugin.project.RedEclipseProjectConfigReader;
import org.robotframework.ide.eclipse.main.plugin.project.build.ProblemsReportingStrategy;
import org.robotframework.ide.eclipse.main.plugin.project.build.RobotArtifactsValidator.ModelUnitValidator;
import org.robotframework.ide.eclipse.main.plugin.project.build.RobotProblem;
import org.robotframework.ide.eclipse.main.plugin.project.build.causes.ConfigFileProblem;
import org.robotframework.ide.eclipse.main.plugin.project.editor.libraries.ILibraryClass;
import org.robotframework.ide.eclipse.main.plugin.project.editor.libraries.JarStructureBuilder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
public class RobotProjectConfigFileValidator implements ModelUnitValidator {
private final ValidationContext context;
private final IFile configFile;
private final ProblemsReportingStrategy reporter;
public RobotProjectConfigFileValidator(final ValidationContext context, final IFile configFile,
final ProblemsReportingStrategy reporter) {
this.context = context;
this.configFile = configFile;
this.reporter = reporter;
}
@Override
public void validate(final IProgressMonitor monitor) throws CoreException {
try {
final RobotProjectConfigWithLines config = new RedEclipseProjectConfigReader()
.readConfigurationWithLines(configFile);
validate(monitor, config);
} catch (final CannotReadProjectConfigurationException e) {
// this problem is handled by RobotLibraries builder
return;
}
}
@VisibleForTesting
void validate(final IProgressMonitor monitor, final RobotProjectConfigWithLines config) throws CoreException {
final RobotProjectConfig model = config.getConfigurationModel();
final Map<Object, ProblemPosition> linesMapping = config.getLinesMapping();
for (final RemoteLocation location : model.getRemoteLocations()) {
validateRemoteLocation(monitor, location, linesMapping);
}
int index = 0;
for (final ReferencedLibrary library : model.getLibraries()) {
validateReferencedLibrary(monitor, library, index, linesMapping);
index++;
}
for (final SearchPath path : model.getPythonPath()) {
validateSearchPath(monitor, path, model, linesMapping);
}
for (final SearchPath path : model.getClassPath()) {
validateSearchPath(monitor, path, model, linesMapping);
}
for (final ReferencedVariableFile variableFile : model.getReferencedVariableFiles()) {
validateReferencedVariableFile(monitor, variableFile, linesMapping);
}
for (final ExcludedFolderPath excludedPath : model.getExcludedPath()) {
validateExcludedPath(monitor, excludedPath, model.getExcludedPath(), linesMapping);
}
}
private void validateRemoteLocation(final IProgressMonitor monitor, final RemoteLocation location,
final Map<Object, ProblemPosition> linesMapping)
throws CoreException {
if (monitor.isCanceled()) {
return;
}
final URI uriAddress = location.getUriAddress();
final Socket s = new Socket();
try {
final SocketAddress sockaddr = new InetSocketAddress(uriAddress.getHost(), uriAddress.getPort());
s.connect(sockaddr, 5000);
} catch (final IOException | IllegalArgumentException ex) {
final RobotProblem unreachableHostProblem = RobotProblem.causedBy(ConfigFileProblem.UNREACHABLE_HOST)
.formatMessageWith(uriAddress);
reporter.handleProblem(unreachableHostProblem, configFile, linesMapping.get(location));
} finally {
try {
s.close();
} catch (final IOException e) {
// fine
}
}
}
private void validateReferencedLibrary(final IProgressMonitor monitor, final ReferencedLibrary library,
final int index, final Map<Object, ProblemPosition> linesMapping) {
if (monitor.isCanceled()) {
return;
}
final LibraryType libType = library.provideType();
final IPath libraryPath = Path.fromPortableString(library.getPath());
final ProblemPosition position = linesMapping.get(library);
final Map<String, Object> additional = ImmutableMap.<String, Object> of(ConfigFileProblem.LIBRARY_INDEX, index);
List<RobotProblem> libProblems;
switch (libType) {
case JAVA:
libProblems = findJavaLibraryProblem(libraryPath, library.getName());
break;
case PYTHON:
libProblems = findPythonLibraryProblem(libraryPath, library.getName());
break;
case VIRTUAL:
libProblems = findVirtualLibraryProblem(libraryPath);
break;
default:
libProblems = newArrayList();
break;
}
for (final RobotProblem problem : libProblems) {
reporter.handleProblem(problem, configFile, position, additional);
}
}
private List<RobotProblem> findJavaLibraryProblem(final IPath libraryPath, final String libName) {
final List<RobotProblem> javaLibProblems = newArrayList();
if (!"jar".equals(libraryPath.getFileExtension())) {
javaLibProblems.add(
RobotProblem.causedBy(ConfigFileProblem.JAVA_LIB_NOT_A_JAR_FILE).formatMessageWith(libraryPath));
}
javaLibProblems.addAll(validatePath(libraryPath, ConfigFileProblem.MISSING_LIBRARY_FILE));
final IPath absolutePath = RedWorkspace.Paths.toAbsoluteFromWorkspaceRelativeIfPossible(libraryPath);
final RobotProject robotProject = context.getModel().createRobotProject(configFile.getProject());
boolean containsClass = false;
final Collection<ILibraryClass> classes = new JarStructureBuilder(robotProject.getRuntimeEnvironment(),
robotProject.getRobotProjectConfig(), robotProject.getProject())
.provideEntriesFromFile(absolutePath.toFile());
for (final ILibraryClass jarClass : classes) {
if (jarClass.getQualifiedName().equals(libName)) {
containsClass = true;
break;
}
}
if (!containsClass) {
javaLibProblems.add(RobotProblem.causedBy(ConfigFileProblem.JAVA_LIB_MISSING_CLASS)
.formatMessageWith(libraryPath, libName));
}
if (context.getExecutorInUse() != SuiteExecutor.Jython) {
javaLibProblems.add(RobotProblem.causedBy(ConfigFileProblem.JAVA_LIB_IN_NON_JAVA_ENV)
.formatMessageWith(libraryPath, context.getExecutorInUse()));
}
return javaLibProblems;
}
private List<RobotProblem> findPythonLibraryProblem(final IPath libraryPath, final String libName) {
final List<RobotProblem> pyLibProblems = newArrayList();
pyLibProblems.addAll(validatePath(libraryPath, ConfigFileProblem.MISSING_LIBRARY_FILE));
// TODO validate classes
return pyLibProblems;
}
private List<RobotProblem> findVirtualLibraryProblem(final IPath libraryPath) {
return newArrayList(validatePath(libraryPath, ConfigFileProblem.MISSING_LIBRARY_FILE));
}
private List<RobotProblem> validatePath(final IPath path, final ConfigFileProblem missingFileProblem) {
final List<RobotProblem> problems = newArrayList();
if (path.isAbsolute()) {
problems.add(RobotProblem.causedBy(ConfigFileProblem.ABSOLUTE_PATH).formatMessageWith(path));
if (!path.toFile().exists()) {
problems.add(RobotProblem.causedBy(missingFileProblem).formatMessageWith(path));
}
} else {
final IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if (resource == null || !resource.exists()) {
problems.add(RobotProblem.causedBy(missingFileProblem).formatMessageWith(path));
}
}
return problems;
}
private void validateSearchPath(final IProgressMonitor monitor, final SearchPath searchPath,
final RobotProjectConfig config, final Map<Object, ProblemPosition> linesMapping) {
if (monitor.isCanceled()) {
return;
}
try {
final File location = new RedEclipseProjectConfig(config).toAbsolutePath(searchPath,
configFile.getProject());
if (!location.exists()) {
final RobotProblem problem = RobotProblem.causedBy(ConfigFileProblem.MISSING_SEARCH_PATH)
.formatMessageWith(location.toString());
reporter.handleProblem(problem, configFile, linesMapping.get(searchPath));
}
} catch (final PathResolvingException e) {
final RobotProblem problem = RobotProblem.causedBy(ConfigFileProblem.INVALID_SEARCH_PATH)
.formatMessageWith(searchPath.getLocation());
reporter.handleProblem(problem, configFile, linesMapping.get(searchPath));
}
}
private void validateReferencedVariableFile(final IProgressMonitor monitor,
final ReferencedVariableFile variableFile,
final Map<Object, ProblemPosition> linesMapping) {
if (monitor.isCanceled()) {
return;
}
final IPath libraryPath = Path.fromPortableString(variableFile.getPath());
final List<RobotProblem> pathProblems = validatePath(libraryPath,
ConfigFileProblem.MISSING_VARIABLE_FILE);
for (final RobotProblem pathProblem : pathProblems) {
reporter.handleProblem(pathProblem, configFile, linesMapping.get(variableFile));
}
}
private void validateExcludedPath(final IProgressMonitor monitor, final ExcludedFolderPath excludedPath,
final List<ExcludedFolderPath> allExcluded, final Map<Object, ProblemPosition> linesMapping) {
if (monitor.isCanceled()) {
return;
}
final IProject project = configFile.getProject();
final IPath asExcludedPath = Path.fromPortableString(excludedPath.getPath());
final Path projectPath = new Path(project.getName());
if (!project.exists(asExcludedPath)) {
final RobotProblem problem = RobotProblem.causedBy(ConfigFileProblem.MISSING_EXCLUDED_FOLDER)
.formatMessageWith(projectPath.append(asExcludedPath));
reporter.handleProblem(problem, configFile, linesMapping.get(excludedPath));
}
for (final ExcludedFolderPath otherPath : allExcluded) {
if (otherPath != excludedPath) {
final IPath otherAsPath = Path.fromPortableString(otherPath.getPath());
if (otherAsPath.isPrefixOf(asExcludedPath)) {
final RobotProblem problem = RobotProblem.causedBy(ConfigFileProblem.USELESS_FOLDER_EXCLUSION)
.formatMessageWith(projectPath.append(asExcludedPath), projectPath.append(otherAsPath));
reporter.handleProblem(problem, configFile, linesMapping.get(excludedPath));
}
}
}
}
}