Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ SBOLCanvas is a web application for creation and editing of genetic constructs u
## SynBioSuite Branch

This branch is specifically for SBOLCanvas as embedded in the [SynBioSuite app](https://github.com/MyersResearchGroup/SynBioSuite);
however, it can still function as a standalone application.
however, it can still function as a standalone application.

## Repository Structure

This is a monorepo containing an Angular app as a frontend (in the frontend directory)
and a Dockerized Java API (in the backend directory) that handles things like
and a Dockerized Java API (in the backend directory) that handles things like
conversion, communication with SynBioHub, etc.

This differs from the *final* branch, which Dockerizes the frontend and backend
together, and serves the Angular app from the backend.

The changes made were an optimization to allow the API to be deployed serverlessly
and the frontend to be deployed as a static web app served from CDNs.
and the frontend to be deployed as a static web app served from CDNs.

## Run Locally

Clone the project
Expand Down Expand Up @@ -62,6 +64,7 @@ Start the Angular development server
```bash
npm run dev
```

Or, if you plan to develop the frontend, use

```bash
Expand All @@ -75,11 +78,14 @@ Alternatively, you can build both the frontend and backend together on one Docke
```bash
docker build -t sbolcanvas .
```

and then

```bash
docker run --rm --name canvas --publish 4040:8080 sbolcanvas
```
A local instance will be available on http://localhost:4040/

A local instance will be available on <http://localhost:4040/>

If you plan to contribute to this repository, this is recommended before you open a Pull Request. GitHub Actions will use a similar process to check the Docker build and deployment.

Expand All @@ -92,6 +98,11 @@ cd SBOLCanvasBackend
mvn test
```

```bash
# when adding a new test class, run a clean build
mvn clean test
```

Frontend:

```bash
Expand All @@ -108,15 +119,14 @@ npm run build
```

The built output will be available in frontend/dist and can be deployed anywhere
a static web app can be deployed. Genetic Logic Lab's weapon of choice is
a static web app can be deployed. Genetic Logic Lab's weapon of choice is
[Azure Static Web Apps](https://azure.microsoft.com/en-us/products/app-service/static/).

To build the backend, from the backend directory, run

```bash
docker build -t sbolcanvas .
```

The resulting Docker image can be deployed anywhere you can run Docker containers.
Genetic Logic Lab uses [Azure Container Apps](https://azure.microsoft.com/en-us/products/container-apps/).


18 changes: 18 additions & 0 deletions SBOLCanvasBackend/src/main/java/org/sbolcanvas/data/EventInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
public class EventInfo extends Info {

private Hashtable<String, Object> simulationData;
private String name;
private String description;

@Override
public String getFullURI() {
Expand All @@ -23,4 +25,20 @@ public void setSimulationData(Hashtable<String, Object> simulationData) {
this.simulationData = simulationData;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,17 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
} catch (SBOLValidationException | IOException | SBOLConversionException | ParserConfigurationException
| TransformerException | SAXException | TransformerFactoryConfigurationError | URISyntaxException | SynBioHubException | javax.xml.stream.XMLStreamException e) {
ServletOutputStream outputStream = response.getOutputStream();
String message = e.getMessage() != null ? e.getMessage() : "Export failed";
String action = "/toMxGraph".equals(request.getPathInfo()) ? "Import" : "Export";
String message = e.getMessage() != null ? e.getMessage() : action + " failed";
InputStream inputStream = new ByteArrayInputStream(message.getBytes());
IOUtils.copy(inputStream, outputStream);

response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
e.printStackTrace();
} catch (RuntimeException e) {
// Catch unchecked exceptions from SBML export
String message = e.getMessage() != null ? e.getMessage() : "Export failed";
String action = "/toMxGraph".equals(request.getPathInfo()) ? "Import" : "Export";
String message = e.getMessage() != null ? e.getMessage() : action + " failed";
ServletOutputStream outputStream = response.getOutputStream();
InputStream inputStream = new ByteArrayInputStream(message.getBytes());
IOUtils.copy(inputStream, outputStream);
Expand Down
48 changes: 27 additions & 21 deletions SBOLCanvasBackend/src/main/java/org/sbolcanvas/utils/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ public class Converter {
/**
* mxObjectCodec that decodes to a Hashtable instead of an ArrayList.
*
* @param template the template object (e.g., new GlyphInfo())
* @param targetType the expected runtime class of the decoded object
* @param setter applies the parsed Hashtable to the decoded object
* @param template the template object (e.g., new GlyphInfo())
* @param targetType the expected runtime class of the decoded object
* @param setter applies the parsed Hashtable to the decoded object
*/
private static <T> mxObjectCodec createSimulationDataCodec(
Object template, Class<T> targetType,
Expand Down Expand Up @@ -177,7 +177,8 @@ public boolean filter(Object arg0) {
static Filter containerFilter = new Filter() {
@Override
public boolean filter(Object arg0) {
return (arg0 instanceof mxCell && ((mxCell) arg0).getStyle() != null && (((mxCell) arg0).getStyle().contains(STYLE_CIRCUIT_CONTAINER)) && (((mxCell) arg0).getChildCount() > 1));
return (arg0 instanceof mxCell && ((mxCell) arg0).getStyle() != null && (((mxCell) arg0).getStyle().contains(STYLE_CIRCUIT_CONTAINER))
&& (((mxCell) arg0).getChildCount() > 1));
}
};

Expand Down Expand Up @@ -215,6 +216,16 @@ public boolean filter(Object arg0) {
}
};

/**
* Filters mxCells that contain "eventGlyph" in the style string
*/
static Filter eventFilter = new Filter() {
@Override
public boolean filter(Object arg0) {
return arg0 instanceof mxCell && ((mxCell) arg0).getStyle() != null && ((mxCell) arg0).getStyle().contains(STYLE_EVENT);
}
};

protected static URI getParticipantType(boolean source, Set<URI> interactionTypes) {
URI interactionType = null;
for (URI interactionURI : SBOLData.interactions.values()) {
Expand Down Expand Up @@ -246,12 +257,13 @@ static QName createQName(String name) {
* @see MxToSBML#sanitizeId for SBML SId sanitization (different spec, different rules)
*/
static String sanitizeAnnotationKey(String key) {
if (key == null || key.isEmpty()) return key;
if (key == null || key.isEmpty())
return key;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
if (i == 0 ? (Character.isLetter(c) || c == '_')
: (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == '_')) {
: (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == '_')) {
sb.append(c);
} else {
sb.append("_x").append(String.format("%04X", (int) c)).append("_");
Expand All @@ -264,7 +276,8 @@ static String sanitizeAnnotationKey(String key) {
* Reverse sanitizeAnnotationKey: decode _xHHHH_ sequences back to characters.
*/
static String desanitizeAnnotationKey(String key) {
if (key == null || key.isEmpty()) return key;
if (key == null || key.isEmpty())
return key;
StringBuilder sb = new StringBuilder();
int i = 0;
while (i < key.length()) {
Expand Down Expand Up @@ -317,7 +330,7 @@ protected mxGraph loadGraphAndDictionaries(InputStream graphStream) throws IOExc
infoDict = loadDictionary(dataContainer, INFO_DICT_INDEX);
combinatorialDict = loadDictionary(dataContainer, COMBINATORIAL_DICT_INDEX);
interactionDict = loadDictionary(dataContainer, INTERACTION_DICT_INDEX);
loadEventDictOrEmpty(dataContainer);
eventDict = loadDictionary(dataContainer, EVENT_DICT_INDEX);
return graph;
}

Expand All @@ -331,6 +344,10 @@ protected mxGraph loadGraphAndDictionaries(InputStream graphStream) throws IOExc
*/
@SuppressWarnings("unchecked")
protected <T extends Info> Hashtable<String, T> loadDictionary(ArrayList<Object> dataContainer, int dictionaryIndex) {
// Older designs predate this slot; treat an absent slot as an empty dictionary.
if (dictionaryIndex >= dataContainer.size()) {
return new Hashtable<String, T>();
}
if (dataContainer.get(dictionaryIndex) instanceof ArrayList) {
// 90% sure it only happens when it's empty meaning that we could just return a
// empty hash table.
Expand Down Expand Up @@ -366,7 +383,8 @@ protected <T extends Info> Hashtable<String, T> loadDictionary(ArrayList<Object>
*/
static void writeSimulationAnnotations(Identified parent, Hashtable<String, Object> simulationData,
String identityPrefix) throws SBOLValidationException {
if (simulationData == null || simulationData.isEmpty()) return;
if (simulationData == null || simulationData.isEmpty())
return;
List<Annotation> annList = new ArrayList<Annotation>();
for (String key : new TreeSet<>(simulationData.keySet())) {
annList.add(new Annotation(createQName(sanitizeAnnotationKey(key)), simulationData.get(key).toString()));
Expand Down Expand Up @@ -395,16 +413,4 @@ static Hashtable<String, Object> readSimulationAnnotations(Identified identified
return new Hashtable<>();
}

/**
* Load eventDict from the dataContainer, falling back to an empty Hashtable
* for designs created before events were added.
*/
protected void loadEventDictOrEmpty(ArrayList<Object> dataContainer) {
if (dataContainer.size() > EVENT_DICT_INDEX) {
eventDict = loadDictionary(dataContainer, EVENT_DICT_INDEX);
} else {
eventDict = new Hashtable<>();
}
}

}
Loading
Loading