-
Notifications
You must be signed in to change notification settings - Fork 10
[UMF][Feature] Relation
Relation 은 from Element 와 to Element 를 필드로 가지면서 엘리먼트들을 linked list, graph 구조로 연결한다. BPMN 에서는 SequenceFlow 가 Relation을 상속하여 만들어졌다.
[Note] 현재의 UMF 에선 Relation 을 하나의 Element 와 관계없는 독립된 클래스로 보고 있기 때문에 Element 를 상속하지 않기 때문에 여러가지 화면 출력과 동작에 대한 중복된 구현들이 많다. Relation을 Element의 한 종류로 보아도 무방하지 않을까?
package org.uengine.modeling;
import org.metaworks.annotation.Hidden;
import java.io.Serializable;
/**
* @author jyj
*/
public class Relation implements Serializable, IRelation{
private static final long serialVersionUID = 1234L;
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
RelationView relationView;
@Hidden
public RelationView getRelationView() {
return relationView;
}
public void setRelationView(RelationView relationView) {
this.relationView = relationView;
}
/**
* sourceElement
*/
transient IElement sourceElement;
@Override
@Hidden
public IElement getSourceElement() {
return this.sourceElement;
}
public void setSourceElement(IElement sourceElement) {
this.sourceElement = sourceElement;
}
/**
* targetElement
*/
transient IElement targetElement;
@Override
@Hidden
public IElement getTargetElement() {
return this.targetElement;
}
public void setTargetElement(IElement targetElement) {
this.targetElement = targetElement;
}
@Override
public RelationView createView() {
RelationView relationView = null;
try {
relationView = (RelationView)Thread.currentThread().getContextClassLoader().loadClass("org.uengine.modeling.RelationView").newInstance();
relationView.setRelation(this);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return relationView;
}
}
RelationView 는 element 와 element 사이를 연결하는 Relation 에 대한 View 이다. 좌표값 및 변곡점 값 등을 서버모델로 가지며, RelationView.ejs.js 에서 OpenGraph 라이브러리와 협력하여 실제 SVG vector 그래픽으로 출력한다. 이 모든 동작 들이 metaworks3 를 통하여 관리된다.
package org.uengine.modeling;
public class RelationView implements Serializable, ContextAware, Cloneable {
private static final long serialVersionUID = 1234L;
public final static String SHAPE_ID = "OG.shape.bpmn.C_Flow";
public final String TERMINAL_IN_OUT = "_TERMINAL_C_INOUT_0";
String id;
@Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
String shapeId;
public String getShapeId() {
return shapeId;
}
public void setShapeId(String shapeId) {
this.shapeId = shapeId;
}
double x; // x 좌표
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
double y; // y 좌표
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
double width; // 폭
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
double height; //높이
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
String from; // OpenGraph 상의 from element id
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public void setFromWithTerminal(String from) {
this.from = from + TERMINAL_IN_OUT;
}
String to; // OpenGraph 상의 to element id
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public void setToWithTerminal(String to) {
this.to = to + TERMINAL_IN_OUT;
}
/**
* contains the way-points
*/
String value; // 변곡점 정보 : 어떻게 저장되는지는... 박승필과장님이 설명좀..
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
String geom; // OpenGraph 상의 정보 문자열 형태.
public String getGeom() {
return geom;
}
public void setGeom(String geom) {
this.geom = geom;
}
String style;
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
String label;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
IRelation relation;
@Face(ejsPath = "dwr/metaworks/genericfaces/HiddenFace.ejs")
public IRelation getRelation() {
return relation;
}
public void setRelation(IRelation relation) {
this.relation = relation;
}
public RelationView() {
setShapeId(SHAPE_ID);
}
public RelationView(IRelation relation) {
this.setRelation(relation);
}
...
@ServiceMethod(callByContent = true, eventBinding = EventContext.EVENT_DBLCLICK, target = ServiceMethodContext.TARGET_POPUP) // 더블클릭되면 선의 연결정보를 팝업하여 편집할 수 있도록 제공한다.
public RelationPropertiesView showProperty() throws Exception {
return new RelationPropertiesView(this);
}
}
RelationView.ejs.js 는 RelationView 를 실제 화면인 OpenGraph 라이브러리를 통하여 동기화 해주는 역할을 한다. 즉, 일반적인 메타웍스 객체가 자신의 ejs 를 통하여 브라우저 dom 에 추가된 후, 거기에 세부요소를 다루게끔 ejs.js 등에서 처리해주는 방식이 아니라, 그냥 OpenGraph 라이브러 자체를 호출해주도록만 만들어졌다. 이유는 opengraph 라이브러리 자체가 dom을 다루는 형식이 아니기 때문이다. 따라서 Canvas 요소를 찾아 거기에 매핑된 opengraph 핸들러를 통하여 직접 그리는 OpenGraph 라이브러리를 호출하는 (function draw()) 대리자 역할만 수행한다. RelationView 는 OpenGraph 내의 SVG 요소를 직접 출력하는 것이 아니라, OpenGraph 요소 하나와 대응되는 가짜 div 객체로 설계되었다. 따라서, RelationView 자체가 메타웍스 객체이기 때문에 화면에 dummy div 가 하나 생기게 된다. metaworks3 의 @ServiceMethod 에 대한 다양한 이벤트 설정들에 의한 처리를 실제 RelationView.java 쪽으로 넘겨주기 위하여 이벤트를 포워드 ( function bindMapping() ) 하는 복잡함을 담고 있으며, OG 에서 제거될때 같이 제거되어야 하는 수고스러움이 리팩토링의 욕구를 자극하고 있다.
var org_uengine_modeling_RelationView = function (objectId, className) {
var faceHelper = this;
this.objectId = objectId;
this.className = className;
this.object = mw3.objects[this.objectId];
this.objectDivId = mw3._getObjectDivId(this.objectId);
this.objectDiv = $(document.getElementById(this.objectDivId));
this.canvas = faceHelper.getCanvas();
this.element = null;
this.metadata = mw3.getMetadata(this.className);
faceHelper.init();
};
org_uengine_modeling_RelationView.prototype = {
getValue: function () {
if (this.element) {
if ($('#' + this.element.id).length == 0)
return {__objectId: this.objectId, __className: this.className};
this.element = document.getElementById(this.element.id);
this.object.label = this.element.shape.label;
this.object.x = this.element.shape.geom.getBoundary().getCentroid().x;
this.object.y = this.element.shape.geom.getBoundary().getCentroid().y;
this.object.width = this.element.shape.geom.getBoundary().getWidth();
this.object.height = this.element.shape.geom.getBoundary().getHeight();
this.object.id = this.element.id;
this.object.shapeId = this.element.shape.SHAPE_ID;
this.object.from = $(this.element).attr('_from');
this.object.to = $(this.element).attr('_to');
var vertices = this.element.shape.geom.getVertices();
this.object.vertices = vertices;
this.object.value = '';
for (var i = 0; i < vertices.length; i++) {
this.object.value = this.object.value + vertices[i];
if (i < vertices.length - 1) {
this.object.value = this.object.value + ','
}
}
this.object.geom = escape(this.element.shape.geom.toString());
this.object.style = escape(OG.JSON.encode(this.element.shape.geom.style));
if (this.object.from != undefined && this.object.to != undefined) {
var temp = this.object.from.split('_');
var fromStr = temp[0] + '_' + temp[1] + '_' + temp[2];
temp = this.object.to.split('_');
var toStr = temp[0] + '_' + temp[1] + '_' + temp[2];
var sourceElementView = mw3.getAutowiredObject('org.uengine.modeling.ElementView@' + fromStr, true);
var targetElementView = mw3.getAutowiredObject('org.uengine.modeling.ElementView@' + toStr, true);
if (sourceElementView != undefined && targetElementView != undefined) {
this.object.relation.sourceElement = sourceElementView.element;
this.object.relation.targetElement = targetElementView.element;
this.object.relation.source = sourceElementView.element.tracingTag;
this.object.relation.target = targetElementView.element.tracingTag;
}
}
}
},
getLabel: function () {
return unescape(this.object.label != null ? this.object.label : '');
},
getCanvas: function () {
var canvas = mw3.getClosestObject(this.objectId, "org.uengine.modeling.Canvas");
if(!canvas) canvas = mw3.getAutowiredObject("org.uengine.modeling.Canvas");
if(!canvas) throw "can't find canvas!";
var canvasId = canvas.__objectId;
var object = mw3.objects[canvasId];
return object.canvas;
},
init: function () {
var existElement = this.canvas.getElementById(this.object.id);
var style = this.object.style;
if (existElement) {
this.element = existElement;
} else if (this.object.from && this.object.to) {
var fromPos = this.object.from.indexOf('_TERMINAL_');
var toPos = this.object.to.indexOf('_TERMINAL_');
var fromElementId = this.object.from.substring(0, fromPos);
var toElementId = this.object.to.substring(0, toPos);
this.readyTargetElement(fromElementId, 'from');
this.readyTargetElement(toElementId, 'to');
} else if (this.object.from && !this.object.to) {
var list = JSON.parse('[' + this.object.value + ']');
var fromto = JSON.stringify(list[0]) + ',' + JSON.stringify(list[list.length - 1]);
var shape = eval('new ' + this.object.shapeId + '(\'' + fromto + '\')');
var geom = unescape(this.object.geom);
if (geom) {
geom = OG.JSON.decode(geom);
if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.POLYLINE]) {
geom = new OG.geometry.PolyLine(geom.vertices);
shape.geom = geom;
} else if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.CURVE]) {
geom = new OG.geometry.Curve(geom.controlPoints);
shape.geom = geom;
}
}
if (this.getLabel()) {
shape.label = this.getLabel();
}
this.element = this.canvas.drawShape(null,
shape,
null,
OG.JSON.decode(unescape(style)),
this.object.id,
null,
null);
if (this.object.from) {
$(this.element).attr('_from', this.object.from);
var pos = this.object.from.indexOf('_TERMINAL_');
this.canvas._RENDERER.redrawShape($('#' + this.object.from.substring(0, pos))[0], null, true);
}
} else if (!this.object.from && this.object.to) {
var list = JSON.parse('[' + this.object.value + ']');
var fromto = JSON.stringify(list[0]) + ',' + JSON.stringify(list[list.length - 1]);
var shape = eval('new ' + this.object.shapeId + '(\'' + fromto + '\')');
var geom = unescape(this.object.geom);
if (geom) {
geom = OG.JSON.decode(geom);
if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.POLYLINE]) {
geom = new OG.geometry.PolyLine(geom.vertices);
shape.geom = geom;
} else if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.CURVE]) {
geom = new OG.geometry.Curve(geom.controlPoints);
shape.geom = geom;
}
}
if (this.getLabel()) {
shape.label = this.getLabel();
}
this.element = this.canvas.drawShape(null,
shape,
null,
OG.JSON.decode(unescape(style)),
this.object.id,
null,
false);
if (this.object.to) {
$(this.element).attr('_to', this.object.to);
var pos = this.object.to.indexOf('_TERMINAL_');
this.canvas._RENDERER.redrawShape($('#' + this.object.to.substring(0, pos))[0], null, true);
}
} else {
var shape = eval('new ' + this.object.shapeId);
if (this.getLabel()) {
shape.label = this.getLabel();
}
this.element = this.canvas.drawShape([this.object.x, this.object.y],
shape,
[parseInt(this.object.width, 10), parseInt(this.object.height, 10)],
OG.JSON.decode(unescape(style)),
this.object.id,
null,
null);
this.object[this.metadata.keyFieldDescriptor.name] = this.element.id;
mw3.putObjectIdKeyMapping(this.objectId, this.object, true);
}
this.bindMapping();
},
bindMapping: function () {
var metadata = mw3.getMetadata(this.className);
for (var methodName in metadata.serviceMethodContextMap) {
var methodContext = metadata.serviceMethodContextMap[methodName];
if (methodContext.eventBinding) {
for (var eventNameIndex in methodContext.eventBinding) {
var eventName = methodContext.eventBinding[eventNameIndex];
this.bind(eventName);
}
}
}
},
bind: function (name) {
$(this.element).bind(name + '.' + this.objectId, {objectId: this.objectId}, function (event, ui) {
$('#' + mw3._getObjectDivId(event.data.objectId)).trigger(event.type);
});
},
destroy: function () {
$(this.element).unbind('.' + this.objectId);
},
readyTargetElement: function (id, fieldName) {
if ($('#' + id).length) {
this[fieldName] = true;
this.draw();
} else {
$(this.canvas._CONTAINER).one('loaded.' + id, {
updateFieldName: fieldName,
objectId: this.objectId
}, function (event) {
var faceHelper = mw3.getFaceHelper(event.data.objectId);
faceHelper[fieldName] = true;
faceHelper.draw();
});
}
},
draw: function () {
var style = this.object.style;
var fromPos = this.object.from.indexOf('_TERMINAL_');
var toPos = this.object.to.indexOf('_TERMINAL_');
var fromElementId = this.object.from.substring(0, fromPos);
var toElementId = this.object.to.substring(0, toPos);
var existElement = this.canvas.getElementById(this.object.id);
if (!existElement) {
if ($('#' + fromElementId).length && $('#' + toElementId).length) {
var geom;
if (this.object.geom) {
geom = OG.JSON.decode(unescape(this.object.geom));
}
this.element = this.canvas.connectWithTerminalId(
this.object.from, this.object.to, OG.JSON.decode(unescape(style)),
this.getLabel(), this.object.id, this.object.shapeId, geom);
this.bindMapping();
if (this.object.needReconnect && this.element) {
this.object.needReconnect = false;
this.canvas.reconnect(this.element);
}
}
}
}
};
한번 RelationView 의 설정창이 열린 이후에 설정창이 다시 열리지 않는 오류가 발생함. OpenGraph 가 요소에 대한 label 편집 기능을 위하여 더블클릭 이벤트를 가로채서 stopImmediateEvent 를 해버리기 때문임. label 편집 기능을 옵션을 혹은 override 하여 잠시 죽여두는것이 방법일듯..
...
OG.handler.EventHandler.prototype = {
enableEditLabel: function (element) {
//disabling label editing
}
};