-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c0d2ae0
Showing
12 changed files
with
1,015 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
PORT= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.venv/ | ||
__pycache__/ | ||
.git/ | ||
data/ | ||
.DS_Store | ||
app.py | ||
.env |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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! | ||
} | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |