blob: 5f1ab6cced654055abbc394ab49efe50204986e9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2011 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* wesendon
******************************************************************************/
package org.eclipse.emf.emfstore.internal.server.startup;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.FeatureNotFoundException;
import org.eclipse.emf.emfstore.internal.common.model.ModelElementId;
import org.eclipse.emf.emfstore.internal.common.model.Project;
import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.internal.common.model.util.SerializationException;
import org.eclipse.emf.emfstore.internal.server.exceptions.FatalESException;
import org.eclipse.emf.emfstore.internal.server.model.ProjectHistory;
import org.eclipse.emf.emfstore.internal.server.model.ServerSpace;
import org.eclipse.emf.emfstore.internal.server.model.versioning.Version;
import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation;
import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.CompositeOperation;
import org.eclipse.emf.emfstore.server.ESCloseableIterable;
/**
* Validates the serverspace in three different ways. First it resolves all proxies, then checks whether all ME have ids
* and it is checked whether the changes generate the corret projectstate.
*
* @author wesendon
*/
public class EmfStoreValidator {
private static final String CHANGES_FEATURE_NAME = "changes"; //$NON-NLS-1$
private static final String PROJECTSTATE_FEATURE_NAME = "projectState"; //$NON-NLS-1$
private final ServerSpace serverSpace;
private List<String> excludedProjects;
/**
* Default constructor.
*
* @param serverSpace serverspace
*/
public EmfStoreValidator(ServerSpace serverSpace) {
this.serverSpace = serverSpace;
excludedProjects = new ArrayList<String>();
}
/**
* Option for resolving all proxies. The validation therefore uses
* {@link EcoreUtil#resolveAll(org.eclipse.emf.ecore.EObject)}
*/
public static final int RESOLVEALL = 1;
/**
* Option for checking all modelelement ids in projecstate and changes. Every modelelement and changeoperation has
* to have a modelelement id.
*/
public static final int MODELELEMENTID = 2;
/**
* Option for the change test. The initial projecstate is loaded and all changes are applied, until the next
* projecstate is reached. Then the calculated state and the saved state are comparesd.
*/
public static final int PROJECTGENERATION = 4;
private long timeMillis;
/**
* Runs the validation. You can configure the validation by a bitmask, therefore use the value: {@link #RESOLVEALL},
* {@link #MODELELEMENTID} and {@link #PROJECTGENERATION}.
*
* @param options options
* @param throwException allows you to prevent that an exception is thrown if validation failes
* @throws FatalESException in case of failure
*/
public void validate(int options, boolean throwException) throws FatalESException {
boolean errors = true;
if ((options & RESOLVEALL) == RESOLVEALL) {
errors = validateResolveAll() && errors;
}
if ((options & MODELELEMENTID) == MODELELEMENTID) {
errors = validateModelelementId() && errors;
}
// Not effieciently possible with branches
// if ((options & PROJECTGENERATION) == PROJECTGENERATION) {
// errors = validateProjectGeneration() && errors;
// }
if (!errors && throwException) {
throw new FatalESException(Messages.EmfStoreValidator_ValidationFailed);
}
}
/**
* Runs the validation. You can configure the validation by a bitmask, therefore use the value: {@link #RESOLVEALL},
* {@link #MODELELEMENTID} and {@link #PROJECTGENERATION}.
*
* @param options options
* @throws FatalESException in case of failure
*/
public void validate(int options) throws FatalESException {
validate(options, true);
}
/**
* {@link #RESOLVEALL}.
*/
private boolean validateResolveAll() {
start(Messages.EmfStoreValidator_ResolvingAllElements);
EcoreUtil.resolveAll(serverSpace.eResource().getResourceSet());
final EList<Resource.Diagnostic> errors = new BasicEList<Resource.Diagnostic>();
final EList<Resource> resources = serverSpace.eResource().getResourceSet().getResources();
for (final Resource currentResource : resources) {
errors.addAll(currentResource.getErrors());
}
removeAcceptedErrors(errors);
errors(errors);
stop();
return errors.size() == 0;
}
private void removeAcceptedErrors(EList<Diagnostic> errors) {
final Iterator<Diagnostic> iterator = errors.iterator();
while (iterator.hasNext()) {
final Resource.Diagnostic diagnostic = iterator.next();
// removes errors for projectState and changePackage references in versions.
if (diagnostic instanceof FeatureNotFoundException) {
final FeatureNotFoundException featureNotFoundException = (FeatureNotFoundException) diagnostic;
if (featureNotFoundException.getObject() instanceof Version) {
if (featureNotFoundException.getName().equals(PROJECTSTATE_FEATURE_NAME)
|| featureNotFoundException.getName().equals(CHANGES_FEATURE_NAME)) {
iterator.remove();
}
}
}
}
}
/**
* {@link #MODELELEMENTID}.
*/
private boolean validateModelelementId() {
start(Messages.EmfStoreValidator_CheckingModelElementIds);
final List<String> errors = new ArrayList<String>();
for (final ProjectHistory projectHistory : serverSpace.getProjects()) {
if (isExcluded(projectHistory)) {
continue;
}
System.out.println(
MessageFormat.format(Messages.EmfStoreValidator_CheckingProject,
projectHistory.getProjectId().getId()));
for (final Version version : projectHistory.getVersions()) {
if (version.getChanges() != null) {
final ESCloseableIterable<AbstractOperation> operations = version.getChanges().operations();
try {
for (final AbstractOperation abstractOperation : operations.iterable()) {
if (!(abstractOperation instanceof CompositeOperation)
&& (abstractOperation.getModelElementId() == null
|| abstractOperation.getModelElementId().getId() == null)) {
errors.add(
MessageFormat.format(
Messages.EmfStoreValidator_ChangeOperation_Has_No_ModelElementId,
projectHistory.getProjectId(),
version.getPrimarySpec().getIdentifier()));
}
}
} finally {
operations.close();
}
}
if (version.getProjectState() != null) {
for (final EObject me : version.getProjectState().getAllModelElements()) {
final ModelElementId modelElementId = ModelUtil.getProject(me).getModelElementId(me);
if (modelElementId == null || modelElementId.getId() == null) {
errors.add(
MessageFormat.format(
Messages.EmfStoreValidator_ModelElement_Has_No_ModelElementId,
projectHistory.getProjectId(),
version.getPrimarySpec().getIdentifier()));
}
}
}
}
}
errors(errors);
stop();
return errors.size() == 0;
}
/**
* Note: This validation has been deactivated since the introduction of branch support. With branches this can't be
* done efficiently anymore, we have to discuss alternatives.
*
* {@value #PROJECTGENERATION}.
*/
@SuppressWarnings("unused")
private boolean validateProjectGeneration() {
start(Messages.EmfStoreValidator_ProjectGenerationCompare);
final List<String> errors = new ArrayList<String>();
for (final ProjectHistory history : serverSpace.getProjects()) {
if (isExcluded(history)) {
continue;
}
System.out.println(
MessageFormat.format(
Messages.EmfStoreValidator_CheckingProject, history.getProjectId().getId()));
// history = (ProjectHistory) ModelUtil.clone(history);
Project state = null;
for (final Version version : history.getVersions()) {
if (version.getProjectState() != null && state == null) {
state = ModelUtil.clone(version.getProjectState());
} else {
version.getChanges().apply(state, true);
if (version.getProjectState() != null) {
final int[] compare = linearCompare(version.getProjectState(), state);
if (compare[0] == 0) {
errors.add(
MessageFormat.format(
Messages.EmfStoreValidator_ProjectVersionCompareFailed,
history.getProjectId().getId(),
version.getPrimarySpec().getIdentifier()));
// debug(history, state, version);
}
state = ModelUtil.clone(version.getProjectState());
}
}
}
}
errors(errors);
stop();
return errors.size() == 0;
}
/**
* Allows to exclude projects from validation aside from {@link #RESOLVEALL}.
*
* @param excludedProjects list of project id as string
*/
public void setExcludedProjects(List<String> excludedProjects) {
if (excludedProjects != null) {
this.excludedProjects = excludedProjects;
}
}
private boolean isExcluded(ProjectHistory projectHistory) {
return excludedProjects.contains(projectHistory.getProjectId().getId());
}
private void start(String str) {
timeMillis = System.currentTimeMillis();
System.out.println(Messages.EmfStoreValidator_Validation + str);
}
private void stop() {
System.out.println(
MessageFormat.format(
Messages.EmfStoreValidator_ValidationDuration, System.currentTimeMillis() - timeMillis));
}
private void errors(Collection<? extends Object> errorList) {
System.out.println(Messages.EmfStoreValidator_Errors + errorList.size());
for (final Object obj : errorList) {
System.out.println(obj);
}
}
private static int[] linearCompare(Project projectA, Project projectB) {
final int[] result = new int[5];
final int areEqual = 0;
final int differencePosition = 1;
final int character = 2;
final int lineNum = 3;
final int colNum = 4;
result[areEqual] = 1;
String stringA = StringUtils.EMPTY;
String stringB = StringUtils.EMPTY;
try {
stringA = ModelUtil.eObjectToString(projectA, ModelUtil.getChecksumSaveOptions());
stringB = ModelUtil.eObjectToString(projectB, ModelUtil.getChecksumSaveOptions());
} catch (final SerializationException e) {
ModelUtil.logException(e);
result[areEqual] = 0;
return result;
}
final int length = Math.min(stringA.length(), stringB.length());
for (int index = 0; index < length; index++) {
if (stringA.charAt(index) != stringB.charAt(index)) {
result[areEqual] = 0;
result[differencePosition] = index;
result[character] = stringA.charAt(index);
final int lineNumber = getLineNum(stringA, index);
result[lineNum] = lineNumber;
result[colNum] = getColNum(stringA, index);
break;
}
}
return result;
}
private static int getColNum(String stringA, int index) {
int pos = index;
int j = 0;
for (int i = 0; i < index; i++) {
j++;
if (stringA.charAt(i) == '\n') {
pos -= j;
j = 0;
}
}
return pos;
}
private static int getLineNum(String stringA, int index) {
int lineNum = 1;
for (int i = 0; i < index; i++) {
if (stringA.charAt(i) == '\n') {
lineNum++;
}
}
return lineNum;
}
}