| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- #!/usr/bin/env python
- API_VERSION = 3
- # Backend for the 'dtdesign' drawio plugin.
- # Offers a repository to store/load drawio diagrams, and conversion to OML, and updating Fuseki with newly generated triples.
- # python standard library:
- import os
- import io
- import sys
- import json
- import tempfile
- import xml.etree.ElementTree as ET
- import subprocess
- import argparse
- # packages from pypi:
- import flask
- import flask_cors
- # our own packages:
- from drawio2py.parser import Parser as drawio_parser
- from drawio2py.generator import generate_diagram
- import drawio2py.util
- from drawio2oml.convert import page_to_oml
- from drawio2oml.bundle.oml_generator import write_bundle
- from drawio2oml import util
- from xopp2py import parser as xopp_parser
- from xopp2oml import writer as xopp_oml_writer
- from drawio2oml.pt import fetcher as pt_fetcher
- from drawio2oml.pt import renderer as pt_renderer
- argparser = argparse.ArgumentParser(
- description = "Backend for DTDesign drawio plugin.")
- argparser.add_argument('projectdir', help="Path to SystemDesignOntology2Layers OML project directory (containing \"build.gradle\"). Example: /home/maestro/repos/dtdesign/examples/oml/SystemDesignOntology2Layers")
- argparser.add_argument('shapelibdir', help="Path to where the Draw.io shape libraries (bunch of XML files) for FTG, PM and PT are at. Necessary for rendering process traces. Example: /home/maestro/repos/drawio/src/main/webapp/myPlugins/shape_libs")
- args = argparser.parse_args() # exits on error
- WEE = "http://localhost:8081"
- WEE_FETCHER = pt_fetcher.Fetcher(WEE)
- # See below
- DTDESIGN_PROJECT_DIR = args.projectdir
- SHAPE_LIB_DIR = args.shapelibdir
- # Quick check to see if the projectdir argument is valid:
- if not os.path.isfile(DTDESIGN_PROJECT_DIR + "/build.gradle"):
- sys.exit("Invalid projectdir: " + DTDESIGN_PROJECT_DIR)
- # Quick check to see if the shapelibdir argument is valid:
- if not os.path.isfile(SHAPE_LIB_DIR + "/pt.xml"):
- sys.exit("Invalid shapelibdir: " + SHAPE_LIB_DIR)
- OML_DESCRIPTION_DIR = DTDESIGN_PROJECT_DIR + "/src/oml/ua.be/sdo2l/description"
- OML_ARTIFACT_DIR = OML_DESCRIPTION_DIR + "/artifacts"
- FILES_DIR = DTDESIGN_PROJECT_DIR + "/src/oml/ua.be/sdo2l/files"
- DRAWIO_DIR = FILES_DIR + "/drawio"
- XOPP_DIR = FILES_DIR + "/xopp"
- CSV_DIR = FILES_DIR + "/csv"
- ANYFILE_DIR = FILES_DIR + "/file"
- print()
- print(" ",DTDESIGN_PROJECT_DIR)
- print(" │")
- print(" ├ description/")
- print(" │ │")
- print(" │ ├ bundle.oml")
- print(" │ │ ↳ re-generated before every oml build")
- print(" │ │")
- print(" │ └ artifacts/")
- print(" │ ↳ generated OML artifacts will be put here")
- print(" │")
- print(" └ files/")
- print(" │")
- print(" ├ drawio/")
- print(" │ ↳ Drawio diagrams (XML) will be put here")
- print(" │")
- print(" ├ xopp/")
- print(" │ ↳ Xournal++ (Gzipped XML) will be put here")
- print(" │")
- print(" └ csv/")
- print(" ↳ CSV files will be put here")
- print()
- # Create directories if they don't exist yet:
- for d in [DRAWIO_DIR, XOPP_DIR, CSV_DIR, ANYFILE_DIR]:
- os.makedirs(d, exist_ok=True)
- app = flask.Flask(__name__)
- flask_cors.CORS(app)
- # API version
- @app.route("/version", methods=["GET"])
- def version():
- return json.dumps(API_VERSION), 200
- @app.route("/files/<path:filepath>", methods=["GET"])
- def files_ls(filepath):
- if "/../" in filepath or filepath[:3] == "../" or filepath[-3:] == "/..":
- # Security measure, not allowed to break out of /files/ directory:
- return "Illegal path, '..' directory traveral not allowed", 403
- if os.path.isdir(FILES_DIR + '/' + filepath):
- # Send JSON array of filenames of files in directory:
- return flask.Response(json.dumps(os.listdir(FILES_DIR + '/' + filepath)))
- else:
- # Send file contents:
- return flask.send_file(FILES_DIR + '/' + filepath, as_attachment=True)
- @app.route("/files/drawio/<model_name>", methods=["PUT"])
- def put_drawio_file(model_name):
- diagram = flask.request.data # the serialized XML of a drawio page.
- # Parse XML/drawio and call the right drawio->DSL parser (e.g., FTG, PM, ...):
- tree = ET.fromstring(diagram)
- page = drawio_parser.parse_page(tree)
- try:
- parsed_as = page_to_oml(page=page, outdir=OML_ARTIFACT_DIR, input_uri=flask.request.url)
- except Exception as e:
- return str(e), 500
- # If the above was successful, store the serialized XML:
- with open(DRAWIO_DIR+'/'+model_name, 'wb') as f:
- f.write(diagram)
- return json.dumps(parsed_as), 200 # OK
- # Even though the slashes in pt_iri will be escaped, we still have to tell Flask it's a 'path' or it won't accept it.
- @app.route("/render_pt/<path:pt_iri>", methods=["GET"])
- def render_pt(pt_iri):
- last_event = WEE_FETCHER.get_pt(pt_iri)
- id_gen = drawio2py.util.DrawioIDGenerator()
- page = drawio2py.util.generate_empty_page(id_gen, "XXX")
- default_layer = page.root.children[0]
- pt_renderer.render(SHAPE_LIB_DIR, last_event, parent=default_layer, id_gen=id_gen)
- diagram_node = generate_diagram(page)
- return ET.tostring(diagram_node, encoding="unicode"), 200 # OK
- @app.route("/files/xopp/<file_name>", methods=["PUT"])
- def put_xopp_file(file_name):
- if file_name[-5:] != ".xopp":
- return "File extension must be .xopp", 500
- model_name = file_name[:-5]
- xopp_data = flask.request.data # the file contents (gzipped XML)
- # Convert to OML and store the OML:
- try:
- xopp_as = xopp_parser.parseFile(io.BytesIO(initial_bytes=xopp_data))
- with open(OML_ARTIFACT_DIR+'/'+model_name+"_xopp.oml", 'wt') as f:
- xopp_oml_writer.writeOML(xopp_as,
- inputfile=flask.request.url,
- model_name=model_name,
- namespaces=util.DTDESIGN_NAMESPACES, # use the namespaces from this repo
- ostream=f)
- except Exception as e:
- return str(e), 500
- # If the above was successful, store the raw XOPP file:
- with open(XOPP_DIR+'/'+model_name, 'wb') as f:
- f.write(xopp_data)
- return json.dumps(["xopp"]), 200 # OK
- @app.route("/files/csv/<model_name>", methods=["PUT"])
- def put_csv_file(model_name):
- csv_data = flask.request.data # the file contents (gzipped XML)
- # We don't convert CSV to OML...
- # (TODO) But we probably store *something* about the CSV in OML?
- # Store the raw XOPP file:
- with open(CSV_DIR+'/'+model_name, 'wb') as f:
- f.write(csv_data)
- return json.dumps(["csv"]), 200 # OK
- @app.route("/files/file/<model_name>", methods=["PUT"])
- def put_any_file(model_name):
- file_data = flask.request.data # the file contents (gzipped XML)
- # (TODO) We probably should store *something* about the file in OML?
- # Store the raw file:
- with open(ANYFILE_DIR+'/'+model_name, 'wb') as f:
- f.write(file_data)
- return json.dumps(["file"]), 200 # OK
- # TODO: this endpoint should queue rebuilds (using Futures?), such that only one rebuild is running at a time.
- @app.route("/rebuild_oml", methods=["PUT"])
- def rebuild_oml():
- with open(OML_DESCRIPTION_DIR+'/bundle.oml', 'wt') as f:
- write_bundle(OML_DESCRIPTION_DIR, f)
- # owlLoad builds OML and makes Fuseki load the new triples
- owlLoad = subprocess.run(["gradle", "owlLoad"], cwd=DTDESIGN_PROJECT_DIR,
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # combine both streams into one
- text=True,
- )
- if owlLoad.returncode != 0:
- return owlLoad.stdout, 500 # Return all output of the failed owlLoad
- else:
- return owlLoad.stdout, 200 # OK
- app.run(host="0.0.0.0")
|