blob: 8790df052a2b0b782b5e6415db548fb9e2618279 [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* 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.onosproject.yang.compiler.tool;
import org.apache.commons.io.IOUtils;
import org.onosproject.yang.compiler.api.YangCompilationParam;
import org.onosproject.yang.compiler.api.YangCompiledOutput;
import org.onosproject.yang.compiler.api.YangCompilerException;
import org.onosproject.yang.compiler.api.YangCompilerService;
import org.onosproject.yang.compiler.datamodel.YangDeviationHolder;
import org.onosproject.yang.compiler.datamodel.YangNode;
import org.onosproject.yang.compiler.datamodel.YangReferenceResolver;
import org.onosproject.yang.compiler.datamodel.exceptions.DataModelException;
import org.onosproject.yang.compiler.linker.YangLinker;
import org.onosproject.yang.compiler.linker.exceptions.LinkerException;
import org.onosproject.yang.compiler.linker.impl.YangLinkerManager;
import org.onosproject.yang.compiler.parser.YangUtilsParser;
import org.onosproject.yang.compiler.parser.exceptions.ParserException;
import org.onosproject.yang.compiler.parser.impl.YangUtilsParserManager;
import org.onosproject.yang.compiler.utils.io.YangPluginConfig;
import org.onosproject.yang.model.DefaultYangModel;
import org.onosproject.yang.model.DefaultYangModuleId;
import org.onosproject.yang.model.YangModel;
import org.onosproject.yang.model.YangModule;
import org.onosproject.yang.model.YangModuleId;
import org.slf4j.Logger;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import static java.nio.file.Files.copy;
import static java.nio.file.Paths.get;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Collections.sort;
import static org.onosproject.yang.compiler.datamodel.ResolvableType.YANG_DERIVED_DATA_TYPE;
import static org.onosproject.yang.compiler.datamodel.ResolvableType.YANG_IDENTITYREF;
import static org.onosproject.yang.compiler.datamodel.utils.DataModelUtils.getDateInStringFormat;
import static org.onosproject.yang.compiler.linker.impl.YangLinkerUtils.resolveGroupingInDefinationScope;
import static org.onosproject.yang.compiler.translator.tojava.JavaCodeGeneratorUtil.generateJavaCode;
import static org.onosproject.yang.compiler.translator.tojava.JavaCodeGeneratorUtil.translatorErrorHandler;
import static org.onosproject.yang.compiler.utils.UtilConstants.NEW_LINE;
import static org.onosproject.yang.compiler.utils.UtilConstants.YANG_META_DATA;
import static org.onosproject.yang.compiler.utils.UtilConstants.YANG_RESOURCES;
import static org.onosproject.yang.compiler.utils.io.impl.YangFileScanner.getJavaFiles;
import static org.onosproject.yang.compiler.utils.io.impl.YangIoUtils.createDirectories;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Represents implementation of YANG compiler manager.
*/
public class YangCompilerManager implements YangCompilerService {
private static final Logger log = getLogger(YangCompilerManager.class);
private static final String SLASH = File.separator;
private final YangUtilsParser yangUtilsParser = new YangUtilsParserManager();
private final YangLinker yangLinker = new YangLinkerManager();
private final Set<YangNode> yangNodeSet = new HashSet<>();
// YANG file information set.
private Set<YangFileInfo> yangFileInfoSet; //initialize in tool invocation;
private YangFileInfo curYangFileInfo = new YangFileInfo();
private Set<Path> genJavaPath = new LinkedHashSet<>();
private YangModel model;
@Override
public YangCompiledOutput compileYangFiles(YangCompilationParam param)
throws IOException, YangCompilerException {
synchronized (YangCompilerManager.class) {
processYangFiles(param);
return new DefaultYangCompiledOutput(model, genJavaPath);
}
}
/**
* Returns YANG node set.
*
* @return YANG node set
*/
public Set<YangNode> getYangNodeSet() {
return yangNodeSet;
}
/**
* Creates a YANG file info set.
*
* @param yangFileList YANG files list
* @return yang file info set
*/
public Set<YangFileInfo> createYangFileInfoSet(Set<Path> yangFileList) {
if (yangFileInfoSet == null) {
yangFileInfoSet = new HashSet<>();
}
for (Path yangFile : yangFileList) {
YangFileInfo yangFileInfo = new YangFileInfo();
yangFileInfo.setYangFileName(yangFile.toString());
yangFileInfoSet.add(yangFileInfo);
}
return yangFileInfoSet;
}
/**
* Compile te YANG files and generate the corresponding Java files.
* Update the generated bundle with the schema metadata.
*
* @param param YANG compilation parameters
* @throws IOException when fails to do IO operations
*/
private void processYangFiles(YangCompilationParam param) throws IOException {
YangPluginConfig config = new YangPluginConfig();
synchronized (YangCompilerManager.class) {
try {
String codeGenDir = param.getCodeGenDir() + SLASH;
String resourceGenDir = param.getMetadataGenDir() + SLASH;
config.setCodeGenDir(codeGenDir);
config.resourceGenDir(resourceGenDir);
yangFileInfoSet = createYangFileInfoSet(param.getYangFiles());
// Check if there are any file to translate, if not return.
if (yangFileInfoSet.isEmpty()) {
// No files to translate
return;
}
//Create resource directory.
createDirectories(resourceGenDir);
// Resolve inter jar dependency.
addSchemaToFileSet(dependentSchema(param.getDependentSchemas()));
// Carry out the parsing for all the YANG files.
parseYangFileInfoSet();
createYangNodeSet();
// Serialize data model.
processSerialization(resourceGenDir, param.getModelId());
// Resolve dependencies using linker.
try {
resolveDependenciesUsingLinker();
} catch (Exception e) {
log.error("DependentSchemas: {}",
dependentSchema(param.getDependentSchemas())
.stream()
.map(YangNode::getName)
.collect(Collectors.toList()), e);
throw e;
}
// Perform translation to JAVA.
translateToJava(config);
//add to generated java code map
processGeneratedCode(codeGenDir);
//add YANG files to JAR
processCopyYangFile(resourceGenDir);
} catch (IOException | ParserException e) {
//TODO: provide unified framework for exceptions
YangCompilerException exception =
new YangCompilerException(e.getMessage(), e);
exception.setYangFile(get(
curYangFileInfo.getYangFileName()));
if (curYangFileInfo != null &&
curYangFileInfo.getRootNode() != null) {
try {
translatorErrorHandler(curYangFileInfo.getRootNode(),
config);
} catch (IOException ex) {
e.printStackTrace();
throw ex;
}
}
throw exception;
}
}
}
/**
* Adds all generated java class paths to YANG model.
*
* @param codeGenDir code gen directory.
* @throws IOException when fails to do IO operations
*/
private void processGeneratedCode(String codeGenDir) throws IOException {
List<String> files = getJavaFiles(codeGenDir);
for (String file : files) {
genJavaPath.add(Paths.get(file));
}
}
/**
* Returns dependent schema nodes.
*
* @param dependentSchemaPath dependent schema paths
* @return dependent schema nodes
*/
private Set<YangNode> dependentSchema(Set<Path> dependentSchemaPath) {
Set<YangNode> depNodes = new LinkedHashSet<>();
for (Path path : dependentSchemaPath) {
log.trace("from file:{}", path.getParent());
try {
Set<YangNode> yangNodes = getYangNodes(deSerializeDataModel(path.toString()));
if (log.isTraceEnabled()) {
log.trace(" got: {}", yangNodes.stream()
.map(YangNode::getName)
.collect(Collectors.toList()));
}
depNodes.addAll(yangNodes);
} catch (IOException e) {
throw new YangCompilerException(
"Failed to fetch dependent schema from given " +
"path :" + path.toString(), e);
}
}
return depNodes;
}
/**
* Resolved inter-jar dependencies.
*
* @param dependentSchema dependent schema list
*/
private void addSchemaToFileSet(Set<YangNode> dependentSchema) {
if (dependentSchema == null || dependentSchema.isEmpty()) {
return;
}
for (YangNode node : dependentSchema) {
YangFileInfo dependentFileInfo = new YangFileInfo();
node.setToTranslate(false);
dependentFileInfo.setRootNode(node);
dependentFileInfo.setForTranslator(false);
dependentFileInfo.setYangFileName(node.getName());
dependentFileInfo.setInterJar(true);
yangFileInfoSet.add(dependentFileInfo);
}
}
/**
* Links all the provided schema in the YANG file info set.
*
* @throws YangCompilerException failed to link schema
*/
public void resolveDependenciesUsingLinker() {
try {
yangLinker.resolveDependencies(yangNodeSet);
} catch (LinkerException e) {
printLog(e.getFileName(), e.getLineNumber(), e.getCharPositionInLine(),
e.getMessage(), e.getLocalizedMessage());
log.error("Linking failed", e);
throw new YangCompilerException(e.getMessage(), e);
}
}
/**
* Creates YANG nodes set.
*/
public void createYangNodeSet() {
for (YangFileInfo yangFileInfo : yangFileInfoSet) {
yangNodeSet.add(yangFileInfo.getRootNode());
}
}
/**
* Parses all the provided YANG files and generates YANG data model tree.
*
* @throws IOException a violation in IO
*/
public void parseYangFileInfoSet()
throws IOException {
for (YangFileInfo yangFileInfo : yangFileInfoSet) {
curYangFileInfo = yangFileInfo;
if (yangFileInfo.isForTranslator()) {
try {
YangNode yangNode = yangUtilsParser.getDataModel(
yangFileInfo.getYangFileName());
yangFileInfo.setRootNode(yangNode);
resolveGroupingInDefinationScope((YangReferenceResolver) yangNode);
try {
((YangReferenceResolver) yangNode)
.resolveSelfFileLinking(YANG_DERIVED_DATA_TYPE);
((YangReferenceResolver) yangNode)
.resolveSelfFileLinking(YANG_IDENTITYREF);
} catch (DataModelException e) {
printLog(e.getFileName(), e.getLineNumber(), e
.getCharPositionInLine(), e.getMessage(), e
.getLocalizedMessage());
}
} catch (ParserException e) {
printLog(e.getFileName(), e.getLineNumber(), e
.getCharPositionInLine(), e.getMessage(), e
.getLocalizedMessage());
throw e;
}
}
}
}
/**
* Translates to java code corresponding to the YANG schema.
*
* @param pluginConfig YANG plugin config
* @throws IOException when fails to generate java code file the current node
*/
public void translateToJava(YangPluginConfig pluginConfig)
throws IOException {
List<YangNode> yangNodeSortedList = new LinkedList<>();
yangNodeSortedList.addAll(yangNodeSet);
sort(yangNodeSortedList);
for (YangNode node : yangNodeSortedList) {
if (node.isToTranslate() && !((YangDeviationHolder) node)
.isModuleForDeviation()) {
generateJavaCode(node, pluginConfig);
}
}
}
/**
* Adds log info for exception.
*
* @param fileName file name
* @param line line number
* @param position character position
* @param msg error message
* @param localMsg local message
*/
private void printLog(String fileName, int line, int position, String
msg, String localMsg) {
String logInfo = "Error in file: " + fileName;
if (line != 0) {
logInfo = logInfo + " at line: " + line + " at position: "
+ position;
}
if (msg != null) {
logInfo = logInfo + NEW_LINE + localMsg;
}
log.info(logInfo);
}
/**
* Process serialization of datamodel.
*
* @param path path of resource directory
* @param id model id
* @throws IOException when fails to IO operations
*/
public void processSerialization(String path, String id) throws IOException {
List<YangNodeInfo> nodeInfo = new ArrayList<>();
setNodeInfo(yangFileInfoSet, nodeInfo);
model = processYangModel(path, nodeInfo, id, false);
String serFileName = path + YANG_META_DATA;
try (FileOutputStream out = new FileOutputStream(serFileName);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(out))) {
objectOutputStream.writeObject(model);
}
}
private void setNodeInfo(Set<YangFileInfo> yangFileInfoSet,
List<YangNodeInfo> infos) {
for (YangFileInfo i : yangFileInfoSet) {
infos.add(new YangNodeInfo(i.getRootNode(), i.isInterJar()));
}
}
/**
* Copies yang files to resource directory.
*
* @param path yang file paths
* @throws IOException when fails to do IO operations
*/
private void processCopyYangFile(String path) throws IOException {
//add YANG files to JAR
List<File> files = getListOfFile(yangFileInfoSet);
File targetDir = new File(path);
if (!targetDir.exists()) {
boolean isCreated = targetDir.mkdirs();
if (!isCreated) {
throw new YangCompilerException(
"failed to create yang resource directory: " + path);
}
}
for (File file : files) {
copy(file.toPath(),
new File(path + file.getName()).toPath(),
REPLACE_EXISTING);
}
}
/**
* Returns yang file info set.
*
* @return yang file info set
*/
public Set<YangFileInfo> getYangFileInfoSet() {
return yangFileInfoSet;
}
/**
* Sets yang file info set.
*
* @param yangFileInfoSet yang file info set
*/
public void setYangFileInfoSet(Set<YangFileInfo> yangFileInfoSet) {
this.yangFileInfoSet = yangFileInfoSet;
}
/**
* Returns YANG model for application.
*
* @param path path for metadata file
* @param info list of YANG node info
* @param modelId model id
* @param fromUt if method is called from unit test
* @return YANG model
*/
public static YangModel processYangModel(
String path, List<YangNodeInfo> info, String modelId, boolean fromUt) {
YangModel.Builder b = DefaultYangModel.builder();
YangModuleId id;
for (YangNodeInfo i : info) {
id = processModuleId(i.getNode());
String serFile = path + id.moduleName() + id.revision() + ".ser";
if (!fromUt) {
serializeModuleMetaData(serFile, i.getNode());
}
//take the absolute jar path and make a new path for our yang files.
String fileName = getFileName(i.getNode().getFileName());
YangModuleExtendedInfo module = new YangModuleExtendedInfo(
id, new File(path + fileName), new File(serFile), i.isInterJar());
module.setSchema(i.getNode());
b.addModule(id, module);
}
return b.addModelId(modelId).build();
}
/**
* Returns the file name from provided absolute path.
*
* @param absPath absolute path
* @return file name
*/
private static String getFileName(String absPath) {
String[] file = absPath.split(SLASH);
return file[file.length - 1];
}
/**
* Serializes YANG Node.
*
* @param serFileName path of resource directory
* @param node YangNode
*/
private static void serializeModuleMetaData(String serFileName, YangNode node) {
try (FileOutputStream outStream = new FileOutputStream(serFileName);
ObjectOutputStream objOutStream = new ObjectOutputStream(new BufferedOutputStream(outStream))) {
objOutStream.writeObject(node);
} catch (IOException e) {
log.info("Error while serializing YANG node", e);
}
}
/**
* Returns YANG module id for a given YANG module node.
*
* @param module YANG module
* @return YANG module id for a given YANG module node
*/
public static YangModuleId processModuleId(YangNode module) {
String rev = getDateInStringFormat(module);
return new DefaultYangModuleId(module.getName(), rev);
}
/**
* Returns YANG model for serialization.
*
* @param path path for metadata file
* @param list set of YANG file info
* @param modelId model id
* @param fromUt if method is called from unit test
* @return YANG model
*/
private static YangModel getModelForSerialization(
String path, Set<YangFileInfo> list, String modelId, boolean fromUt) {
YangModel.Builder b = DefaultYangModel.builder();
YangModuleId id;
boolean interJar;
for (YangFileInfo info : list) {
YangNode node = info.getRootNode();
id = processModuleId(node);
interJar = info.isInterJar();
String serFile = path + id.moduleName() + id.revision() + ".ser";
if (!fromUt) {
serializeModuleMetaData(serFile, node);
}
//take the absolute jar path and make a new path for our yang files.
String fileName = getFileName(node.getFileName());
YangModuleExtendedInfo module = new YangModuleExtendedInfo(
id, new File(path + fileName), new File(serFile), interJar);
module.setSchema(node);
b.addModule(id, module);
}
return b.addModelId(modelId).build();
}
/**
* Provides a list of files from list of strings.
*
* @param yangFileInfo set of yang file information
* @return list of files
*/
private static List<File> getListOfFile(Set<YangFileInfo> yangFileInfo) {
List<File> files = new ArrayList<>();
for (YangFileInfo yangFile : yangFileInfo) {
if (yangFile.isForTranslator()) {
files.add(new File(yangFile.getYangFileName()));
}
}
return files;
}
/**
* Returns de-serializes YANG data-model.
*
* @param info serialized File path
* @return de-serializes YANG data-model
* @throws IOException when fails do IO operations
*/
public static YangModel deSerializeDataModel(String info)
throws IOException {
YangModel model;
try (FileInputStream fileInputStream = new FileInputStream(info);
ObjectInputStream objectInputStream =
new ObjectInputStream(new BufferedInputStream(fileInputStream))) {
model = ((YangModel) objectInputStream.readObject());
} catch (IOException | ClassNotFoundException e) {
throw new IOException(info + " failed to fetch nodes due to " + e
.getLocalizedMessage(), e);
}
return model;
}
/**
* Returns the set of YANG nodes from a given YANG model.
*
* @param model YANG model
* @return set of YANG nodes
*/
public static Set<YangNode> getYangNodes(YangModel model) {
Set<YangNode> yangNodes = new HashSet<>();
if (model != null) {
Set<YangModule> modules = model.getYangModules();
for (YangModule info : modules) {
yangNodes.add(((YangModuleExtendedInfo) info).getSchema());
}
}
return yangNodes;
}
/**
* Sets YANG node info.
*
* @param model YANG model
* @param infos node info to be filled
*/
public static void setNodeInfo(YangModel model, List<YangNodeInfo> infos) {
for (YangModule m : model.getYangModules()) {
YangModuleExtendedInfo i = (YangModuleExtendedInfo) m;
infos.add(new YangNodeInfo(i.getSchema(), i.isInterJar()));
}
}
/**
* Parses jar file and returns YANG model.
*
* @param jarFile jar file to be parsed
* @param directory directory where to search
* @return YANG model
* @throws IOException when fails to do IO operations
*/
public static YangModel parseJarFile(String jarFile, String directory)
throws IOException {
YangModel model = null;
try (JarFile jar = new JarFile(jarFile)) {
Enumeration<?> enumEntries = jar.entries();
File dir = new File(directory + SLASH + YANG_RESOURCES);
if (!dir.exists()) {
dir.mkdirs();
}
while (enumEntries.hasMoreElements()) {
JarEntry file = (JarEntry) enumEntries.nextElement();
if (file.getName().endsWith(YANG_META_DATA) ||
file.getName().endsWith(".yang")) {
String name = getFileName(file.getName());
File serializedFile = new File(directory + SLASH +
YANG_RESOURCES + SLASH + name);
if (file.isDirectory()) {
serializedFile.mkdirs();
continue;
}
try (InputStream inputStream = jar.getInputStream(file);
FileOutputStream fileOutputStream =
new FileOutputStream(serializedFile)) {
IOUtils.copy(inputStream, fileOutputStream);
fileOutputStream.close();
if (serializedFile.getName().endsWith(YANG_META_DATA)) {
model = deSerializeDataModel(serializedFile.toString());
}
}
}
}
}
return model;
}
}