Skip to content

Commit

Permalink
重构EntityDefDirTransformer
Browse files Browse the repository at this point in the history
  • Loading branch information
entropy-cloud committed Sep 14, 2024
1 parent 90a6c34 commit 6013dc9
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 76 deletions.
7 changes: 6 additions & 1 deletion docs/compare/nop-vfs-ofbiz.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ OFBiz使用的xsd语法非常臃肿,而且引入了完全不必要的顺序依

## 服务引擎(Service Engine)

与程序语言无关
与程序语言无关。输入是Map,输出也是Map

You can also override a service by using the same name down in the deployment context (which is first framework, then themes, then applications, then specialpurpose, then hot-deploy)

ECA (Event Condition Action) is much like a trigger. When a service is called, a lookup is performed to see if any ECAs are defined for this event.


## 组件系统(Widget System)

Expand Down
26 changes: 25 additions & 1 deletion docs/dev-guide/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class NopOrmCodeGen {
public static void main(String[] args) {
AppConfig.getConfigProvider().updateConfigValue(CoreConfigs.CFG_CORE_MAX_INITIALIZE_LEVEL,
CoreConstants.INITIALIZER_PRIORITY_ANALYZE);

CoreInitialization.initialize();
try {
File projectDir = MavenDirHelper.projectDir(NopOrmCodeGen.class);
Expand Down Expand Up @@ -187,6 +187,30 @@ if(globalVar){
2. `{data.@mapper}` 属性表达式存在一种特殊的约定。对于HashSet或者LinkedHashSet类型,`@mapper`属性会判断集合中是否存在该文本值。
相当于 `((Set)data).contains('mapper')`

### xgen模板文件
xgen本质上就是xpl模板语言,它动态执行输出内容。xpl模板语言中可以通过`<c:for>``<c:script>`等标签执行逻辑,并通过`<c:import>`来导入标签库。
xpl还提供了`<c:print>`这种标签用于原样输出它的body内容,即使其中包含`c:script`等标签。

例如
```xml
<c:unit>
<c:script>
let n = 100; // 设置一个变量,可以执行复杂的XScript代码
</c:script>

<!-- 通过表达式可以使用当前环境中的变量。xpl模板语言提供了c:for等标签用于实现循环逻辑 -->
<c:for var="i" begin="${1}" end="${n}">
<div/>
</c:for>
</c:unit>
```
上面的代码会输出100个`<div>`节点。

`c:script`就是编译成一个Expression,然后`expr.invoke(scope)`, 在当前的scope中执行。scope中的数据就是 `@init.xrun`中准备的。在`@init.xurn``assign("x",1")`可以向scope中设置变量。另外`builder.defineGlobalVar('basePackagePath', pkgPath)`等也会设置变量。

xpl模板语言的介绍参见[xpl.md](xlang/xpl.md)


## 二. 差量化的代码生成器

如果我们不把代码生成器看作是某种一次性的、临时使用的外围工具,而是把它作为元编程的一个有机组成部分,则代码生成器必然是支持增量生成的。所谓增量生成,是指代码生成器允许反复执行,且同时允许手工修改输出产物,自动生成和手工修改的部分都可以看作是对初次生成结果的增量化修改,并且它们会自动合并在一起。
Expand Down
7 changes: 7 additions & 0 deletions docs/dev-guide/orm/orm-basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ transactionTemplate.runInTransaction(txn->{
})
```

## OrmEntity实体属性
NopORM中的所有实体类都从OrmEntity类继承。OrmEntity不支持JSON序列化,但是提供了帮助函数可以获取实体上的字段值。

实体上当前的字段值是 `entity.orm_initedValues()`, 修改前的值是 `orm_dirtyOldValues()`



## 与MyBatis的区别

NopORM是一个类似JPA的完整的ORM引擎,因此它使用OrmSession来管理所有加载到内存中的实体,整体使用类似于JPA和Hibernate,相比于MyBatis要少很多手工调用步骤。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,66 @@
import io.nop.core.lang.xml.XNode;
import io.nop.core.lang.xml.parse.XNodeParser;
import io.nop.core.resource.impl.FileResource;
import io.nop.ofbiz.migration.OfbizMigrationConstants;
import io.nop.orm.model.OrmModel;
import io.nop.xlang.xdsl.DslModelHelper;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class EntityDefDirTransformer {
private final Map<String, XNode> ormNodes = new HashMap<>();
private final Map<String, XNode> entityNodes = new HashMap<>();
private final Map<String, List<XNode>> unknownRelations = new HashMap<>();
private final OrmModel baseModel = (OrmModel) DslModelHelper.loadDslModelFromPath(OfbizMigrationConstants.PATH_OFBIZ_BASE_ORM);

// 记录哪些ORM模型增加了哪些外部实体定义
private final Map<XNode, Set<String>> externalEntityMap = new HashMap<>();
private final Map<String, EntityDefModel> entityDefModels = new HashMap<>();

private final Map<String, XNode> entityMap = new HashMap<>();

public void transformDir(File dir, File targetDir) {
loadFromDir(dir);

// 先加载所有的entitydef模型,记录未识别的relation实体引用
for (File file : dir.listFiles()) {
XNode node = XNodeParser.instance().parseFromResource(new FileResource(file));
EntityDefModelToOrmModel trans = new EntityDefModelToOrmModel();
XNode ormNode = trans.transform(node);
ormNodes.put(StringHelper.fileNameNoExt(file.getName()), ormNode);
entityDefModels.values().forEach(defModel -> {
defModel.collectEntities(entityMap);
});

trans.mergeTo(entityNodes, unknownRelations);
}
resolveRelations();

resolveUnknownRelations();
entityDefModels.values().forEach(EntityDefModel::addExternalEntities);

saveOrmNodes(targetDir);
}

void resolveUnknownRelations() {
unknownRelations.forEach((name, list) -> {
XNode refEntityNode = entityNodes.get(name);
if (refEntityNode != null) {
String fullName = refEntityNode.attrText("name");
// 将relation的refEntityName设置为全名
for (XNode refNode : list) {
refNode.setAttr("refEntityName", fullName);

// relation -> relations -> entity
XNode ormNode = refNode.root();
Set<String> externals = externalEntityMap.computeIfAbsent(ormNode, k -> new HashSet<>());
if (externals.add(fullName)) {
// 缺少external定义
XNode entities = ormNode.makeChild("entities");
XNode externalNode = refEntityNode.cloneInstance();
externalNode.setAttr("notGenCode", true);
entities.appendChild(externalNode);
}
}
} else {
throw new IllegalArgumentException("nop.err.ofbiz.unresolved-relation-entity-name:" + name + "," + list.get(0));
public void loadFromDir(File dir) {
// 先加载所有的entitydef模型,记录未识别的relation实体引用
for (File file : dir.listFiles()) {
String name = file.getName();
if (!name.endsWith("-entitymodel.xml")) {
continue;
}
String moduleName = StringHelper.removeTail(name, "-entitymodel.xml");

XNode node = XNodeParser.instance().parseFromResource(new FileResource(file));
EntityDefModel defModel = new EntityDefModel(node, baseModel);
entityDefModels.put(moduleName, defModel);
}
}

void resolveRelations() {
this.entityDefModels.values().forEach(defModel -> {
defModel.resolveRelations(refEntityName -> {
XNode node = entityMap.get(refEntityName);
if (node == null)
throw new IllegalArgumentException("nop.err.ofbiz.unresolved-relation-entity-name:" + refEntityName);
return node;
});
});
}

void saveOrmNodes(File targetDir) {
ormNodes.forEach((name, node) -> {
File file = new File(targetDir, name + ".orm.xml");
node.saveToResource(new FileResource(file), null);

// DslModelHelper.loadDslModel(new FileResource(file));
entityDefModels.forEach((name, node) -> {
File file = new File(targetDir, "ofbiz/" + name + "/app.orm.xml");
node.getOrmNode().saveToResource(new FileResource(file), null);
DslModelHelper.loadDslModel(new FileResource(file));
});
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,67 @@
package io.nop.ofbiz.migration.transform;

import io.nop.commons.util.StringHelper;
import io.nop.commons.util.objects.Pair;
import io.nop.core.lang.xml.XNode;
import io.nop.ofbiz.migration.OfbizMigrationConstants;
import io.nop.orm.model.OrmDomainModel;
import io.nop.orm.model.OrmModel;
import io.nop.xlang.xdsl.DslModelHelper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

public class EntityDefModelToOrmModel {
private final OrmModel baseModel = (OrmModel) DslModelHelper.loadDslModelFromPath(OfbizMigrationConstants.PATH_OFBIZ_BASE_ORM);
public class EntityDefModel {
private final OrmModel baseModel;
private final XNode node;
private final XNode ormNode;
private final Map<String, Pair<XNode, XNode>> entityNodes = new HashMap<>();
private final Map<String, Pair<XNode, XNode>> viewEntityNodes = new HashMap<>();
private final Map<String, XNode> externalNodes = new HashMap<>();

private final Map<String, XNode> entityNodes = new HashMap<>();
private final Map<String, List<XNode>> unknownRelations = new HashMap<>();
public EntityDefModel(XNode node, OrmModel baseModel) {
this.node = node;
this.baseModel = baseModel;
this.ormNode = transform(node);
}

public Map<String, XNode> getEntityNodes() {
return entityNodes;
public XNode getDefNode() {
return node;
}

public Map<String, List<XNode>> getUnknownRelations() {
return unknownRelations;
public XNode getOrmNode() {
return ormNode;
}

public void mergeTo(Map<String, XNode> entityNodes, Map<String, List<XNode>> unknownRelations) {
entityNodes.putAll(this.getEntityNodes());
this.getUnknownRelations().forEach((name, list) -> {
unknownRelations.computeIfAbsent(name, k -> new ArrayList<>()).addAll(list);
public void collectEntities(Map<String, XNode> map) {
entityNodes.forEach((name, pair) -> {
if (map.containsKey(name))
throw new IllegalArgumentException("nop.err.ofbiz.duplicate-entity-name:name=" + name + "," + pair.getSecond());
map.put(name, pair.getSecond());
});

viewEntityNodes.forEach((name, pair) -> {
if (map.containsKey(name))
throw new IllegalArgumentException("nop.err.ofbiz.duplicate-entity-name:name=" + name + "," + pair.getSecond());
map.put(name, pair.getSecond());
});
}

public XNode transform(XNode node) {
public void addExternalEntities() {
// 缺少external定义
XNode entities = ormNode.makeChild("entities");
for (XNode node : this.externalNodes.values()) {
XNode externalNode = node.cloneInstance();
externalNode.getChildren().removeIf(child -> !child.getTagName().equals("columns"));
externalNode.setAttr("notGenCode", true);
entities.appendChild(externalNode);
}
}

private XNode transform(XNode node) {
XNode ret = XNode.make("orm");
ret.setAttr("x:extends", OfbizMigrationConstants.PATH_OFBIZ_BASE_ORM);
ret.setAttr("x:schema", "/nop/schema/orm/orm.xdef");
Expand All @@ -51,18 +76,20 @@ public XNode transform(XNode node) {
entities.appendChild(entityNode);
if (entityNodes.containsKey(entityName))
throw new IllegalArgumentException("nop.err.ofbiz.duplicate-entity-name:" + entityName);
entityNodes.put(entityName, entityNode);
entityNodes.put(entityName, Pair.of(child, entityNode));
}
}

for (XNode child : node.getChildren()) {
if (child.getTagName().equals("entity")) {
if (child.getTagName().equals("view-entity")) {
XNode entityNode = transformViewEntity(child);
String entityName = child.attrText("entity-name");
XNode entityNode = entityNodes.get(entityName);
transformRelation(entityNode, child);
entities.appendChild(entityNode);
if (entityNodes.containsKey(entityName))
throw new IllegalArgumentException("nop.err.ofbiz.duplicate-entity-name:" + entityName);
viewEntityNodes.put(entityName, Pair.of(child, entityNode));
}
}

return ret;
}

Expand Down Expand Up @@ -100,6 +127,42 @@ private XNode transformEntity(XNode node) {
return ret;
}

private XNode transformViewEntity(XNode node) {
XNode ret = XNode.make("entity");
ret.setAttr("tableView", true);
ret.setAttr("noPrimaryKey", true);
ret.setAttr("readonly", true);

String packageName = node.attrText("package-name");
String entityName = node.attrText("entity-name");
String name = StringHelper.fullClassName(entityName, packageName);
String tableName = node.attrText("table-name");
if (StringHelper.isEmpty(tableName)) {
tableName = StringHelper.camelCaseToUnderscore(entityName, true);
} else {
tableName = tableName.toUpperCase(Locale.ROOT);
}
String title = node.attrText("title");
ret.setAttr("name", name);
ret.setAttr("className", name);
ret.setAttr("tableName", tableName);
ret.setAttr("displayName", title);

return ret;
}

private void transformViewAlias(XNode viewEntityNode, XNode entityNode) {
Map<String, String> aliasMap = new HashMap<>();

viewEntityNode.childrenByTag("member-entity").forEach(child -> {
String alias = child.attrText("entity-alias");
String entityName = child.attrText("entity-name");
aliasMap.put(alias, entityName);
});


}

private XNode transformField(XNode node, Set<String> pkNames) {
String name = node.attrText("name");
String type = node.attrText("type");
Expand Down Expand Up @@ -139,7 +202,17 @@ private Set<String> getPrimaryKeys(XNode node) {
return ret;
}

private void transformRelation(XNode ret, XNode node) {
public void resolveRelations(Function<String, XNode> refEntityResolver) {
entityNodes.values().forEach(pair -> {
transformRelation(pair.getSecond(), pair.getFirst(), refEntityResolver);
});

viewEntityNodes.values().forEach(pair -> {
transformRelation(pair.getSecond(), pair.getFirst(), refEntityResolver);
});
}

private void transformRelation(XNode ret, XNode node, Function<String, XNode> externalEntityResolver) {
XNode relations = ret.addChild("relations");
for (XNode child : node.getChildren()) {
if (child.getTagName().equals("relation")) {
Expand All @@ -157,25 +230,32 @@ private void transformRelation(XNode ret, XNode node) {
if (title.equals("class"))
title = "className";

XNode entityModel = entityNodes.get(refEntityName);
if (entityModel != null) {
refEntityName = entityModel.attrText("name");
} else {
unknownRelations.computeIfAbsent(refEntityName, k -> new ArrayList<>()).add(child);
}
String fullName = resolveRefEntity(refEntityName, externalEntityResolver);

String relType = type.equals("one") || type.equals("one-nopk") ? "to-one" : "to-many";
XNode relation = relations.addChild(relType);

relation.setAttr("name", title);
relation.setAttr("refEntityName", refEntityName);
relation.setAttr("refEntityName", fullName);
if (relType.equals("to-one"))
relation.setAttr("constraint", fkName);
addJoin(relation, child);
}
}
}

private String resolveRefEntity(String refEntityName, Function<String, XNode> externalEntityResolver) {
Pair<XNode, XNode> pair = entityNodes.get(refEntityName);
if (pair != null)
return pair.getSecond().attrText("name");
pair = viewEntityNodes.get(refEntityName);
if (pair != null)
return pair.getSecond().attrText("name");
XNode node = externalEntityResolver.apply(refEntityName);
externalNodes.put(refEntityName, node);
return node.attrText("name");
}

private void addJoin(XNode relation, XNode bizRel) {
XNode join = relation.addChild("join");
for (XNode child : bizRel.getChildren()) {
Expand Down
Loading

0 comments on commit 6013dc9

Please sign in to comment.