package data { import events.LoginEvent; import flash.events.Event; import flash.events.EventDispatcher; import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLRequestHeader; import flash.net.URLRequestMethod; import mx.collections.IViewCursor; import mx.collections.XMLListCollection; import mx.controls.Alert; import mx.events.CollectionEvent; import mx.events.PropertyChangeEvent; [Event(name="login", type="events.LoginEvent")] public class AbstractDataSource extends EventDispatcher { private var _hasChanges:Boolean; private var _login:String; private var configUrl:String = "config.xml"; private var config:XML; private var propertiesUrl:String = "properties.xml"; public var properties:XML; private var defaultConfigUrl:String = "defaultGUIConfig.xml"; public var defaultConfig:XML; public var entities:Array; public var trees:Array; private var _proxyServerUrl:String; private var lastClientId:int = 0; public function get proxyServerUrl() : String { return _proxyServerUrl; } public function get author() : String { return _login } public function AbstractDataSource() { var configLoader:URLLoader = new URLLoader(); configLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { config = XML(configLoader.data); _proxyServerUrl = XML(configLoader.data).proxyServer.toString(); }); configLoader.load(new URLRequest(configUrl)); var propertiesLoader:URLLoader = new URLLoader(); propertiesLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { properties = XML(propertiesLoader.data); }); propertiesLoader.load(new URLRequest(propertiesUrl)); var defaultConfigLoader:URLLoader = new URLLoader(); defaultConfigLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { defaultConfig = XML(defaultConfigLoader.data); }); defaultConfigLoader.load(new URLRequest(defaultConfigUrl)); } public function get hasChanges() : Boolean { return _hasChanges; } public function login() : void { var authorLoader:URLLoader = new URLLoader(); authorLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { var authorResponse:XML = XML(authorLoader.data); _login = authorResponse.Name.toString(); removeChangeHandlers(); var initialDataLoader:URLLoader = new URLLoader(); initialDataLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { var dataResponse:XML = XML(initialDataLoader.data); parseLoadingResponse(dataResponse); buildTrees(); addChangeHandlers(); dispatchEvent(new LoginEvent(author)); }); var dataRequestUrl:String = config.server + "request=GetEntities&brief=true"; initialDataLoader.load(new URLRequest(dataRequestUrl)); }); var requestUrl:String = config.server + "request=GetAuthor"; authorLoader.load(new URLRequest(requestUrl)); } private function emptyCollections(collections:Array) : void { for each(var obj:Object in collections) { if(obj is DataElement) { DataElement(obj).collection.disableCollectionEvent = true; DataElement(obj).collection.removeAll(); DataElement(obj).collection.disableCollectionEvent = false; } } } public function logout() : void { if(entities) { emptyCollections(entities); } if(trees) { for each(var obj:Object in trees) { if(obj is Tree) { Tree(obj).cleanup(); } } } } private function getGroupName(elementName:Object) : Object { if(elementName is QName) { var qname:QName = QName(elementName); return new QName(qname.uri, qname.localName + "s"); } else { return elementName.toString() + "s"; } } private function addItemsToTransaction(entityElement:XML, itemList:XMLList) : void { for each(var item:XML in itemList) { var entity:Entity = getEntity(item.name()); if(entity.refs) { item = item.copy(); for each(var obj:Object in entity.refs) { if(obj is EntityRef) { var entityRef:EntityRef = EntityRef(obj); var group:XML = item.child(entityRef.elementGroupName)[0]; if(group != null) { group.setChildren(group.children().(attribute("status").length() == 0 || @status != "deleted")); } } } } entityElement.appendChild(item); } } protected function createTransaction() : XML { var transaction:XML = ; if(entities) { for each(var obj:Object in entities) { if(obj is Entity) { var entity:Entity = Entity(obj); var groupName:Object = getGroupName(entity.elementName); var changed:XMLList = entity.collection.source.(@status == 'changed'); if(changed.length() > 0) { var updateList:XMLList = transaction.child("Update"); if(updateList.length() == 0) { transaction.appendChild(); updateList = transaction.Update; } var entityElement:XML = <{groupName}/>; updateList[0].appendChild(entityElement); addItemsToTransaction(entityElement, changed); } var deleted:XMLList = entity.collection.source.(@status == "deleted" && @clientId == "false"); if(deleted.length() > 0) { var deleteList:XMLList = transaction.child("Delete"); if(deleteList.length() == 0) { transaction.appendChild(); deleteList = transaction.Delete; } entityElement = <{groupName}/>; deleteList[0].appendChild(entityElement); entityElement.setChildren(deleted); } var neww:XMLList = entity.collection.source.(@status == "new"); if(neww.length() > 0) { var insertList:XMLList = transaction.child("Insert"); if(insertList.length() == 0) { transaction.appendChild(); insertList = transaction.Insert; } entityElement = <{groupName}/>; insertList[0].appendChild(entityElement); addItemsToTransaction(entityElement, neww); } } } } return transaction; } private function performTransaction(transaction:XML) : void { var doTransactionLoader:URLLoader = new URLLoader(); doTransactionLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { var response:XML = XML(doTransactionLoader.data); if(response.localName() == "Exception") { Alert.show(response.Message.toString()); } else { processTransactionResponse(response); // Show alert Alert.show("Inserted : " + response.Inserted.@count + "\n" + "Updated : " + response.Updated.@count + "\n" + "Deleted : " + response.Deleted.@count); } }); var request:URLRequest = new URLRequest(config.server + "request=DoTransaction"); request.data = "" + transaction.toXMLString(); request.method = URLRequestMethod.POST; request.contentType = "text/xml; charset=utf-8" request.requestHeaders.push(new URLRequestHeader("Content-Encoding", "UTF-8")); doTransactionLoader.load(request); } public function save() : void { var transaction:XML = createTransaction(); if(transaction.length() > 0) { var briefItems:XMLList = transaction.descendants().(attribute("brief").length() > 0 && @brief == "true"); if(briefItems.length() == 0) { performTransaction(transaction); } else { var entities:Array = new Array(); for each(var item:XML in briefItems) { var name:Object = item.localName(); var entity:Object = null; for(var i:uint = 0; i < entities.length; i++) { if(entities[i].name == name) { entity = entities[i]; break; } } if(!entity) { entity = {name: item.localName(), ids: new Array()}; entities.push(entity); } entity.ids.push(item.@id); } getEntities(entities, true); } } } protected function getMissingRefs(refs:XMLList, elementName:Object) : XMLList { var collection:XMLListCollection = getCollection(elementName); var retval:XMLList = new XMLList(); for(var i:int = 0; i < collection.length; i++) { var item:XML = XML(collection.getItemAt(i)); if(refs.(@id == item.@id && @clientId == item.@clientId).length() == 0) { retval += item; } } return retval; } public function getUnreferredItems(parent:XML, elementName:Object) : XMLList { var props:XML = getProps(parent); var refs:XMLList = props.descendants(elementName); return getMissingRefs(refs, elementName); } public function getRef(node:XML, status:String = null) : XML { var retval:XML = <{node.name()}/>; retval.@clientId = node.@clientId; retval.@id = node.@id; retval.@status = status == null ? node.@status : status; return retval; } private function getEntities(entities:Array, saveOnReceive:Boolean = false) : void { var incrementalDataLoader:URLLoader = new URLLoader(); incrementalDataLoader.addEventListener(Event.COMPLETE, function(event:Event) : void { var dataResponse:XML = XML(incrementalDataLoader.data); removeChangeHandlers(); parseLoadingResponse(dataResponse, true); addChangeHandlers(); if(saveOnReceive) { save(); } }); var dataRequestUrl:String = config.server + "request=GetEntities&brief=false&"; for(var i:uint = 0; i < entities.length; i++) { var entity:Entity = getEntity(entities[i].name); if(i > 0) { dataRequestUrl += "&"; } dataRequestUrl += entity.requestName; dataRequestUrl += "="; for(var j:uint = 0; j < entities[i].ids.length; j++) { if(j > 0) { dataRequestUrl += ","; } dataRequestUrl += entities[i].ids[j]; } } incrementalDataLoader.load(new URLRequest(dataRequestUrl)); } public function getUsageProps(node:XML) : XML { var entity:Entity = getEntity(node.name()); return entity.collection.source.(compareUsageRef(valueOf(), node))[0]; } public function getProps(node:XML, acceptBrief:Boolean = true) : XML { if(!node) { return null; } var collection:XMLListCollection = getCollectionForNode(node); if(collection) { var list:XMLList = collection.source.(@clientId == node.@clientId && @id == node.@id); if(list != null && list.length() > 0) { var retval:XML = list[0]; if(retval.@brief == "true" && !acceptBrief) { getEntities([{name: retval.localName(), ids: [node.@id]}]); } return retval; } } return null; } public function isOwner(item:XML):Boolean { var props:XML = item.child("Owner").length() == 0 ? getProps(item)[0] : item; if(props != null && props.Owner.length() > 0) { return props.Owner == _login; } return false; } public function isUnique(type:Object, name:String, newElement:Boolean = false):Boolean { var count:Number = newElement ? 0 : 1; var entity:Entity = getEntity(type); if(entity) { return entity.collection.source.(Name == name).length() == count; } // Unknown entity -> no unique check to perform return true; } public function deleteItem(item:XML) : void { var props:XML = getProps(item); props.@status = "deleted"; if(trees) { for each(var obj:Object in trees) { if(obj is Tree) { var tree:Tree = Tree(obj); for each(var ref:XML in tree.collection.source.descendants(item.localName()).( attribute("clientId").length() > 0 && @clientId == props.@clientId && @id == props.@id)) { //removed: caused problems //ref.setChildren(new XMLList()); ref.@status = "deleted"; } } } } var entity:Entity = getEntity(item.name()); if(entity) { if(entity.refs) { for each(var refObj:Object in entity.refs) { if(refObj is EntityRef) { var entityRef:EntityRef = refObj as EntityRef; var refs:XMLList = props.child(entityRef.elementGroupName).children(); for each(var currentRef:XML in refs) { var refProps:XML = getProps(currentRef); if(refProps.attribute("useCount").length() > 0) { refProps.@useCount--; } } } } } } if(entities) { for each(obj in entities) { if(obj is Entity) { entity = obj as Entity; if(entity.refs) { for each(refObj in entity.refs) { if(refObj is EntityRef) { entityRef = refObj as EntityRef; if(entityRef.elementName == item.name()) { var list:XMLList = entity.collection.source.child(entityRef.elementGroupName) .children(); for each(currentRef in list.(@id == item.@id && @clientId == item.@clientId)) { currentRef.@status = "deleted"; } } } } } } } } } protected function getEntityRef(item:XML, parent:XML) : EntityRef { var entity:Entity = getEntity(parent.name()); if(entity.refs) { for each(var currentRef:Object in entity.refs) { if(EntityRef(currentRef).elementName == item.name()) { return EntityRef(currentRef); } } } return null; } protected function compareUsageRef(a:XML, b:XML) : Boolean { if(a.localName() != b.localName()) { return false; } return a.Name == b.Name; } public function removeRef(item:XML, parent:XML) : void { var itemProps:XML = getProps(item); var props:XML = getProps(parent); // Remove usage var refList:XMLList = itemProps.Usage.children().(compareUsageRef(valueOf(), props)); var collection:ExtendedXMLListCollection = getEntity(item.name()).collection; collection.disableCollectionEvent = true; for each(var refItem:XML in refList) { refItem.@status = "deleted"; } collection.disableCollectionEvent = false; itemProps.@useCount = itemProps.Usage.children().(attribute("status").length() == 0 || @status != "deleted").length(); // Remove reference var entityRef:EntityRef = getEntityRef(item, parent); if(entityRef) { var refs:XMLList = props.child(entityRef.elementGroupName) .children().(@id == item.@id && @clientId == item.@clientId); for each(var currentItem:XML in refs) { refs.@status = "deleted"; } if(props.@status != 'new') { props.@status = 'changed'; } } } public function moveRef(item:XML, oldParent:XML, newParent:XML, children:XMLList) : void { var oldParentProps:XML = getProps(oldParent); var newParentProps:XML = getProps(newParent); var itemProps:XML = getProps(item); var entityRef:EntityRef = getEntityRef(item, newParent); if(!children || children.length() == 0) { var group:XML = newParentProps.child(entityRef.elementGroupName)[0]; group.appendChild(item); } // disable events for each(var obj:Object in entities) { if(obj is Entity) { Entity(obj).collection.disableCollectionEvent = true; } } if(entityRef && children && children.length() > 0) { group = newParentProps.child(entityRef.elementGroupName)[0]; var newChildren:XMLList = new XMLList(); for each(var currentItem:XML in children) { newChildren += getRef(currentItem); } group.setChildren(newChildren); } if(oldParent != newParent) { entityRef = getEntityRef(item, oldParent); if(entityRef) { group = oldParentProps.child(entityRef.elementGroupName)[0]; group.setChildren(group.children().(!(@id == item.@id && @clientId == item.@clientId))); } } // update usage if(itemProps.child("Usage").length() > 0) { itemProps.Usage[0].setChildren(itemProps.Usage.(!compareUsageRef(valueOf(), oldParent))); } else { itemProps.appendChild(); } itemProps.Usage[0].appendChild(getUsageRef(newParent)); // enable events for each(obj in entities) { if(obj is Entity) { Entity(obj).collection.disableCollectionEvent = false; } } if(newParentProps.@status != "new") { newParentProps.@status = "changed"; } if(oldParentProps != newParent && oldParentProps.@status != "new") { oldParentProps.@status = "changed"; } } protected function getUsageRef(item:XML, newItem:Boolean = false) : XML { var retval:XML = item.copy(); retval.setChildren(new XMLList()); if(retval.attribute("brief").length() > 0) { delete retval.@brief; } if(!newItem) { item = getProps(item); } retval.Owner = item.Owner; if(item.child("Name").length() > 0) { retval.Name = item.Name; } if(item.child("Titles").length() > 0 ) { retval.Titles = item.Titles; } return retval; } public function addRef(item:XML, parent:XML) : void { var parentProps:XML = getProps(parent); var itemProps:XML = getProps(item); var itemCollection:ExtendedXMLListCollection = getEntity(item.name()).collection; itemCollection.disableCollectionEvent = true; // Add usage if(itemProps.child("Usage").length() == 0) { itemProps.appendChild(); } itemProps.Usage[0].appendChild(getUsageRef(parentProps)); itemCollection.disableCollectionEvent = false; itemProps.@useCount++; // Add reference var entityRef:EntityRef = getEntityRef(itemProps, parent); if(entityRef) { var group:XML = parentProps.child(entityRef.elementGroupName)[0]; group.appendChild(getRef(item, "new")); if(parentProps.@status != 'new') { parentProps.@status = 'changed'; } } } protected function createUniqueItem(item:XML) : void { var entity:Entity = getEntity(item.name()); while(entity.collection.source.(Name == item.Name).length() > 0) { item.Name = item.Name + "_copy"; } } public function duplicateItem(item:XML) : XML { item = getProps(item).copy(); item.@status = "new"; item.@useCount = 0; if(item.child("Usage").length() > 0) { item.Usage[0].setChildren(new XMLList()); } createUniqueItem(item); var itemRef:XML = addItem(item); // recreate references (to force consistency between references and usage references) var entity:Entity = getEntity(item.name()); if(entity.refs) { for each(var refObj:Object in entity.refs) { if(refObj is EntityRef) { var ref:EntityRef = refObj as EntityRef; var group:XML = item.child(ref.elementGroupName)[0]; var refList:XMLList = group.children().copy(); group.setChildren(new XMLList()); for each(var currentRef:XML in refList) { addRef(currentRef, item); } } } } return itemRef; } public function addItem(item:XML) : XML { item.@clientId = true; item.@id = lastClientId++; var collection:XMLListCollection = getCollectionForNode(item) if(collection) { collection.addItem(item); } return getRef(item); } protected function addChangeHandlers() : void { if(!entities) { return; } for each(var obj:Object in entities) { if(obj is DataElement) { DataElement(obj).collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, changeHandler); } } } protected function removeChangeHandlers() : void { if(!entities) { return; } for each(var obj:Object in entities) { if(obj is DataElement) { DataElement(obj).collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, changeHandler); } } } protected function changeHandler(event:CollectionEvent) : void { if(event.kind == "update") { for each(var item:Object in event.items) { if(item is PropertyChangeEvent) { var changeEvent:PropertyChangeEvent = PropertyChangeEvent(item); if(changeEvent.property != "@status" && changeEvent.property != "@useCount") { //if(changeEvent.newValue != changeEvent.oldValue) { var status:String = changeEvent.source.@status; if(status != "new") { changeEvent.source.@status = "changed" } //} } else { if(trees != null) { for each(var obj:Object in trees) { if(obj is Tree) { var tree:Tree = Tree(obj); var node:XML = XML(changeEvent.source); for each(var ref:XML in tree.collection.source.descendants( node.name()).(@id == node.@id && @clientId == node.@clientId)) { ref.@status = node.@status; } } } } } } } } } protected function getEntity(name:Object) : Entity { if(entities) { for each(var obj:Object in entities) { if(obj is Entity) { var entity:Entity = Entity(obj); if(entity.elementName == name) { return entity; } } } } return null; } protected function getCollection(name:Object) : XMLListCollection { var entity:Entity = getEntity(name); if(entity) { return entity.collection; } return null; } protected function getCollectionForNode(node:XML) : XMLListCollection { return getCollection(node.name()); } protected function buildTrees() : void { if(trees != null) { for each(var obj:Object in trees) { if(obj is Tree) { var tree:Tree = Tree(obj); tree.buildTree(); } } } } protected function setAllDefaultAttributes(dataResponse:XML) : void { if(entities != null) { for each(var obj:Object in entities) { if(obj is Entity) { var entity:Entity = Entity(obj); var entities:XMLList = dataResponse.descendants(entity.elementName); for each(var node:XML in entities) { setDefaultAttributes(node); } } } } } protected function setDefaultAttributes(node:XML) : void { node.@clientId = "false"; node.@status = "existing"; } protected function processTransactionResponse(dataResponse:XML) : void { var clientIdNodes:XMLList = new XMLList(); if(trees) { for each(var obj:Object in trees) { if(obj is Tree) { var tree:Tree = Tree(obj); clientIdNodes += tree.collection.source.descendants().( attribute("clientId").length() > 0 && @clientId == "true"); } } } for each(var insertNode:XML in dataResponse.Inserted.children()) { var localName:Object = insertNode.localName(); var id:String = insertNode.@id; var clientId:String = insertNode.@clientId; var props:XML = getProps(<{localName} clientId='true' id={clientId}/>); props.@clientId = false; props.@id = id; props.@status = "existing"; for each(item in clientIdNodes.(copy().localName() == localName && @id == clientId)) { item.@clientId = false; item.@id = id; item.@status = "existing"; } } if(entities) { for each(obj in entities) { if(obj is Entity) { var entity:Entity = Entity(obj); var cursor:IViewCursor = entity.collection.createCursor(); while (!cursor.afterLast) { var current:XML = XML(cursor.current); if(current.child("Usage").length() > 0) { entity.collection.disableCollectionEvent = true; current.Usage[0].setChildren(current.Usage.children().( attribute("status").length() == 0 || @status != "deleted")); entity.collection.disableCollectionEvent = false; } status = current.@status; if(status == "deleted") { cursor.remove(); } else { if(status != "existing") { // prevents unnecessary update events if(entity.refs) { for each(var refObj:Object in entity.refs) { if(refObj is EntityRef) { var entityRef:EntityRef = EntityRef(refObj); var group:XML = current.child(entityRef.elementGroupName)[0]; if(group != null) { entity.collection.disableCollectionEvent = true; group.setChildren(group.children().(attribute("status").length() == 0 || @status != "deleted")); for each(var ref:XML in group.descendants() .(attribute("clientId").length() > 0 && @clientId == "true")) { ref.@clientId = false; ref.@id = dataResponse.Inserted.descendants(ref.name()) .(@clientId = ref.@id)[0].@id; } entity.collection.disableCollectionEvent = false; } } } } current.@status = "existing"; } cursor.moveNext(); } } } } } if(trees) { for each(obj in trees) { if(obj is Tree) { tree = Tree(obj); for each(var item:XML in tree.collection.source.descendants().( attribute("status").length() > 0 && @status != "existing")) { var status:String = item.@status; switch(status) { case "new": case "changed": item.@status = "existing"; default: } } for each(item in tree.collection.source.descendants().( children().(attribute("status").length() > 0 && @status == "deleted").length() > 0)) { item.setChildren(item.children().(@status != "deleted")); } } } } } protected function addNode(node:XML, collection:XMLListCollection) : void { collection.addItem(node); } protected function parseLoadingResponse(dataResponse:XML, incremental:Boolean = false, undo:Boolean = false) : void { setAllDefaultAttributes(dataResponse); if(entities != null) { for each(var obj:Object in entities) { if(obj is Entity) { var entity:Entity = Entity(obj); var entities:XMLList = dataResponse.children().child(entity.elementName); for each(var node:XML in entities) { if(incremental) { var props:XML = getProps(node); if(undo) { props.setChildren(node.children()); } else { var newChildren:XMLList = new XMLList(); for each(var child:XML in node.children()) { var currentChild:XMLList = props.child(child.localName()); if(currentChild.length() == 0) { newChildren += child; } else { // Copy existing Usage refs if(child.name() == "Usage") { child = child.copy(); for each(var usageRef:XML in currentChild.children()) { child.appendChild(usageRef); } newChildren += child; } else { newChildren += currentChild; } } } props.setChildren(newChildren); } props.@brief = false; } else { addNode(node, entity.collection); } } } } } } } }