Crafting NS Customizations

An NS project consists of 2 things provided by the developers:

  • A model that describes the application in an abstract way and from which the expanders will expand a codebase
  • Customizations in the form of code inserted in custom anchors, and additional customized files, which are labeled ext files.

Custom Anchors

The easiest way to add custom code is to place it in custom anchors. Custom anchors are points in the generated code that allow you to add code to add to or change behavior of the generated code. The code written in custom anchors will be stored separately after harvesting, so that the next expansion can reinsert in the correct place.

For example, the following create-method has 2 custom-anchor-pairs. Between the start and end anchors custom code can be placed. After harvesting, the code will be injected between the anchors during expansion.

public CrudsResult<DataRef> create(ParameterContext<CarDetails> detailsParameter) {
   CarDetails details = detailsParameter.getValue();
   UserContext userContext = detailsParameter.getUserContext();

   // anchor:custom-preCreate:start
   // anchor:custom-preCreate:end

   CrudsResult<DataRef> result = carCrudsLocal.create(detailsParameter);

   if (result.isError()) {
     return result;
   }
   // anchor:custom-postCreate:start
   // anchor:custom-postCreate:end
   return result;
}

You can add some validation in the custom-preCreate anchor:

// anchor:custom-preCreate:start
if (!details.getLicensePlate().matches("[0-9A-Z\\-]+")) {
  return CrudsResult.error();
}
// anchor:custom-preCreate:end

Common Mistakes

  • Harvest frequently! When re-expanding, any code that is not harvested will disappear. In case you forget, IntelliJ has a memory of changes to a file that might help you recover.
  • Only anchor of the form // anchor:custom-*:(start|end) are custom anchors. There are some other anchors used by the expanders which are not harvested.
    If this proves too confusing, adding the option hideAnchors to you expansionSettings file will hide all non-custom anchors.
  • Note that the imports of a class also have a custom anchor. New imports should be placed there. Since most idea’s automatically add imports, you might run into this a few times.

Ext files

If you take a look at an expanded, you will notice that each layer has a gen and an ext directory.

  • The gen directory is where you will find most of the expanded files.
  • The ext directory is where you can place custom files

The directories under ext will be harvested and reinserted when harvesting and expanding.

Creating ext files that contain your custom logic and referencing them from custom anchors has some advantages to putting everything in the custom anchors:

  • The logic will be encapsulated, which decreases coupling
  • The expanded files can become large, which makes it harder to manage customizations. Separating some of the logic into ext files allows you to keep the most complex logic out of the custom anchors.
  • Testing logic will be easier when separated into separate classes.
  • Ext files do not have the problem of forgetting imports in the custom-imports anchors

Organizing Customizations

note: This requires expanders 4.15.5 or up. Older versions contain a bug that filters the custom feature anchors when expanding.

Version 1.10.0 of the nsx-support plugin for IntelliJ introduces the CustomFeatures tool window. This tool window lists all custom features in your application.

A custom feature is a group of customizations that implement a certain coherent feature.

Let’s say we have some customizations to process messages. We can start by labeling all related ext files with a comment mentioning the custom feature name:

// @feature:message-processing
@XmlRootElement(name = "message")
public class MessageXml {
...
// @feature:message-processing
@XmlRootElement(name = "refundRequest")
public class RefundRequestXml {
...

Next, we provide similar comments around the related code in custom anchors:

// anchor:custom-imports:start
// @feature:message-processing:start
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

import net.demo.xml.AccountUpdateXml;
import net.demo.xml.OrderUpdateXml;
import net.demo.xml.RefundRequestXml;

...
// @feature:message-processing:end
// anchor:custom-imports:end
// anchor:custom-perform:start
// @feature:message-processing:start
MessageDetails details = targetParameter.getValue();

JAXBContext jaxbContext = JAXBContext.newInstance(AccountUpdateXml.class, OrderUpdateXml.class, RefundRequestXml.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
try (StringReader reader = new StringReader(details.getContent())) {
Object object = unmarshaller.unmarshal(reader);
taskResult = processXmlObject(context, object);
}

details.setProcessedAt(new Date());
MessageLocalAgent.getMessageAgent(context).modify(details);
// @feature:message-processing:end
// anchor:custom-perform:end
// anchor:custom-methods:start
// @feature:message-processing:start
private TaskResult<Void> processXmlObject(Context context, Object object) {
...
// @feature:message-processing:end
// anchor:custom-methods:end    

The tool window will update when it is focused. It will list all of the labeled customization under the custom feature message-processing:

The windows allows you to navigate to the files by selecting them. Carefully labeling all of your customizations allows you and other developers to navigate through the custom code and gain better understanding.

In addition to the tool-window, the same information is also present for report generators.

Common Customizations

Below are some aspects of the code which require manual intervention by the application developer after expanding the code. These customizations are