diff --git a/CSV.py b/CSV.py new file mode 100644 index 0000000..c3653a5 --- /dev/null +++ b/CSV.py @@ -0,0 +1,36 @@ +import json, csv, os, time, stomp +from datetime import datetime +from flask_backend.Stomp_client import get_connection + +QUEUE_NAME = "/queue/testQueue" +OUTPUT_DIR = "/app/output" +os.makedirs(OUTPUT_DIR, exist_ok=True) + +class MyListener(stomp.ConnectionListener): + def on_message(self, frame): + try: + data = json.loads(frame.body) + except json.JSONDecodeError: + print("Received invalid JSON:", frame.body) + return + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = os.path.join(OUTPUT_DIR, f"message_{timestamp}.csv") + + try: + with open(filename, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(data.keys()) + writer.writerow(data.values()) + print(f"Saved CSV: {filename}") + except Exception as e: + print("Error writing CSV:", e) + +# Připojení k ActiveMQ s listenerem +listener = MyListener() +conn = get_connection(listener=listener) +conn.subscribe(destination=QUEUE_NAME, id=1, ack="auto") + +# keep process alive +while True: + time.sleep(1) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..17a6ac7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . \ No newline at end of file diff --git a/README.md b/README.md index 0017505..76a182e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ # Titeto-WSO2-ActiveMQ +## WSO2 API Manager +3. Vytvor jednoduche rest api v WSO2 API manageru s resource POST. + + +## Flask +4. Vytvor Python script ktery bude fungovat jako rest API, a jeho endpoint pouzij v API manageru. + +5. Rest API bude prijmat XML jako input_payload. + +6. Rest API tento payload odesle na backend do python scriptu. + +## Sender.py +7. Vytvor JSON payload s mappingem viz. attachment a priklady output payloadu. + +8. Script posle payload jako zpravu do vytvorene Q v ActiveMQ. + +9. Posli vyslednou response s HTTP-SC. + +## Active MQ + +## CSV.py + +10. Vytvor dalsi script ktery bude naslouchat na vytvorenou Q. + +11. Vytvor CSV file dle prikladu viz. attachment. + +12. Uloz CSV s filename viz. diagram s aktualni timestampou, do libovolne slozky na lokalu. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..82604c8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,67 @@ +version: "3.8" + +services: + # 🐍 Flask REST API + flask_backend: + build: . + container_name: flask_backend + ports: + - "5000:5000" + networks: + - internal + command: ["python", "flask_backend/app.py"] + depends_on: + - activemq + + + # 📨 ActiveMQ + activemq: + image: rmohr/activemq:5.15.9 + container_name: activemq + ports: + - "61613:61613" # STOMP port (pro komunikaci s Pythonem) + - "8161:8161" # Web UI: http://localhost:8161/admin + environment: + ACTIVEMQ_ADMIN_LOGIN: admin + ACTIVEMQ_ADMIN_PASSWORD: admin + restart: always + networks: + - internal + + + receiving_consumer: + build: . + container_name: CSV_python + depends_on: + - activemq + environment: + MQ_HOST: activemq + MQ_PORT: 61613 + MQ_USER: admin + MQ_PASS: admin + command: ["python", "CSV.py"] + volumes: + - ./output:/app/output # ✅ CSV soubory se uloží do ./output na tvém disku + restart: on-failure + networks: + - internal + + # odešle na Flask API testovací XML zprávu + wso2apim: + image: wso2/wso2am:3.2.0 + container_name: wso2apim + volumes: + - ./wso2/deployment.toml:/home/wso2carbon/wso2am-3.2.0/repository/conf/deployment.toml + - ./data/activemq:/opt/activemq/data + ports: + - "9443:9443" # HTTPS Management Console + - "8280:8280" # HTTP Gateway + - "8243:8243" # HTTPS Gateway + environment: + - JAVA_OPTS=-Duser.timezone=UTC + networks: + - internal + +networks: + internal: + driver: bridge \ No newline at end of file diff --git a/flask_backend/Dockerfile b/flask_backend/Dockerfile new file mode 100644 index 0000000..8e898f3 --- /dev/null +++ b/flask_backend/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY ../requirements.txt . + +RUN pip install --no-cache-dir -r ../requirements.txt + +COPY . . \ No newline at end of file diff --git a/flask_backend/Sender.py b/flask_backend/Sender.py new file mode 100644 index 0000000..20d8beb --- /dev/null +++ b/flask_backend/Sender.py @@ -0,0 +1,14 @@ +import json +from Stomp_client import get_connection + +QUEUE_NAME = "/queue/testQueue" + +def send_to_queue(payload: dict): + """ + Posílá JSON payload do ActiveMQ queue, přijíma dic + """ + conn = get_connection() # připojení s retry loop + conn.send(destination=QUEUE_NAME, body=json.dumps(payload)) + print("Message sent to ActiveMQ:", payload) + conn.disconnect() + diff --git a/flask_backend/Stomp_client.py b/flask_backend/Stomp_client.py new file mode 100644 index 0000000..6e48f70 --- /dev/null +++ b/flask_backend/Stomp_client.py @@ -0,0 +1,25 @@ +import stomp, os, time + +MQ_HOST = os.getenv("MQ_HOST", "activemq") +MQ_PORT = int(os.getenv("MQ_PORT", 61613)) +MQ_USER = os.getenv("MQ_USER", "admin") +MQ_PASS = os.getenv("MQ_PASS", "admin") + +def get_connection(listener=None): + """ + Vrací připojený STOMP Connection objekt. + Pokud listener je None, připojení bude bez listeneru. + """ + conn = stomp.Connection12([(MQ_HOST, MQ_PORT)]) + if listener: + conn.set_listener("", listener) + + while True: + try: + conn.connect(MQ_USER, MQ_PASS, wait=True) + print("Connected to ActiveMQ") + break + except Exception as e: + print("Waiting for ActiveMQ to be ready...", e) + time.sleep(3) + return conn diff --git a/flask_backend/app.py b/flask_backend/app.py new file mode 100644 index 0000000..4efb997 --- /dev/null +++ b/flask_backend/app.py @@ -0,0 +1,20 @@ +from flask import Flask, request, jsonify +from Sender import send_to_queue + +app = Flask(__name__) + +@app.route("/send", methods=["POST"]) +def send(): + # očekává JSON payload + payload = request.get_json() + if not payload: + return jsonify({"error": "No JSON payload provided"}), 400 + + try: + send_to_queue(payload) # voláme funkci z sender.py + return jsonify({"status": "ok"}), 200 + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/flask_backend/templates/index.html b/flask_backend/templates/index.html new file mode 100644 index 0000000..64e95f5 --- /dev/null +++ b/flask_backend/templates/index.html @@ -0,0 +1,19 @@ + + + + + + Document + + +

Upload the

+
+ +
+
+ +
+ + {{ message }} + + \ No newline at end of file diff --git a/misc/inputs/Input_payload.xml b/misc/inputs/Input_payload.xml new file mode 100644 index 0000000..c6582b2 --- /dev/null +++ b/misc/inputs/Input_payload.xml @@ -0,0 +1,65 @@ + + + + + + 59088032 + Jiri Novak + Vika +
+ StreetName + 4312 + SANDNES + NO + true +
+ StreetName 1A + NORWAY + +4793488400 + +4793488999 + testguy@gmail.com + 1962-10-01 + false + false +
+ + 59088033 + Tomas Novak + Vika +
+ Postboks 16 + 4318 + BERGEN + NO + true +
+ StreetName 1B + NORWAY + +4793488888 + testguy2@gmail.com + 1963-10-01 + false + false +
+ + 59088034 + Karel Novak + Vika +
+ StreetName + 4319 + STAVANGER + NO + true +
+ StreetName 1C + NORWAY + +4793488777 + testguy3@gmail.com + 1964-10-01 + false + false +
+
+
+
\ No newline at end of file diff --git a/misc/inputs/Output_file_AMQ.json b/misc/inputs/Output_file_AMQ.json new file mode 100644 index 0000000..879c9bf --- /dev/null +++ b/misc/inputs/Output_file_AMQ.json @@ -0,0 +1,56 @@ +{ + "list": [ + { + "id": 59088032, + "firstName": "Jiri Novak", + "lastName": "Vika", + "streetName": "StreetName", + "postCode": 4312, + "cityName": "SANDNES", + "countryCode": "NO", + "isPostBox": true, + "addressLine1": "StreetName 1A", + "country": "NORWAY", + "phoneNumber": 4793488400, + "cellularPhoneNumber": 4793488999, + "emailAddress": "testguy@gmail.com", + "dateOfBirth": "1962-10-01", + "hasSecretAddress": false, + "isReservedAgainstSalesMaterial": false + }, + { + "id": 59088033, + "firstName": "Tomas Novak", + "lastName": "Vika", + "streetName": "Postboks 16", + "postCode": 4318, + "cityName": "BERGEN", + "countryCode": "NO", + "isPostBox": true, + "addressLine1": "StreetName 1B", + "country": "NORWAY", + "phoneNumber": 4793488888, + "emailAddress": "testguy2@gmail.com", + "dateOfBirth": "1963-10-01", + "hasSecretAddress": false, + "isReservedAgainstSalesMaterial": false + }, + { + "id": 59088034, + "firstName": "Karel Novak", + "lastName": "Vika", + "streetName": "StreetName", + "postCode": 4319, + "cityName": "STAVANGER", + "countryCode": "NO", + "isPostBox": true, + "addressLine1": "StreetName 1C", + "country": "NORWAY", + "phoneNumber": 4793488777, + "emailAddress": "testguy3@gmail.com", + "dateOfBirth": "1964-10-01", + "hasSecretAddress": false, + "isReservedAgainstSalesMaterial": false + } + ] +} \ No newline at end of file diff --git a/misc/output/Output_file.csv b/misc/output/Output_file.csv new file mode 100644 index 0000000..fd1410d --- /dev/null +++ b/misc/output/Output_file.csv @@ -0,0 +1,3 @@ +list;59088032;Jiri Novak;Vika;StreetName;4312;Sandnes;NO;true;StreetName;StreetName 1A;NORWAY;+4793488400;+4793488999;testguy@gmail.com;1962-10-01;false;false +list;59088033;Tomas Novak;Vika;StreetName;4318;Bergen;NO;true;Postboks 16;StreetName 1B;NORWAY;+4793488888;;testguy2@gmail.com;1963-10-01;false;false +list;59088034;Karel Novak;Vika;StreetName;4319;Stavanger;NO;true;StreetName;StreetName 1C;NORWAY;+4793488777;;testguy3@gmail.com;1964-10-01;false;false \ No newline at end of file diff --git a/misc/schéma.png b/misc/schéma.png new file mode 100644 index 0000000..0a9e67d Binary files /dev/null and b/misc/schéma.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6080ff6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +stomp.py +xmltodict \ No newline at end of file diff --git a/wso2/deployment.toml b/wso2/deployment.toml new file mode 100644 index 0000000..c41dde9 --- /dev/null +++ b/wso2/deployment.toml @@ -0,0 +1,4 @@ +[super_admin] +username = "admin" +password = "admin" +create_admin_account = true