Skip to content

Commit

Permalink
Merge pull request #41 from NodePit/migration-nodes
Browse files Browse the repository at this point in the history
Migration nodes
  • Loading branch information
qqilihq authored Aug 6, 2024
2 parents a852352 + ee33abb commit 9b41a2a
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 1 deletion.
4 changes: 3 additions & 1 deletion application/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.6.0,5.0.0)",
org.eclipse.equinox.p2.engine;bundle-version="[2.4.100,3.0.0)",
org.knime.product;bundle-version="[4.7.0,6.0.0)",
org.apache.log4j;bundle-version="[1.2.0,2.0.0)",
com.google.gson;bundle-version="[2.8.6,3.0.0)"
com.google.gson;bundle-version="[2.8.6,3.0.0)",
org.knime.workflow.migration;bundle-version="[4.7.0,6.0.0)",
org.mockito.mockito-core;bundle-version="[2.28.2,3.0.0)"
Bundle-ClassPath: .
Bundle-ActivationPolicy: lazy
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ public class JsonNodeDocuGenerator implements IApplication {

private static final String SKIP_SPLASH_ICONS = "-skipSplashIcons";

private static final String SKIP_MIGRATION_RULES = "-skipMigrationRules";

/** Return code in case an error occurs during execution. */
private static final Integer EXIT_EXECUTION_ERROR = Integer.valueOf(1);

Expand Down Expand Up @@ -170,6 +172,8 @@ private static void printUsage() {

private boolean m_skipSplashIcons = false;

private boolean m_skipMigrationRules = false;

private CategoryDocBuilder rootCategoryDoc;

@Override
Expand Down Expand Up @@ -199,6 +203,8 @@ public Object start(final IApplicationContext context) throws Exception {
m_skipPortDocumentation = true;
} else if (args[i].equals(SKIP_SPLASH_ICONS)) {
m_skipSplashIcons = true;
} else if (args[i].equals(SKIP_MIGRATION_RULES)) {
m_skipMigrationRules = true;
} else if (args[i].equals("-help")) {
printUsage();
return EXIT_OK;
Expand Down Expand Up @@ -315,6 +321,14 @@ private void generate() throws Exception {
IOUtils.write(Utils.toJson(splashIcons), new FileOutputStream(splashIconsResultFile),
StandardCharsets.UTF_8);
}

if (!m_skipMigrationRules) {
var migrationRuleDocs = MigrationRuleExtractor.extractMigrationRules();
var migrationsResultFile = new File(m_directory, "migrations.json");
LOGGER.info("Writing migrations to " + migrationsResultFile);
IOUtils.write(Utils.toJson(migrationRuleDocs), new FileOutputStream(migrationsResultFile),
StandardCharsets.UTF_8);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package de.philippkatz.knime.jsondocgen;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.knime.core.node.NodeFactory;
import org.knime.core.node.extension.NodeFactoryExtensionManager;
import org.knime.workflow.migration.MigrationNodeMatchResult;
import org.knime.workflow.migration.NodeMigrationRuleRegistry;
import org.knime.workflow.migration.model.MigrationNode;
import org.mockito.Mockito;

import de.philippkatz.knime.jsondocgen.docs.MigrationRuleDoc;
import de.philippkatz.knime.jsondocgen.docs.MigrationRuleDoc.MigrationRuleDocBuilder;

/**
* Extract migration rules - their migration “framework” is the probably most
* complex thinkable solution from a technical and API level, yet frighteningly
* limited at the end.
*
* This code tries to extract an original node factory ID and its corresponding
* replacement factory.
*
* Context-specific properties, where the migration depends e.g. on settings are
* _not_ covered - however only few rules actually make use of them.
*
* @author Philipp Katz
*/
public class MigrationRuleExtractor {

private static final Logger LOGGER = Logger.getLogger(MigrationRuleExtractor.class);

public static List<MigrationRuleDoc> extractMigrationRules() {

var migrationRules = NodeMigrationRuleRegistry.getInstance().getRules();
// they replaced this in 5.2 but of course the “get all” is not accessible -
// facepalm; in case it gets removed, we'll need to build this ourselves or get
// it from the “generate node documenation” phase instead
var nodeFactoryExtensions = NodeFactoryExtensionManager.getInstance().getNodeFactoryExtensions();

LOGGER.info(String.format("Generating %s migration rules", migrationRules.size()));

var migrationRuleInfos = new ArrayList<MigrationRuleDoc>();
for (var rule : migrationRules) {
try {
// (1) match
var matchMethod = getDeclaredMethodSuper(rule.getClass(), "match", MigrationNode.class);
matchMethod.setAccessible(true);

var getReplacementNFClass = getDeclaredMethodSuper(rule.getClass(), "getReplacementNodeFactoryClass",
MigrationNode.class, MigrationNodeMatchResult.class);
getReplacementNFClass.setAccessible(true);

// we'll need to loop through all known nodes here to extract the rules -
// probably it makes sense to integrate this into the node documentation loop,
// as we have |node| >> |migration_rule|
for (var nodeFactoryExtension : nodeFactoryExtensions) {
var migrationNodeMock = Mockito.mock(MigrationNode.class);
var factoryClass = nodeFactoryExtension.getFactory().getClass();
Mockito.when(migrationNodeMock.getOriginalNodeFactoryClass()).then(invocation -> factoryClass);
Mockito.when(migrationNodeMock.getOriginalNodeFactoryClassName())
.then(invocation -> factoryClass.getName());

var matchResult = (MigrationNodeMatchResult) matchMethod.invoke(rule,
new Object[] { migrationNodeMock });
var nodeActions = matchResult.getNodeActions();
if (nodeActions.size() == 1) {
LOGGER.info(String.format("Rule %s returned %s actions for original %s",
rule.getClass().getName(), nodeActions.size(), factoryClass.getName()));

// (2) replacement
@SuppressWarnings("unchecked")
var replacementFactoryClass = (Class<? extends NodeFactory<?>>) getReplacementNFClass
.invoke(rule, new Object[] { migrationNodeMock, matchResult });

migrationRuleInfos.add(new MigrationRuleDocBuilder() //
.setOriginalNodeFactoryClass(factoryClass.getName()) //
.setReplacementNodeFactoryClass(replacementFactoryClass.getName()) //
.build()); //
} else if (nodeActions.size() > 1) {
LOGGER.error(
String.format("Rule %s returned %s actions for %s - this is unexpected and unsupported",
rule.getClass().getName(), factoryClass.getName(), nodeActions.size()));
}
}

} catch (Exception e) {
Throwable unwrapped = e;
if (e instanceof InvocationTargetException ite) {
unwrapped = ite.getTargetException();
}
LOGGER.warn(String.format("Error for %s: %s", rule.getClass().getName(), unwrapped.getMessage()),
unwrapped);
}
}

return migrationRuleInfos;

}

/**
* Get a declared method on the class, or any super class.
*
* @param cl
* @param methodName
* @param parameterTypes
* @return
* @throws IllegalStateException If there is no such method.
*/
private static final Method getDeclaredMethodSuper(Class<?> cl, String methodName, Class<?>... parameterTypes) {
Method method = null;
for (;;) {
try {
method = cl.getDeclaredMethod(methodName, parameterTypes);
break;

} catch (NoSuchMethodException e) {
var superClass = cl.getSuperclass();
if (superClass == null) {
break;
}
cl = superClass;
}
}
if (method == null) {
throw new IllegalStateException(
String.format("method %s is not implemented by class or super classes", methodName));
}

return method;
}

private MigrationRuleExtractor() {
// ...
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package de.philippkatz.knime.jsondocgen.docs;

public class MigrationRuleDoc {

public static class MigrationRuleDocBuilder {

private String originalNodeFactoryClass;
private String replacementNodeFactoryClass;

public MigrationRuleDocBuilder setOriginalNodeFactoryClass(String originalNodeFactoryClass) {
this.originalNodeFactoryClass = originalNodeFactoryClass;
return this;
}

public MigrationRuleDocBuilder setReplacementNodeFactoryClass(String replacementNodeFactoryClass) {
this.replacementNodeFactoryClass = replacementNodeFactoryClass;
return this;
}

public MigrationRuleDoc build() {
return new MigrationRuleDoc(this);
}

}

private final String originalNodeFactoryClass;

private final String replacementNodeFactoryClass;

MigrationRuleDoc(MigrationRuleDocBuilder migrationRuleInfoBuilder) {
this.originalNodeFactoryClass = migrationRuleInfoBuilder.originalNodeFactoryClass;
this.replacementNodeFactoryClass = migrationRuleInfoBuilder.replacementNodeFactoryClass;
}

public String getOriginalNodeFactoryClass() {
return originalNodeFactoryClass;
}

public String getReplacementNodeFactoryClass() {
return replacementNodeFactoryClass;
}

}

0 comments on commit 9b41a2a

Please sign in to comment.