Skip to content

Commit

Permalink
Stable version
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosborges1 committed Dec 4, 2023
0 parents commit c0d2ae0
Show file tree
Hide file tree
Showing 12 changed files with 1,015 additions and 0 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PORT=
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.venv/
__pycache__/
.git/
data/
.DS_Store
app.py
.env
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Architecture Generator (AG)

## Overview

The Architecture Generator (AG) is the main component of our approach, playing a crucial role in creating SoS architectures in the System Entity Structure (SES) language, which was designed to detail the decomposition of systems into subsystems based on a given perspective, and how such subsystems interact with each other. The AG generates the SES architecture using information from the JSON files missionFileJson and cssBehaviorFileJson, which are produced by the other two components, ME and CBA, respectively.

The essence of AG's functionality is rooted in the [Algorithm section](#Algorithm).

## Algorithm

The AG's core is based on the algorithm described below.

<img src="/images/ag_algorithm.png" height="300"/>

## Implementation Details

Constructed using Python, the AG service is a lightweight, dynamic, and web-compatible solution. The choice of language complements the ME algorithm's versatility and caters to the overarching requirements of the System of Systems context.

## Setup

Before running the application, make sure to install the required dependencies. You can install them using `pip`:

```bash
pip install -r requirements.txt
```

## Usage

Before you start the AG, be sure to start it.

```bash
python server.py
```

Access the AG from the GraphQL endpoint:

```bash
http://localhost:4003/graphql
```

**Note**:

- The default PORT is _4003_, but can be change for your convenience.
- This project heavily relies on GraphQL, a powerful query language for APIs, and a server-side runtime for executing those queries with your existing data. If you're unfamiliar with GraphQL or wish to dive deeper, you can [learn more about GraphQL here](https://graphql.org/).

<!-- ## References -->

## Project Status

The AG, currently in the evolutionary phase. It is actively undergoing improvements and changes to refine its capabilities and more effectively meet new requirements.

## Author

**Marcos Borges**
PhD Student at Federal University of Ceará, Brazil
Email: [marcos.borges@alu.ufc.br](mailto:marcos.borges@alu.ufc.br)

## Contributing

Community-driven improvements are always welcome. If you're looking to contribute, feel free to raise pull requests. For more significant changes or additions, it's recommended to open an issue first for discussions.

## License

This project is licensed under the GPL-3.0 License - see the [LICENSE](LICENSE) file for details.
2 changes: 2 additions & 0 deletions core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from core.utils import Utils
from core.architecture_generator import ArchitectureGenerator
121 changes: 121 additions & 0 deletions core/architecture_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import re
import json
import os
import aiohttp
from core.utils import Utils


class ArchitectureGenerator:
def __init__(self):
self.architectural_file = []

