Currently, it not feasible in MDSD to generate 100% of an application. Usually, you generate some kind of implementation skeleton into which developers integrate their own, manually written code. For example, the generator generates an abstract base class, from which developers extend their implementation classes – which contains the application logic.
The screenshot above shows an example – and also illustrates the use of the Recipe plugin. Let us look at the various items in the screenshot:
The generator creates a so-called
.recipes
file. How this file is created, will
be shown below. The recipe file contains the checks that need to be
executed on the code base. In the context menu of a
.recipes
file you will find an item called
Open Recipe File that opens the checks in the
respective browser. Note that the file has to have a
.recipes
extension – otherwise the plugin will
not open the file.
Here, is the manually written code (in the
src
folder). You can see the
classCalculatorImplementation
– this is why
the check that verifies its presence is successful. There is no
C1Implementation
why the check that checks
its presence fails.
src-gen
). It contains the generated base classes.
There are a couple of options to work with those recipes, as shown in the next illustration:
If you right-click on a check in the tree view, you can reevaluate the check explicitly. At the top-right of the view, there are three buttons. The first on collapses the tree view. The "play button with the small red cross" reevaluates all the failed checks - and only those! The third button is a filter button; if selected, the tree view hides the checks that are OK. In the drop down menu of the view (activated with the small downward-pointing triangle), there are two entries: the green run button labelled "reevaluate all" reevaluates all checks, even those that have been successful before. And finally, the reload button reloads the recipe file, and reevaluates everything.
There are two important automation steps:
There are two steps: The first one installs the plugin itself,
i.e. the Recipe browser view, etc. The respective plugin is
org.openarchitectureware.recipe
. As usual, you
install it by copying it into the Eclipse plugin directory or just by
downloading it from the oAW update site. If the plugin is installed in
this way, it can only evaluate the (relatively trivial) checks in the
recipe.simpleChecks
project. To check useful
things, you will have to extend the plugin – you have to contribute the
checks that should be executed. For this purpose, the
org.openarchitectureware.recipe plugin provides the
check
extension point. A number of useful
checks that can be evaluated in Eclipse are contained in the
org.openarchitectureware.recipe.eclipseChecks plugin.
You should install that one, too. It also comes automatically from the
update site.
In general, this means: whenever you develop your own checks and want to evaluate them in Eclipse, you have to contain them in a plugin and extend the above-mentioned extension point. Otherwise it will not work.
In order for the workflow to find the recipe JARs, they need to be on the classpath. The easiest way to achieve that is to make your generator project a plugin project and reference the recipe plugins in the plugin dependencies (all the oAW plugins with recipe in their name). This will update your classpath and add the necessary JARs. If, for some reason, you do not want your projects to be plugins, you have to reference the JAR files of the above mentioned plugins manually.
You have to write a workflow component that creates the recipes.
Your custom workflow component has to extend the
RecipeCreationComponent
base class and overwrite
the createRecipes()
operation. The operation
has to return the collection of recipes that should stored into the
recipe file. In the workflow file, you then have to configure this
component with the name of the application project in Eclipse, the
source path where the manually write code can be found, and the name of
the recipe file to be written.
Please take a look a the emfHelloWorld example. It contains an extensive example of how to use the recipes in oAW 4.
You can also check the recipes using ant. Of course you cannot use those nice and cool interactive checks – and you also cannot use Eclipse-based checks. They can be in the recipes file, since the ant tasks skips them automatically. The following piece of ant code shows how to run the checks – should be self-explanatory. Note that you have to use all the jar files from the recipe.ant project.
<?xml version="1.0" encoding="ISO-8859-1" ?> <project name="scm - hello world - generate" default="generate"> <property file="build.properties" /> <path id="ant.runtime.classpath"> <pathelement location="${GENROOT}" /> <fileset dir="${GENROOT}" includes="*.jar"/> <fileset dir="${OAWROOT}/dist" includes="*.jar"/> <fileset dir="${RECIPE.CORE.DIR}/dist" includes="*.jar"/> <fileset dir="${RECIPE.SIMPLECHECKS.DIR}/dist" includes="*.jar"/> <fileset dir="${RECIPE.ANT.DIR}/dist" includes="*.jar"/> <fileset dir="${RECIPE.ANT.DIR}/lib" includes="*.jar"/> <fileset dir="${RECIPE.ECLIPSECHECKS.DIR}" includes="*.jar"/> </path> <target name="check" depends=""> <taskdef name="check" classname="org.openarchitectureware.recipe.ant.RecipeCheckTask"> <classpath refid="ant.runtime.classpath" /> </taskdef> <check recipeFile="L:/workspace/xy/helloWorld.recipes"/> </target> </project>
The checks use log4j logging to output the messages. So you can set the log level using the log4j.properties file. The following output shows all the checks being successful:
Buildfile: l:\exampleWorkspace-v4\scmHelloWorld\build.xml check: [check] 0 INFO - checking recipes from file: L:/runtime-EclipseApplication/xy/helloWorld.recipes BUILD SUCCESSFUL Total time: 1 second
If you set the log level to DEBUG, you will get more output. Here, in our case, you can see that all the Eclipse checks are skipped.
Buildfile: l:\exampleWorkspace-v4\scmHelloWorld\build.xml check: [check] 0 INFO - checking recipes from file: L:/runtime- EclipseApplication/xy/helloWorld.recipes [check] 60 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. [check] 70 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. [check] 70 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. [check] 80 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. [check] 80 DEBUG - [skipped] resource exists exists -- skipped - mode was batch only. [check] 90 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. [check] 90 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. [check] 90 DEBUG - [skipped] resource exists exists - skipped - mode was batch only. BUILD SUCCESSFUL Total time: 1 second
If there are errors, they will be output as an ERROR level message.
The following piece of code is the simplest check, you could possibly write:
package org.openarchitectureware.recipe.checks.test; import org.openarchitectureware.recipe.core.AtomicCheck; import org.openarchitectureware.recipe.eval.EvaluationContext; public class HelloWorldCheck extends AtomicCheck { private static final long serialVersionUID = 1L; public HelloWorldCheck() { super("hello world", "this check always succeeds"); } public void evaluate(EvaluationContext c) { ok(); } }
A couple of notes:
setLongDescription()
if you want to set the explanatory text.
EvalTrigger
. By default, the
EvalTrigger.ON_REQUEST
is used which means that the check is only evaluated upon explicit request. If you pass
EvalTrigger.ON_CHANGE
, the check will be automatically re-evaluated if the Eclipse workspace changes.
evaluate()
method you do the check itself. We will explain more on that later.
More sensible checks distinguish themselves in two respects:
evaluate()
operation.
setParameter(name, value)
operation for that. More on that below.
An example:
public void evaluate(EvaluationContext c) { if ( something is not right ) { fail( "something has gone wrong"); } if ( some condition is met ) { ok(); }
By the way, you do not need to care about the EvaluationContext. It is only needed by the framework.
Eclipse checks are a bit special. If the check were implemented in the way described above, you would have a lot of dependencies to all the Eclipse plugins/jars. You would have these dependencies, as soon as you instantiate the check – i.e. also in the generator when you configure the check. In order to avoid this, we have to decouple the configuration of a check in the generator and its evaluation later in Eclipse:
An Eclipse check is thus implemented in the following way. First
of all, our check has to extend the
EclipseCheck
base class.
public class ResourceExistenceCheck extends EclipseCheck {
Again, we add a serial version UID to make sure deserialization will work.
private static final long serialVersionUID = 2L;
In the constructor we decide whether we want to have this check
evaluated whenever the Eclipse workspace changes
(EvalTrigger.ON_CHANGE
) or not. We also store
some of the parameters in the parameter facility of the framework.
Note that we do not implement the
evaluate()
operation!
public ResourceExistenceCheck(String message, String projectName, String resourceName) { super("resource exists exists", message, EvalTrigger.ON_CHANGE); setProjectName(projectName); setResourceName(resourceName); } private void setProjectName(String projectName) { setParameter("projectName", projectName); } private void setResourceName(String resourceName) { setParameter("resourceName", resourceName); }
In order to provide the evaluation functionality, you have to
implement an ICheckEvaluator
. It has to have
the same qualified name as the check itself,
postfixed with Evaluator
. During the evaluation
of the check, the class is loaded dynamically based on its name. A
wrong name will result in a runtime error during the evaluation of the
Eclipse plugin.
public class ResourceExistenceCheckEvaluator implements ICheckEvaluator { public void evaluate(AtomicCheck check) { String projectName = check.getParameter("projectName").getValue().toString(); String resourceName = check.getParameter("resourceName").getValue().toString(); IWorkspace workspace = ResourcesPlugin.getWorkspace(); IResource project = workspace.getRoot().getProject(projectName); if (project == null) check.fail("project not found: "+projectName); IFile f = workspace.getRoot().getFile( new Path(projectName+"/"+resourceName) ); String n = f.getLocation().toOSString(); if ( !f.exists() ) check.fail( "resource not found: "+projectName+"/"+resourceName); check.ok(); }
When implementing the evaluator, you can basically do the same
things as in the evaluate()
operation in
normal checks. However, in order to set the
ok()
or fail("why")
flags, you have to call the respective operations on the check passed
as the parameter to the operation.
In order to allow the Eclipse runtime to execute your checks, it has to find the respective classes when deserializing the recipe file. This is a bit tricky in Eclipse, since each plugin has its own class loader. So, assume you want to define your own checks and want to use them in Eclipse; what you have to do is: implement your own plugin that extends a given extension point in the Recipe Browser plugin. The following XML is the plugin descriptor of the org.openarchitectureware.recipe.eclipseChecks.plugin.EclipseChecksPlugin , a sample plugin that comes with the recipe framework and provides a number of Eclipse-capable checks.
<?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.0"?> <plugin id="org.openarchitectureware.recipe.eclipseChecks" name="%plugin_name" version="4.0.0" provider-name="%provider_name" class="org.openarchitectureware.recipe.\ eclipseChecks.plugin.EclipseChecksPlugin">
Here we now define the jar file that contains our checks (This will be important below)
<runtime> <library name="oaw-recipe-eclipsechecks.jar"> <export name="*"/> </library> </runtime>
The required plugins mainly depend on the implementations of
CheckEvaluator
, of course, however, you have to
make sure the dependencies contains the
org.openarchitectureware.recipe plugin, since you
are going to extend an extension point defined in this plugin.
<requires> <import plugin="org.eclipse.ui"/> <import plugin="org.eclipse.core.runtime"/> <import plugin="org.eclipse.core.resources"/> <import plugin="org.openarchitectureware.recipe"/> <import plugin="org.eclipse.jdt.core"/> </requires>
This is the important line: here you specify that you extend the
check
extension point of the
Recipe browser plugin. If you do not do this,
deserialization of the recipe file will fail and you will get nasty
errors. And yes, you need the dummy element; because otherwise, the
class loading "magic" will not work.
<extension point="org.openarchitectureware.recipe.check"> <dummy/> </extension> </plugin>
When you need to use the checks outside of Eclipse (e.g. in the generator for configuration/serialization purposes) you just add the JAR file of the plugin to your generator classpath. You do not need to add references to all the JAR files of the Eclipse plugin in the requires section, since these things will only be used during evaluation.
Component | Plugin | Depends on | Description |
---|---|---|---|
recipe.core | Yes | - | Framework core. Needed whenever you do anything with recipes |
recipe.ant | Yes | recipe.core | Contains the ant task to check recipes. Needed only for recipe evaluation in ant |
recipe.simpleChecks | Yes | recipe.core | Contains a number of (more or less useful) sample checks |
recipe.plugin | Yes | recipe.core | Contains the Eclipse Recipe Browser view |
recipe.eclipsechecks.plugin | Yes | recipe.core | Contains the pre-packaged Eclipse checks. |
This table contains a list of all currently available checks. We are working on additional ones. Contributions are always welcome, of course! This list might, thus, not always be up to date – just take a look at the code to find out more.
Type | Classnames | Purpose |
---|---|---|
Batch | org.openarchitectureware.recipe.checks. file.FileExistenceCheck | Checks whether a given file exists |
Batch | org.openarchitectureware.recipe.checks. file.FileContentsCheck | Checks whether a given substring can be found in a certain file |
Eclipse | org.openarchitectureware.recipe. eclipseChecks.checks. JavaClassExistenceCheck | Checks whether a given Java class exists |
Eclipse | org.openarchitectureware.recipe. eclipseChecks.checks. JavaSupertypeCheck | Checks whether a given Java class extends a certain superclass |
Eclipse | org.openarchitectureware.recipe. eclipseChecks.checks. ResourceExistenceCheck | Checks whether a given Eclipse Workspace Resource exists |