Skip to content

Commit 14e9334

Browse files
committedAug 24, 2024
Add architecture analysis endpoint
1 parent c4f900b commit 14e9334

5 files changed

+204
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package restAPI.architecture_analysis;
2+
3+
import com.google.common.collect.ArrayListMultimap;
4+
import com.google.common.collect.Multimap;
5+
import org.apache.commons.io.FileUtils;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.http.HttpStatus;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestParam;
13+
import org.springframework.web.bind.annotation.RestController;
14+
import org.springframework.web.multipart.MultipartFile;
15+
import restAPI.util.TempFileUtils;
16+
17+
import java.io.IOException;
18+
import java.nio.file.Path;
19+
20+
@RestController
21+
public class ArchitectureAnalysisController {
22+
public static final String architectureFieldName = "architecture";
23+
private final ArchitectureAnalysisService architectureAnalysisService;
24+
25+
Logger logger = LoggerFactory.getLogger(ArchitectureAnalysisController.class);
26+
27+
@Autowired
28+
public ArchitectureAnalysisController(ArchitectureAnalysisService architectureAnalysisService) {
29+
this.architectureAnalysisService = architectureAnalysisService;
30+
}
31+
32+
@PostMapping("/analyze/upload")
33+
public ResponseEntity<String> handleMultipleFilesUpload(
34+
@RequestParam("architectures") MultipartFile[] architectures) throws IOException {
35+
return runSimulation(architectures);
36+
}
37+
38+
// TODO: Handle this call in a non-blocking manner, taking into account that this implementation is not
39+
// client friendly as it can time-out the request due to the long processing time.
40+
private ResponseEntity<String> runSimulation(MultipartFile[] architectures) throws IOException {
41+
Path tmpFolder = null;
42+
try {
43+
tmpFolder = TempFileUtils.createDefaultTempDir("architecture-analysis");
44+
Multimap<String, String> savedFiles = saveArchitectureFile(architectures, tmpFolder);
45+
var response = architectureAnalysisService.runAnalysis(savedFiles);
46+
return new ResponseEntity<>(response.toJSON(), HttpStatus.OK);
47+
} catch (Exception e) {
48+
String errorMessage = e.getMessage();
49+
logger.error(errorMessage);
50+
return new ResponseEntity<>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
51+
} finally {
52+
// Do the clean-up
53+
if (tmpFolder != null) {
54+
FileUtils.deleteDirectory(tmpFolder.toFile());
55+
}
56+
}
57+
}
58+
59+
private Multimap<String, String> saveArchitectureFile(MultipartFile[] architectures, Path tmpFolder) {
60+
Multimap<String, String> savedFiles = ArrayListMultimap.create();
61+
savedFiles = TempFileUtils.saveFile(savedFiles, architectureFieldName, architectures, tmpFolder);
62+
return savedFiles;
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package restAPI.architecture_analysis;
2+
3+
import java.io.File;
4+
import java.io.FileWriter;
5+
import java.io.IOException;
6+
7+
public class ArchitectureAnalysisExperimentFileGenerator {
8+
private static final String experimentJSON = """
9+
{
10+
"simulation_metadata": {
11+
"experiment_name": "Architecture-Only-Experiment",
12+
"model_name": "Architecture-Only-Model",
13+
"duration": 0
14+
}
15+
}""";
16+
17+
public File generateExperimentFile() throws IOException {
18+
File file = File.createTempFile("architecture-only-experiment", ".json");
19+
FileWriter writer = new FileWriter(file);
20+
writer.write(experimentJSON);
21+
writer.close();
22+
return file;
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package restAPI.architecture_analysis;
2+
3+
import cambio.simulator.ExperimentCreator;
4+
import cambio.simulator.ExperimentStartupConfig;
5+
import cambio.simulator.entities.NamedEntity;
6+
import cambio.simulator.entities.microservice.Microservice;
7+
import cambio.simulator.entities.microservice.Operation;
8+
import cambio.simulator.models.ArchitectureModel;
9+
import cambio.simulator.models.MiSimModel;
10+
import com.google.common.collect.Multimap;
11+
import desmoj.core.simulator.Experiment;
12+
import org.springframework.stereotype.Service;
13+
import restAPI.data_objects.ArchitectureAnalysisResponse;
14+
import restAPI.data_objects.ArchitectureAnalysisResponseImpl;
15+
16+
import java.io.File;
17+
import java.io.IOException;
18+
import java.util.Collection;
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
import static java.util.stream.Collectors.toSet;
23+
24+
@Service
25+
public class ArchitectureAnalysisService {
26+
private final File experiment;
27+
28+
public ArchitectureAnalysisService() throws IOException {
29+
this.experiment = (new ArchitectureAnalysisExperimentFileGenerator()).generateExperimentFile();
30+
}
31+
32+
public ArchitectureAnalysisResponse runAnalysis(Multimap<String, String> inputFiles) throws Exception {
33+
String archDescPath = getArchtectureDescPath(inputFiles);
34+
ArchitectureModelAdapter architectureModel = new ArchitectureModelAdapter(getArchitectureModel(archDescPath));
35+
return new ArchitectureAnalysisResponseImpl(architectureModel.getServiceNames(), architectureModel.getEndpointNames());
36+
}
37+
38+
private String getArchtectureDescPath(Multimap<String, String> inputFiles) throws Exception {
39+
Collection<String> archDescPathCollection = inputFiles.get(ArchitectureAnalysisController.architectureFieldName);
40+
if (archDescPathCollection.isEmpty()) {
41+
throw new Exception("You have to provide an architecture description file.");
42+
}
43+
return archDescPathCollection.iterator().next();
44+
}
45+
46+
private ArchitectureModel getArchitectureModel(final String archDescPath) {
47+
ExperimentStartupConfig config = new ExperimentStartupConfig(archDescPath, experiment.getAbsolutePath(),
48+
null, null, null, false,
49+
false, false, null);
50+
Experiment experiment = (new ExperimentCreator()).createSimulationExperiment(config);
51+
MiSimModel model = (MiSimModel) experiment.getModel();
52+
return model.getArchitectureModel();
53+
}
54+
55+
private static class ArchitectureModelAdapter {
56+
private final ArchitectureModel architectureModel;
57+
58+
public ArchitectureModelAdapter(ArchitectureModel architectureModel) {
59+
this.architectureModel = architectureModel;
60+
}
61+
62+
public Set<String> getServiceNames() {
63+
return architectureModel.getMicroservices().stream().map(NamedEntity::getPlainName).collect(toSet());
64+
}
65+
66+
public Set<String> getEndpointNames() {
67+
Set<String> names = new HashSet<>();
68+
for (Microservice service : architectureModel.getMicroservices()) {
69+
for (Operation operation : service.getOperations()) {
70+
names.add(operation.getFullyQualifiedPlainName());
71+
}
72+
}
73+
return names;
74+
}
75+
}
76+
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package restAPI.data_objects;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
5+
public interface ArchitectureAnalysisResponse {
6+
String toJSON() throws JsonProcessingException;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package restAPI.data_objects;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
6+
import java.util.Set;
7+
8+
public final class ArchitectureAnalysisResponseImpl implements ArchitectureAnalysisResponse {
9+
10+
private final Set<String> serviceNames;
11+
private final Set<String> endpointNames;
12+
13+
public ArchitectureAnalysisResponseImpl(Set<String> serviceNames, Set<String> endpointNames) {
14+
this.serviceNames = serviceNames;
15+
this.endpointNames = endpointNames;
16+
}
17+
18+
@Override
19+
public String toJSON() throws JsonProcessingException {
20+
return new ObjectMapper().writeValueAsString(this);
21+
}
22+
23+
public Set<String> getServiceNames() {
24+
return serviceNames;
25+
}
26+
27+
public Set<String> getEndpointNames() {
28+
return endpointNames;
29+
}
30+
}

0 commit comments

Comments
 (0)
Please sign in to comment.