blob: 7550cab62ce36549ec485e8df9fc0048a980786f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-2014, Abel Hegedus, Istvan Rath and Daniel Varro
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-v20.html.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.viatra.modelobfuscator.application.common;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.BasicExtendedMetaData;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.viatra.modelobfuscator.emf.simple.AbstractModelObfuscator;
import org.eclipse.viatra.modelobfuscator.emf.simple.EMFModelObfuscatorBuilder;
import org.eclipse.viatra.modelobfuscator.xml.XMLModelObfuscator;
import org.eclipse.viatra.modelobfuscator.xml.XMLModelObfuscatorBuilder;
import org.eclipse.viatra.modelobfuscator.xml.XMLSchemaConfiguration;
import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
/**
* @author Abel Hegedus
*
*/
public class ModelObfuscatorHeadless {
/*
* Command line parameters
*/
private static String configParam = "-c";
private static String configLongParam = "--config";
private static String seedParam = "-s";
private static String saltParam = "--salt";
/*
* Property keys
*/
private static final String PROPERTY_PREFIX = "org.eclipse.viatra.modelobfuscator/";
private static final String OBFUSCATION_MODE_PROPERTY = PROPERTY_PREFIX + "mode";
private static final String XML_OBFUSCATION_MODE = "xml";
private static final String EMF_OBFUSCATION_MODE= "emf";
private static final String OBFUSCATION_INPUT_PROPERTY = PROPERTY_PREFIX + "input";
private static final String OBFUSCATION_OUTPUT_PROPERTY = PROPERTY_PREFIX + "output";
private static final String OBFUSCATION_ECORE_PROPERTY = PROPERTY_PREFIX + "ecore";
private static final String OBFUSCATION_TAGS_PROPERTY = PROPERTY_PREFIX + "tags";
private static final String OBFUSCATION_ATTRIBUTES_PROPERTY = PROPERTY_PREFIX + "attributes";
/**
* @param args
* @return
*/
public Integer performHeadlessObfuscation(String[] args) {
String config = null;
String seed = null;
String salt = null;
if (args == null || args.length == 0) {
reportError("Configuration parameter not set");
}
int i = 0;
while (i < args.length) {
if (args[i].equals(configLongParam)) {
config = args[i + 1];
i += 2;
} else if (args[i].equals(configParam)) {
config = args[i + 1];
i += 2;
} else if (args[i].equals(seedParam)) {
seed = args[i + 1];
i += 2;
} else if (args[i].equals(saltParam)) {
salt = args[i + 1];
i += 2;
} else {
i++;
}
}
System.out.println("Obfuscation called with:\n"
+ " Config : " + Strings.nullToEmpty(config) + "\n"
+ " Seed : " + Strings.nullToEmpty(seed) + "\n"
+ " Salt : " + Strings.nullToEmpty(salt));
if (config == null) {
reportError("Configuration parameter not set");
}
Properties configuration = null;
// load configuration file
try {
configuration = loadConfigurationPropertyFile(config);
} catch (FileNotFoundException e) {
reportError("Could not find configuration file");
} catch (IOException e) {
reportError("Could not read configuration file");
}
String mode = getPropertyValue(configuration, OBFUSCATION_MODE_PROPERTY);
if(mode.equals(EMF_OBFUSCATION_MODE)) {
performEMFObfuscation(seed, salt, configuration);
} else if(mode.equals(XML_OBFUSCATION_MODE)) {
performXMLObfuscation(seed, salt, configuration);
} else {
reportError("Unknown mode " + mode + " selected in configuration");
}
return IApplication.EXIT_OK;
}
/**
* @param seed
* @param salt
* @param configuration
*/
private void performEMFObfuscation(String seed, String salt, Properties configuration) {
// ensure output directory existence
File outputDirectory = checkOutputDirectory(configuration);
// check input file existence
Map<String, URI> inputs = processInput(configuration);
Map<String, Object> extensionToFactoryMap = Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap();
ResourceSetImpl ecoreRS = performEMFSetup(extensionToFactoryMap);
// load ecore models into registry
loadEcorePackagesIntoRegistry(configuration, EPackage.Registry.INSTANCE, ecoreRS);
// load inputs into resource set
ResourceSetImpl resourceSet = loadInputModels(inputs, extensionToFactoryMap);
// initialize obfuscator
EMFModelObfuscatorBuilder obfuscatorBuilder = EMFModelObfuscatorBuilder.create();
obfuscatorBuilder.setInput(resourceSet);
if(seed != null) {
obfuscatorBuilder.setSeed(new BigInteger(seed, 36));
}
System.out.println("Obfuscating using seed: " + obfuscatorBuilder.getSeed());
if(salt != null) {
obfuscatorBuilder.setSalt(salt);
System.out.println("Obfuscating using salt: " + obfuscatorBuilder.getSalt());
}
// perform obfuscation
performObfuscation(obfuscatorBuilder);
// save models to output directory
saveObfuscatedModels(outputDirectory, inputs, resourceSet);
}
/**
* @param extensionToFactoryMap
* @return
*/
private ResourceSetImpl performEMFSetup(Map<String, Object> extensionToFactoryMap) {
if(!extensionToFactoryMap.containsKey("ecore")) {
extensionToFactoryMap.put("ecore", new EcoreResourceFactoryImpl());
}
ResourceSetImpl ecoreRS = new ResourceSetImpl();
final ExtendedMetaData extendedMetaData = new BasicExtendedMetaData(EPackage.Registry.INSTANCE);
ecoreRS.getLoadOptions().put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData);
return ecoreRS;
}
/**
* @param configuration
* @return
*/
private Map<String, URI> processInput(Properties configuration) {
Map<String,URI> inputs = Maps.newHashMap();
List<String> resultList = processFileListProperty(configuration, OBFUSCATION_INPUT_PROPERTY);
for (String filePath : resultList) {
URI fileURI = URI.createFileURI(filePath);
inputs.put(filePath,fileURI);
}
return inputs;
}
/**
* @param inputs
* @param extensionToFactoryMap
* @return
*/
private ResourceSetImpl loadInputModels(Map<String, URI> inputs, Map<String, Object> extensionToFactoryMap) {
ResourceSetImpl resourceSet = new ResourceSetImpl();
for (Entry<String, URI> inputEntry : inputs.entrySet()) {
URI uri = inputEntry.getValue();
// XXX we only support XMI resources in this way
if(!extensionToFactoryMap.containsKey(uri.fileExtension())) {
extensionToFactoryMap.put(uri.fileExtension(), new EcoreResourceFactoryImpl());
}
System.out.println("Loading resource: " + inputEntry.getKey());
Stopwatch stopwatch = Stopwatch.createStarted();
resourceSet.getResource(uri, true);
stopwatch.stop();
String elapsedTime = stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms (" + stopwatch.elapsed(TimeUnit.NANOSECONDS) + " ns)";
System.out.println("Loaded resource: " + inputEntry.getKey() + " in " + elapsedTime);
}
return resourceSet;
}
/**
* @param configuration
* @param ePackageRegistryInstance
* @param ecoreRS
*/
private void loadEcorePackagesIntoRegistry(Properties configuration,
org.eclipse.emf.ecore.EPackage.Registry ePackageRegistryInstance, ResourceSetImpl ecoreRS) {
String ecore = getPropertyValue(configuration, OBFUSCATION_ECORE_PROPERTY);
StringTokenizer ecoreTokenizer = new StringTokenizer(ecore,";");
while (ecoreTokenizer.hasMoreTokens()) {
String ecorePath = (String) ecoreTokenizer.nextToken();
// create input stream for input files
Resource ecoreResource = ecoreRS.getResource(URI.createFileURI(ecorePath), true);
EObject root = ecoreResource.getContents().get(0);
if(root instanceof EPackage) {
EPackage ePackage = (EPackage) root;
ePackageRegistryInstance.put(ePackage.getNsURI(), ePackage);
System.out.println("Registered metamodel: " + ePackage.getName() + "(nsURI: " + ePackage.getNsURI() + ")");
}
}
}
/**
* @param obfuscatorBuilder
*/
private void performObfuscation(EMFModelObfuscatorBuilder obfuscatorBuilder) {
AbstractModelObfuscator obfuscator = obfuscatorBuilder.build();
System.out.println("Obfuscating EMF resource set");
Stopwatch stopwatch = Stopwatch.createStarted();
obfuscator.obfuscate();
stopwatch.stop();
String elapsedTime = stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms (" + stopwatch.elapsed(TimeUnit.NANOSECONDS) + " ns)";
System.out.println("Obfuscation finished in: " + elapsedTime);
}
/**
* @param outputDirectory
* @param inputs
* @param resourceSet
*/
private void saveObfuscatedModels(File outputDirectory, Map<String, URI> inputs, ResourceSetImpl resourceSet) {
URI outputDirUri = URI.createFileURI(outputDirectory.getPath());
for (Entry<String, URI> entry : inputs.entrySet()) {
URI uri = entry.getValue();
String fileSegment = uri.lastSegment();
URI outputUri = outputDirUri.appendSegment(fileSegment);
Resource resource = resourceSet.getResource(uri, false);
resource.setURI(outputUri);
try {
System.out.println("Saving resource: " + fileSegment);
Stopwatch stopwatch2 = Stopwatch.createStarted();
resource.save(null);
stopwatch2.stop();
String elapsedTime2 = stopwatch2.elapsed(TimeUnit.MILLISECONDS) + " ms (" + stopwatch2.elapsed(TimeUnit.NANOSECONDS) + " ns)";
System.out.println("Saved resource: " + fileSegment + " in " + elapsedTime2);
} catch (IOException e) {
reportError("Could not save output " + fileSegment);
}
}
}
private List<String> processFileListProperty(Properties configuration, String obfuscationInputProperty) {
List<String> resultList = Lists.newArrayList();
String input = getPropertyValue(configuration, obfuscationInputProperty);
StringTokenizer inputTokenizer = new StringTokenizer(input,";");
while (inputTokenizer.hasMoreTokens()) {
String inputPath = (String) inputTokenizer.nextToken();
// create input stream for input files
File file = new File(inputPath);
if(!file.exists()) {
reportError("Input " + file.getPath() + " specified in configuration could not be found");
}
if(file.isFile()) {
String fileName = file.getName();
if(resultList.contains(fileName)) {
reportError("Multiple files with the same name " + fileName + " in configuration");
}
resultList.add(inputPath);
} else if(file.isDirectory()) {
/*
* TODO we could support directories, either processing only directly contained files or all transitive
* files. However, we would need to take care in putting the directory structure into the output, handle
* symbolic and hard links, etc.
*/
reportError("Input" + file.getPath() + " specified in configuration is a directory");
}
}
return resultList;
}
/**
* @param seed
* @param salt
* @param configuration
*/
private void performXMLObfuscation(String seed, String salt, Properties configuration) {
// ensure output directory existence
File outputDirectory = checkOutputDirectory(configuration);
// check input file existence
Map<String,FileInputStream> inputs = Maps.newHashMap();
prepareInputStreams(configuration, inputs);
// parse schema configuration
XMLSchemaConfiguration schemaConfiguration = prepareSchemaConfiguration(configuration);
// initialize obfuscator
XMLModelObfuscatorBuilder obfuscatorBuilder = XMLModelObfuscatorBuilder.create().setSchemaConfiguration(schemaConfiguration);
if(seed != null) {
obfuscatorBuilder.setSeed(new BigInteger(seed, 36));
}
System.out.println("Obfuscating using seed: " + obfuscatorBuilder.getSeed());
if(salt != null) {
obfuscatorBuilder.setSalt(salt);
System.out.println("Obfuscating using salt: " + obfuscatorBuilder.getSalt());
}
performObfuscation(outputDirectory, inputs, obfuscatorBuilder);
}
private Properties loadConfigurationPropertyFile(String configPath) throws FileNotFoundException, IOException {
Properties bundle = new Properties();
FileInputStream fis = new FileInputStream(configPath);
try {
bundle.load(fis);
} finally {
fis.close();
}
return bundle;
}
/**
* @param configuration
*/
private File checkOutputDirectory(Properties configuration) {
String output = getPropertyValue(configuration, OBFUSCATION_OUTPUT_PROPERTY);
File outputFile = new File(output);
if(!outputFile.exists()) {
boolean directoryCreated = outputFile.mkdir();
if(!directoryCreated) {
reportError("Output " + output + " specified in configuration could not be created");
}
} else if(!outputFile.isDirectory()) {
reportError("Output " + output + " specified in configuration is not a directory");
}
return outputFile;
}
private void prepareInputStreams(Properties configuration, Map<String, FileInputStream> inputs) {
List<String> resultList = processFileListProperty(configuration, OBFUSCATION_INPUT_PROPERTY);
// create input stream for input files
for (String fileName : resultList) {
File file = new File(fileName);
try {
FileInputStream fileInputStream = new FileInputStream(file);
inputs.put(file.getName(),fileInputStream);
} catch (FileNotFoundException e) {
reportError("Input " + file.getPath() + " specified in configuration could not be found, although it exists");
}
}
}
/**
* @param configuration
* @return
*/
private XMLSchemaConfiguration prepareSchemaConfiguration(Properties configuration) {
Optional<String> tags = getOptionalPropertyValue(configuration, OBFUSCATION_TAGS_PROPERTY);
Optional<String> attributes = getOptionalPropertyValue(configuration, OBFUSCATION_ATTRIBUTES_PROPERTY);
if(!tags.isPresent() && !attributes.isPresent()) {
reportError("No schema configuration provided, nothing to obfuscate");
}
Set<String> tagSet = Sets.newHashSet();
if(tags.isPresent()) {
StringTokenizer tagTokenizer = new StringTokenizer(tags.get(), ";");
while (tagTokenizer.hasMoreTokens()) {
String tag = tagTokenizer.nextToken();
tagSet.add(tag);
}
}
Multimap<String, String> attributeMultimap = HashMultimap.create();
if(attributes.isPresent()) {
StringTokenizer attributeTokenizer = new StringTokenizer(attributes.get(), ";");
while (attributeTokenizer.hasMoreTokens()) {
String tagAttributes = attributeTokenizer.nextToken();
try {
StringTokenizer tagAttributeTokenizer = new StringTokenizer(tagAttributes, ":");
String tagName = tagAttributeTokenizer.nextToken();
String attributeList = tagAttributeTokenizer.nextToken();
StringTokenizer attributeListTokenizer = new StringTokenizer(attributeList, ",");
while (attributeListTokenizer.hasMoreTokens()) {
String attributeName = attributeListTokenizer.nextToken();
attributeMultimap.put(tagName, attributeName);
}
} catch (NoSuchElementException e) {
reportError("Incorrect syntax in attributes value of schema configuration when processing: " + tagAttributes);
}
}
}
XMLSchemaConfiguration schemaConfiguration = new XMLSchemaConfiguration(attributeMultimap, tagSet);
return schemaConfiguration;
}
/**
* @param outputDirectory
* @param inputs
* @param obfuscatorBuilder
*/
private void performObfuscation(File outputDirectory, Map<String, FileInputStream> inputs,
XMLModelObfuscatorBuilder obfuscatorBuilder) {
for (Entry<String, FileInputStream> input : inputs.entrySet()) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(input.getValue());
obfuscatorBuilder.setInput(bufferedInputStream);
String fileName = input.getKey();
File output = new File(outputDirectory, fileName);
BufferedOutputStream bufferedOutputStream;
try {
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(output));
obfuscatorBuilder.setOutput(bufferedOutputStream);
XMLModelObfuscator obfuscator = obfuscatorBuilder.build();
System.out.println("Obfuscating " + fileName);
Stopwatch stopwatch = Stopwatch.createStarted();
obfuscator.obfuscate();
stopwatch.stop();
System.out.println("Obfuscation finished in: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms (" + stopwatch.elapsed(TimeUnit.NANOSECONDS) + " ns)");
bufferedOutputStream.close();
bufferedInputStream.close();
} catch (FileNotFoundException e) {
reportError("Could not ouput to file " + output.getPath());
} catch (IOException e) {
reportError("Could not close output file " + output.getPath());
}
}
}
/**
* @param configuration
* @return
*/
private String getPropertyValue(Properties configuration, String propertyName) {
Optional<String> optionalPropertyValue = getOptionalPropertyValue(configuration, propertyName);
if(!optionalPropertyValue.isPresent()) {
reportError(propertyName + " undefined in configuration");
}
return optionalPropertyValue.get();
}
/**
* @param configuration
* @return
*/
private Optional<String> getOptionalPropertyValue(Properties configuration, String propertyName) {
String propertyValue = configuration.getProperty(propertyName);
return Optional.fromNullable(propertyValue);
}
/**
* @return
*/
private Integer reportError(String message) {
System.out.println(message);
displayHelp();
throw new IllegalArgumentException(message);
}
private void displayHelp() {
System.out.println("Usage:\n"
+ "<call> -c <configurationFilePath> [-s <seed>] [-salt <salt>]\n"
+ " -c : Required, the configuration that describes what to obfuscate.\n"
+ " --config : Same as -c.\n"
+ " -s : Optional, the seed used for the obfuscation. Must be base 36 number as string\n"
+ " --seed : Same as -s.\n"
+ " --salt : Optional, the salt used for the obfuscation.");
}
}