20
20
import java .text .MessageFormat ;
21
21
import java .util .ArrayList ;
22
22
import java .util .HashSet ;
23
- import java .util .LinkedList ;
24
23
import java .util .List ;
25
24
import java .util .Map ;
26
25
import java .util .Set ;
52
51
* @author Oliver Libutzki - Added reloadAllModelsOfType method
53
52
* @author Simon Kaufmann - added validation of models before loading them
54
53
* @author Laurent Garnier - Added method generateSyntaxFromModel
54
+ * @author Laurent Garnier - Added method createTemporaryModel
55
55
*/
56
56
@ Component (immediate = true )
57
57
@ NonNullByDefault
58
58
public class ModelRepositoryImpl implements ModelRepository {
59
59
60
+ private static final String PREFIX_TMP_MODEL = "tmp_" ;
61
+
60
62
private final Logger logger = LoggerFactory .getLogger (ModelRepositoryImpl .class );
61
63
private final ResourceSet resourceSet ;
62
64
private final Map <String , String > resourceOptions = Map .of (XtextResource .OPTION_ENCODING ,
@@ -100,17 +102,37 @@ public ModelRepositoryImpl(final @Reference SafeEMF safeEmf) {
100
102
101
103
@ Override
102
104
public boolean addOrRefreshModel (String name , final InputStream originalInputStream ) {
105
+ return addOrRefreshModel (name , originalInputStream , null , null );
106
+ }
107
+
108
+ public boolean addOrRefreshModel (String name , final InputStream originalInputStream , @ Nullable List <String > errors ,
109
+ @ Nullable List <String > warnings ) {
103
110
logger .info ("Loading model '{}'" , name );
104
111
Resource resource = null ;
105
112
byte [] bytes ;
106
113
try (InputStream inputStream = originalInputStream ) {
107
114
bytes = inputStream .readAllBytes ();
108
- String validationResult = validateModel (name , new ByteArrayInputStream (bytes ));
109
- if (validationResult != null ) {
110
- logger .warn ("Configuration model '{}' has errors, therefore ignoring it: {}" , name , validationResult );
115
+ List <String > newErrors = new ArrayList <>();
116
+ List <String > newWarnings = new ArrayList <>();
117
+ boolean valid = validateModel (name , new ByteArrayInputStream (bytes ), newErrors , newWarnings );
118
+ if (errors != null ) {
119
+ errors .addAll (newErrors );
120
+ }
121
+ if (warnings != null ) {
122
+ warnings .addAll (newWarnings );
123
+ }
124
+ if (!valid ) {
125
+ if (!isTemporaryModel (name )) {
126
+ logger .warn ("Configuration model '{}' has errors, therefore ignoring it: {}" , name ,
127
+ String .join ("\n " , newErrors ));
128
+ }
111
129
removeModel (name );
112
130
return false ;
113
131
}
132
+ if (!isTemporaryModel (name ) && !newWarnings .isEmpty ()) {
133
+ logger .info ("Validation issues found in configuration model '{}', using it anyway:\n {}" , name ,
134
+ String .join ("\n " , newWarnings ));
135
+ }
114
136
} catch (IOException e ) {
115
137
logger .warn ("Configuration model '{}' cannot be parsed correctly!" , name , e );
116
138
return false ;
@@ -176,7 +198,7 @@ public Iterable<String> getAllModelNamesOfType(final String modelType) {
176
198
return resourceListCopy .stream ()
177
199
.filter (input -> input .getURI ().lastSegment ().contains ("." ) && input .isLoaded ()
178
200
&& modelType .equalsIgnoreCase (input .getURI ().fileExtension ())
179
- && !input .getURI ().lastSegment (). startsWith ( "tmp_" ))
201
+ && !isTemporaryModel ( input .getURI ().lastSegment ()))
180
202
.map (from -> from .getURI ().path ()).toList ();
181
203
}
182
204
}
@@ -189,7 +211,7 @@ public void reloadAllModelsOfType(final String modelType) {
189
211
for (Resource resource : resourceListCopy ) {
190
212
if (resource .getURI ().lastSegment ().contains ("." ) && resource .isLoaded ()
191
213
&& modelType .equalsIgnoreCase (resource .getURI ().fileExtension ())
192
- && !resource .getURI ().lastSegment (). startsWith ( "tmp_" )) {
214
+ && !isTemporaryModel ( resource .getURI ().lastSegment ())) {
193
215
XtextResource xtextResource = (XtextResource ) resource ;
194
216
// It's not sufficient to discard the derived state.
195
217
// The quick & dirts solution is to reparse the whole resource.
@@ -211,7 +233,7 @@ public Set<String> removeAllModelsOfType(final String modelType) {
211
233
for (Resource resource : resourceListCopy ) {
212
234
if (resource .getURI ().lastSegment ().contains ("." ) && resource .isLoaded ()
213
235
&& modelType .equalsIgnoreCase (resource .getURI ().fileExtension ())
214
- && !resource .getURI ().lastSegment (). startsWith ( "tmp_" )) {
236
+ && !isTemporaryModel ( resource .getURI ().lastSegment ())) {
215
237
logger .debug ("Removing resource '{}'" , resource .getURI ().lastSegment ());
216
238
ret .add (resource .getURI ().lastSegment ());
217
239
resourceSet .getResources ().remove (resource );
@@ -232,10 +254,21 @@ public void removeModelRepositoryChangeListener(ModelRepositoryChangeListener li
232
254
listeners .remove (listener );
233
255
}
234
256
257
+ @ Override
258
+ public @ Nullable String createTemporaryModel (String modelType , InputStream inputStream , List <String > errors ,
259
+ List <String > warnings ) {
260
+ String name = "%smodel_%d.%s" .formatted (PREFIX_TMP_MODEL , ++counter , modelType );
261
+ return addOrRefreshModel (name , inputStream , errors , warnings ) ? name : null ;
262
+ }
263
+
264
+ private boolean isTemporaryModel (String modelName ) {
265
+ return modelName .startsWith (PREFIX_TMP_MODEL );
266
+ }
267
+
235
268
@ Override
236
269
public void generateSyntaxFromModel (OutputStream out , String modelType , EObject modelContent ) {
237
270
synchronized (resourceSet ) {
238
- String name = "tmp_generated_syntax_% d.%s" .formatted (++counter , modelType );
271
+ String name = "%sgenerated_syntax_% d.%s" .formatted (PREFIX_TMP_MODEL , ++counter , modelType );
239
272
Resource resource = resourceSet .createResource (URI .createURI (name ));
240
273
try {
241
274
resource .getContents ().add (modelContent );
@@ -268,28 +301,28 @@ public void generateSyntaxFromModel(OutputStream out, String modelType, EObject
268
301
* Validation will be done on a separate resource, in order to keep the original one intact in case its content
269
302
* needs to be removed because of syntactical errors.
270
303
*
271
- * @param name
272
- * @param inputStream
273
- * @return error messages as a String if any syntactical error were found, <code>null</code> otherwise
304
+ * @param name the model name
305
+ * @param inputStream an input stream with the model's content
306
+ * @param errors the list to be used to fill the errors
307
+ * @param warnings the list to be used to fill the warnings
308
+ * @return false if any syntactical error were found, false otherwise
274
309
* @throws IOException if there was an error with the given {@link InputStream}, loading the resource from there
275
310
*/
276
- private @ Nullable String validateModel (String name , InputStream inputStream ) throws IOException {
311
+ private boolean validateModel (String name , InputStream inputStream , List <String > errors , List <String > warnings )
312
+ throws IOException {
277
313
// use another resource for validation in order to keep the original one for emergency-removal in case of errors
278
- Resource resource = resourceSet .createResource (URI .createURI ("tmp_" + name ));
314
+ Resource resource = resourceSet .createResource (URI .createURI (PREFIX_TMP_MODEL + name ));
279
315
try {
280
316
resource .load (inputStream , resourceOptions );
281
- StringBuilder criticalErrors = new StringBuilder ();
282
- List <String > warnings = new LinkedList <>();
283
317
284
318
if (!resource .getContents ().isEmpty ()) {
285
319
// Check for syntactical errors
286
320
for (Diagnostic diagnostic : resource .getErrors ()) {
287
- criticalErrors
288
- .append (MessageFormat .format ("[{0},{1}]: {2}\n " , Integer .toString (diagnostic .getLine ()),
289
- Integer .toString (diagnostic .getColumn ()), diagnostic .getMessage ()));
321
+ errors .add (MessageFormat .format ("[{0},{1}]: {2}" , Integer .toString (diagnostic .getLine ()),
322
+ Integer .toString (diagnostic .getColumn ()), diagnostic .getMessage ()));
290
323
}
291
- if (!criticalErrors .isEmpty ()) {
292
- return criticalErrors . toString () ;
324
+ if (!resource . getErrors () .isEmpty ()) {
325
+ return false ;
293
326
}
294
327
295
328
// Check for validation errors, but log them only
@@ -299,10 +332,6 @@ public void generateSyntaxFromModel(OutputStream out, String modelType, EObject
299
332
for (org .eclipse .emf .common .util .Diagnostic d : diagnostic .getChildren ()) {
300
333
warnings .add (d .getMessage ());
301
334
}
302
- if (!warnings .isEmpty ()) {
303
- logger .info ("Validation issues found in configuration model '{}', using it anyway:\n {}" , name ,
304
- String .join ("\n " , warnings ));
305
- }
306
335
} catch (NullPointerException e ) {
307
336
// see https://github.com/eclipse/smarthome/issues/3335
308
337
logger .debug ("Validation of '{}' skipped due to internal errors." , name );
@@ -311,12 +340,14 @@ public void generateSyntaxFromModel(OutputStream out, String modelType, EObject
311
340
} finally {
312
341
resourceSet .getResources ().remove (resource );
313
342
}
314
- return null ;
343
+ return true ;
315
344
}
316
345
317
346
private void notifyListeners (String name , EventType type ) {
318
- for (ModelRepositoryChangeListener listener : listeners ) {
319
- listener .modelChanged (name , type );
347
+ if (!isTemporaryModel (name )) {
348
+ for (ModelRepositoryChangeListener listener : listeners ) {
349
+ listener .modelChanged (name , type );
350
+ }
320
351
}
321
352
}
322
353
}
0 commit comments