6
6
import com .fasterxml .jackson .databind .SerializationFeature ;
7
7
import com .structurizr .Workspace ;
8
8
import com .structurizr .io .WorkspaceWriterException ;
9
+ import com .structurizr .model .Relationship ;
10
+ import com .structurizr .util .ImageUtils ;
9
11
import com .structurizr .util .StringUtils ;
12
+ import com .structurizr .util .Url ;
10
13
import org .apache .hc .client5 .http .classic .methods .HttpGet ;
11
14
import org .apache .hc .client5 .http .config .ConnectionConfig ;
12
15
import org .apache .hc .client5 .http .impl .classic .CloseableHttpClient ;
13
16
import org .apache .hc .client5 .http .impl .classic .CloseableHttpResponse ;
14
17
import org .apache .hc .client5 .http .impl .classic .HttpClientBuilder ;
18
+ import org .apache .hc .client5 .http .impl .classic .HttpClients ;
15
19
import org .apache .hc .client5 .http .impl .io .BasicHttpClientConnectionManager ;
16
20
import org .apache .hc .core5 .http .io .entity .EntityUtils ;
17
21
18
22
import java .io .*;
19
23
import java .nio .charset .StandardCharsets ;
24
+ import java .nio .file .Files ;
20
25
import java .util .concurrent .TimeUnit ;
21
26
22
27
/**
@@ -65,7 +70,7 @@ public static String toJson(Workspace workspace) throws Exception {
65
70
}
66
71
67
72
/**
68
- * Loads (and inlines) the element and relationship styles from the themes defined in the workspace, into the workspace itself.
73
+ * Loads the element and relationship styles from the themes defined in the workspace, into the workspace itself.
69
74
* This implementation simply copies the styles from all themes into the workspace.
70
75
* This uses a default timeout value of 10000ms.
71
76
*
@@ -77,40 +82,19 @@ public static void loadThemes(Workspace workspace) throws Exception {
77
82
}
78
83
79
84
/**
80
- * Loads (and inlines) the element and relationship styles from the themes defined in the workspace, into the workspace itself.
85
+ * Loads the element and relationship styles from the themes defined in the workspace, into the workspace itself.
81
86
* This implementation simply copies the styles from all themes into the workspace.
82
87
*
83
88
* @param workspace a Workspace object
84
89
* @param timeoutInMilliseconds the timeout in milliseconds
85
90
* @throws Exception if something goes wrong
86
91
*/
87
92
public static void loadThemes (Workspace workspace , int timeoutInMilliseconds ) throws Exception {
88
- for (String url : workspace .getViews ().getConfiguration ().getThemes ()) {
89
- ConnectionConfig connectionConfig = ConnectionConfig .custom ()
90
- .setConnectTimeout (timeoutInMilliseconds , TimeUnit .MILLISECONDS )
91
- .setSocketTimeout (timeoutInMilliseconds , TimeUnit .MILLISECONDS )
92
- .build ();
93
-
94
- BasicHttpClientConnectionManager cm = new BasicHttpClientConnectionManager ();
95
- cm .setConnectionConfig (connectionConfig );
96
-
97
- CloseableHttpClient httpClient = HttpClientBuilder .create ()
98
- .useSystemProperties ()
99
- .setConnectionManager (cm )
100
- .build ();
101
-
102
- HttpGet httpGet = new HttpGet (url );
103
-
104
- CloseableHttpResponse response = httpClient .execute (httpGet );
105
- if (response .getCode () == HTTP_OK_STATUS ) {
106
- String json = EntityUtils .toString (response .getEntity ());
107
-
108
- ObjectMapper objectMapper = new ObjectMapper ();
109
- objectMapper .enable (DeserializationFeature .ACCEPT_EMPTY_STRING_AS_NULL_OBJECT );
110
- objectMapper .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
111
-
112
- Theme theme = objectMapper .readValue (json , Theme .class );
113
- String baseUrl = url .substring (0 , url .lastIndexOf ('/' ) + 1 );
93
+ for (String themeLocation : workspace .getViews ().getConfiguration ().getThemes ()) {
94
+ if (Url .isUrl (themeLocation )) {
95
+ String json = loadFrom (themeLocation , timeoutInMilliseconds );
96
+ Theme theme = fromJson (json );
97
+ String baseUrl = themeLocation .substring (0 , themeLocation .lastIndexOf ('/' ) + 1 );
114
98
115
99
for (ElementStyle elementStyle : theme .getElements ()) {
116
100
String icon = elementStyle .getIcon ();
@@ -128,9 +112,69 @@ public static void loadThemes(Workspace workspace, int timeoutInMilliseconds) th
128
112
129
113
workspace .getViews ().getConfiguration ().getStyles ().addStylesFromTheme (theme );
130
114
}
115
+ }
116
+ }
131
117
132
- httpClient .close ();
118
+ /**
119
+ * Inlines the element and relationship styles from the specified file, adding the styles into the workspace
120
+ * and overriding any properties already set.
121
+ *
122
+ * @param workspace the Workspace to load the theme into
123
+ * @param file a File object representing a theme (a JSON file)
124
+ * @throws Exception if something goes wrong
125
+ */
126
+ public static void inlineTheme (Workspace workspace , File file ) throws Exception {
127
+ String json = Files .readString (file .toPath ());
128
+ Theme theme = fromJson (json );
129
+
130
+ for (ElementStyle elementStyle : theme .getElements ()) {
131
+ String icon = elementStyle .getIcon ();
132
+ if (!StringUtils .isNullOrEmpty (icon )) {
133
+ if (icon .startsWith ("http" )) {
134
+ // okay, image served over HTTP
135
+ } else if (icon .startsWith ("data:image" )) {
136
+ // also okay, data URI
137
+ } else {
138
+ // convert the relative icon filename into a data URI
139
+ elementStyle .setIcon (ImageUtils .getImageAsDataUri (new File (file .getParentFile (), icon )));
140
+ }
141
+ }
133
142
}
143
+
144
+ workspace .getViews ().getConfiguration ().getStyles ().inlineTheme (theme );
145
+ }
146
+
147
+ private static String loadFrom (String url , int timeoutInMilliseconds ) throws Exception {
148
+ ConnectionConfig connectionConfig = ConnectionConfig .custom ()
149
+ .setConnectTimeout (timeoutInMilliseconds , TimeUnit .MILLISECONDS )
150
+ .setSocketTimeout (timeoutInMilliseconds , TimeUnit .MILLISECONDS )
151
+ .build ();
152
+
153
+ BasicHttpClientConnectionManager cm = new BasicHttpClientConnectionManager ();
154
+ cm .setConnectionConfig (connectionConfig );
155
+
156
+ try (CloseableHttpClient httpClient = HttpClientBuilder .create ()
157
+ .useSystemProperties ()
158
+ .setConnectionManager (cm )
159
+ .build ()) {
160
+
161
+ HttpGet httpGet = new HttpGet (url );
162
+
163
+ CloseableHttpResponse response = httpClient .execute (httpGet );
164
+ if (response .getCode () == HTTP_OK_STATUS ) {
165
+ return EntityUtils .toString (response .getEntity ());
166
+ }
167
+ }
168
+
169
+ return "" ;
170
+ }
171
+
172
+ private static Theme fromJson (String json ) throws Exception {
173
+ ObjectMapper objectMapper = new ObjectMapper ();
174
+ objectMapper .enable (DeserializationFeature .ACCEPT_EMPTY_STRING_AS_NULL_OBJECT );
175
+ objectMapper .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
176
+
177
+ return objectMapper .readValue (json , Theme .class );
134
178
}
135
179
136
180
private static void write (Workspace workspace , Writer writer ) throws Exception {
0 commit comments