async def open_file(self, url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status != 200:
raise ValueError(f"Error {response.status}: {response.reason}")
return await response.json() # Parse JSON and return the content

def read_json(self, file_path):
try:
with open(file_path, "r") as file:
# Load JSON content from the file
data = json.load(file)
return data
except FileNotFoundError:
print(f"File not found: {file_path}")
except json.JSONDecodeError:
print(f"Error reading the JSON file: {file_path}")
except Exception as e:
print(f"An error occurred: {e}")

def update_css_messages_according_to_their_files(self, css, messages):
updated_messages = []

for message in messages:
from_prefix = message[
"from"
].lower() # Lowercase for case-insensitive comparison
to_prefix = message["to"].lower()

from_matches = [
css_item for css_item in css if css_item.lower().startswith(from_prefix)
]
to_matches = [
css_item for css_item in css if css_item.lower().startswith(to_prefix)
]

for from_match in from_matches:
for to_match in to_matches:
new_message = (
message.copy()
) # Create a copy of the original message
new_message["from"] = from_match
new_message["to"] = to_match
updated_messages.append(new_message)

return updated_messages

def find_equal_items(self, list1, list2):
def preprocess_list(lst):
new_list = []
for item in lst:
new_item = (
item.copy()
) # Create a copy to avoid modifying the original item
message = new_item.get("message", "")
# Remove 'in' and 'out' from message
new_item["message"] = message.replace("in", "").replace("out", "")
# Normalize the case for comparison
new_item = {k: v for k, v in new_item.items()}
new_list.append(new_item)
return new_list

normalized_list1 = preprocess_list(list1)
normalized_list2 = preprocess_list(list2)

equal_items = [item for item in normalized_list1 if item in normalized_list2]
return equal_items

async def get_css_names(self, css_behavior_file_json):
css_list = await self.open_file(css_behavior_file_json)
css_names = []
for cs in css_list:
for key in cs:
css_names.append(key)
return css_names

async def generate_architectural_file(
self,
sos_name,
mission_file_json,
css_behavior_file_json,
valid_css_combinations_file_json,
):
css_names = await self.get_css_names(css_behavior_file_json)
first_sentece = f'From the top perspective, {sos_name} are made of {", ".join(css_names[:-1]) + ", and " + css_names[-1]}!'
self.architectural_file.append(first_sentece)
self.architectural_file.append("\n")

pairs = await self.open_file(mission_file_json)
valid_css_combination_list = await self.open_file(
valid_css_combinations_file_json
)
for pair in pairs:
pair_updated = self.update_css_messages_according_to_their_files(
css_names, pair
)
valid_communications = self.find_equal_items(
pair_updated, valid_css_combination_list
)

if valid_communications:
for v in valid_communications:
# Create Scenario
sentence = f"From the top perspective, {v['from']} sends {v['message']} to {v['to']}"
self.architectural_file.append(f"{sentence}!")
if pair != pairs[-1]:
self.architectural_file.append("\n")

def save_file(self, file_name="architecture"):
return Utils.save_file(file_name, "\n".join(self.architectural_file))
57 changes: 57 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import json
import aiohttp
import os, datetime, re


class Utils:
@staticmethod
async def open_file(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status != 200:
raise ValueError(f"Error {response.status}: {response.reason}")
return await response.json() # Parse JSON and return the content

@staticmethod
def read_file(file_path):
"""
Reads all content from the specified file and returns it.
"""
try:
with open(file_path, "r") as file:
return file.read()
except FileNotFoundError:
return "File not found."
except Exception as e:
return f"An error occurred: {e}"

@staticmethod
def save_file(file_name, content, folder_name="data", extension="ses"):
if not os.path.exists(folder_name):
os.makedirs(folder_name)
current_time = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
file_name = f"{file_name}_{current_time}.{extension}"
file_path = os.path.join(folder_name, file_name)
with open(file_path, "w") as file:
file.write(content)

return file_path

@staticmethod
def transform_to_json(text):
# Load the string as JSON
data = json.loads(text)

# Parse the internal stringified JSON objects
for obj in data:
for key, value in obj.items():
if isinstance(value, dict):
for subkey, subvalue in value.items():
if isinstance(subvalue, str):
try:
# Replace escaped double quotes and load as JSON
value[subkey] = json.loads(subvalue.replace('\\"', '"'))
except json.JSONDecodeError:
pass # Handle the case where the string is not a valid JSON

return data
Binary file added images/ag_algorithm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions my_graphql/resolvers/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from core import ArchitectureGenerator
import os

BASE_URL = f"http://localhost:{os.getenv('PORT')}"


async def resolve_generate_architecture(
_,
info,
sos,
mission_file_json,
css_behavior_file_json,
valid_css_combinations_file_json,
):
architecture_generator = ArchitectureGenerator()
await architecture_generator.generate_architectural_file(
sos, mission_file_json, css_behavior_file_json, valid_css_combinations_file_json
)
file_saved = architecture_generator.save_file()
information = f"Architecture file saved in '{BASE_URL}/{file_saved}'"

return {
"architeture_file": f"{BASE_URL}/{file_saved}",
"information": f"{information}",
}
10 changes: 10 additions & 0 deletions my_graphql/schemas/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type_defs = """
type Query {
generate_architeture(sos:String!, mission_file_json:String!, css_behavior_file_json:String!, valid_css_combinations_file_json:String!): architeture_file_output!
}
type architeture_file_output @key(fields: "architeture_file") {
architeture_file: String!
information: String!
}
"""
19 changes: 19 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
aiohttp==3.9.1
aiosignal==1.3.1
anyio==4.1.0
ariadne==0.21
async-timeout==4.0.3
attrs==23.1.0
click==8.1.7
exceptiongroup==1.2.0
frozenlist==1.4.0
graphql-core==3.2.3
h11==0.14.0
idna==3.6
multidict==6.0.4
python-dotenv==1.0.0
sniffio==1.3.0
starlette==0.33.0
typing_extensions==4.8.0
uvicorn==0.24.0.post1
yarl==1.9.3
35 changes: 35 additions & 0 deletions server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# server.py
import os
from dotenv import load_dotenv
import uvicorn
from ariadne import QueryType, make_executable_schema
from ariadne.asgi import GraphQL
from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.staticfiles import StaticFiles
from my_graphql.schemas.index import type_defs
from my_graphql.resolvers.index import resolve_generate_architecture
from ariadne.contrib.federation import make_federated_schema

# Load dotenv
load_dotenv()

# Ariadne setup
query = QueryType()
query.set_field("generate_architeture", resolve_generate_architecture)
schema = make_federated_schema(type_defs, query)
graphql_app = GraphQL(schema, debug=True)

# Define the routes for the app
routes = [
Route("/", graphql_app),
Mount("/data", StaticFiles(directory="data"), name="static"),
]

# Create the Starlette app with the defined routes
app = Starlette(routes=routes)

if __name__ == "__main__":
uvicorn.run(
"server:app", host="localhost", port=int(os.getenv("PORT")), reload=True
)

0 comments on commit c0d2ae0

Please sign in to comment.