]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/ModelTransferableGraphSource.java
Support tg discovery in export/import
[simantics/platform.git] / bundles / org.simantics.db.layer0 / src / org / simantics / db / layer0 / util / ModelTransferableGraphSource.java
1 package org.simantics.db.layer0.util;
2
3 import java.io.Closeable;
4 import java.io.DataInput;
5 import java.io.File;
6 import java.io.IOException;
7 import java.nio.file.Files;
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.Collections;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.TreeMap;
14
15 import org.simantics.databoard.Bindings;
16 import org.simantics.databoard.Databoard;
17 import org.simantics.databoard.binding.Binding;
18 import org.simantics.databoard.binding.mutable.Variant;
19 import org.simantics.databoard.container.DataContainer;
20 import org.simantics.databoard.serialization.Serializer;
21 import org.simantics.databoard.type.Datatype;
22 import org.simantics.databoard.util.binary.ByteBufferReadable;
23 import org.simantics.db.ReadGraph;
24 import org.simantics.db.Resource;
25 import org.simantics.db.common.utils.NameUtils;
26 import org.simantics.db.exception.DatabaseException;
27 import org.simantics.db.exception.RuntimeDatabaseException;
28 import org.simantics.db.exception.ValidationException;
29 import org.simantics.db.layer0.adapter.SubgraphExtent.ExtentStatus;
30 import org.simantics.db.layer0.util.TransferableGraphConfiguration2.RootSpec;
31 import org.simantics.db.service.SerialisationSupport;
32 import org.simantics.graph.db.TransferableGraphSource;
33 import org.simantics.graph.representation.External;
34 import org.simantics.graph.representation.Identity;
35 import org.simantics.graph.representation.Root;
36 import org.simantics.graph.representation.Value;
37 import org.simantics.layer0.Layer0;
38
39 import gnu.trove.list.array.TIntArrayList;
40 import gnu.trove.procedure.TIntIntProcedure;
41
42 public class ModelTransferableGraphSource implements TransferableGraphSource {
43
44         final private TransferableGraphConfiguration2 configuration;
45         final private DomainProcessorState state;
46         final private int externalBase;
47         final private int resourceCount;
48         final private File[] files;
49         final private TGValueModifier valueModifier;
50
51         private volatile boolean closed = false;
52
53         TIntArrayList externalParents = new TIntArrayList();
54         ArrayList<String> externalNames = new ArrayList<String>();
55         TreeMap<String,String> downloads = new TreeMap<String,String>();
56
57         public ModelTransferableGraphSource(final ReadGraph graph, TransferableGraphConfiguration2 configuration, final DomainProcessorState state, File ... fs) throws DatabaseException {
58
59                 this.configuration = configuration;
60                 this.state = state;
61                 this.files = fs;
62                 this.valueModifier = state.valueModifier;
63
64                 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
65
66                 // At this point ids contains all internal resources. Now add roots and externals.
67                 
68                 // Root Library
69                 state.ids.put(ss.getTransientId(graph.getRootLibrary()), state.id++);
70                 
71                 // External roots - internal roots were already processed as internal resources by domain processor
72                 for(RootSpec rs : configuration.roots) {
73                         if(!rs.internal) {
74                                 int resourceId = ss.getTransientId(rs.resource);
75                                 state.ids.put(resourceId, state.id++);
76                                 // The fixed roots have been seen as externals by domain processor. Now remove them from external set.
77                                 state.externals.remove(resourceId);
78                         }
79                 }
80                 
81                 this.externalBase = state.id;
82
83                 final Collection<String> errors = new HashSet<String>();
84
85                 // All resource considered as not internal by domain processor. Can also contain roots.
86                 int[] externals = state.externals.toArray();
87                 
88                 // Build up the state.externals, externalNames and externalParents
89                 for(int i=0;i<externals.length;i++) {
90                         getId(graph, externals[i], errors);
91                 }
92                 
93                 state.inverses.forEachEntry(new TIntIntProcedure() {
94                         
95                         @Override
96                         public boolean execute(int predicate, int inverse) {
97                                 try {
98                                         getId(graph, predicate, errors);
99                                         if(inverse != 0) getId(graph, inverse, errors);
100                                 } catch (DatabaseException e) {
101                                         throw new RuntimeDatabaseException(e);
102                                 }
103                                 return true;
104                         }
105                         
106                 });
107                 
108                 if(!errors.isEmpty()) {
109                         ArrayList<String> sorted = new ArrayList<String>(errors);
110                         Collections.sort(sorted);
111                         StringBuilder message = new StringBuilder();
112                         message.append("Errors in exported model:\n");
113                         for(String error : sorted) {
114                                 message.append(error);
115                                 message.append("\n");
116                         }
117                         throw new DatabaseException(message.toString());
118                 }
119                 
120                 this.resourceCount = state.id;
121                 
122                 state.extensions.put(ExternalDownloadBean.EXTENSION_KEY, new Variant(ExternalDownloadBean.BINDING, new ExternalDownloadBean(downloads)));
123                 
124         }
125
126         int indent = 0;
127         
128         public boolean validateExternal(Resource ext) {
129                 if(configuration.validate) {
130                         ExtentStatus status = configuration.preStatus.get(ext);
131                         if(status != null) {
132                                 if(ExtentStatus.INTERNAL.equals(status)) return false;
133                                 else if(ExtentStatus.EXCLUDED.equals(status)) return false;
134                         }
135                 }
136                 return true;
137         }
138         
139         private Resource getResource(ReadGraph graph, int r) throws DatabaseException {
140                 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
141                 return ss.getResource(r);
142         }
143         
144         final public int getExistingId(ReadGraph graph, int r) throws DatabaseException {
145                 
146                 int ret = state.ids.get(r);
147                 if(ret != -1) {
148                         return ret;
149                 } else {
150                         SerialisationSupport ss = graph.getService(SerialisationSupport.class);
151                         throw new DatabaseException("Id has not been created for " + NameUtils.getSafeName(graph, ss.getResource(r)));
152                 }
153
154         }
155         
156         /*
157          * 
158          * @return -2 if r is not really external and the statement should be excluded
159          * 
160          */
161         public int getId(ReadGraph graph, int r, Collection<String> errors) throws DatabaseException {
162
163 //              // First external is root library
164 //              if(r == rootId) return internalCount;
165                 
166                 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
167                 Layer0 L0 = Layer0.getInstance(graph);
168                 
169                 if(state.ids.containsKey(r)) {
170                     int ret = state.ids.get(r);
171                     if(ret == -1) {
172                         for(int i=0;i<=indent;++i)
173                             System.out.print("  ");
174                         System.out.println("Cycle!!!"); // with " + GraphUtils.getReadableName(g, r));
175                     }
176                         return ret;
177                 }
178                 else {
179                         Resource res = getResource(graph, r);
180             if(!validateExternal(res)) {
181                 errors.add("Illegal reference to " + graph.getPossibleURI(getResource(graph, r)));
182                 return -2;
183             }
184                         Collection<Resource> parents = graph.getObjects(res, L0.PartOf);                        
185                         if(parents.size() != 1) {
186                                 throw new ValidationException("Reference to external resource " 
187                                                 + NameUtils.getSafeName(graph, getResource(graph, r), true) + " without unique uri (" + parents.size() + " parents).");
188                         }
189                         int pid = 0;
190                         for(Resource p : parents) {
191                             ++indent;
192                             pid = getId(graph, ss.getTransientId(p), errors);
193                             if(pid == -2) {
194                         errors.add("Illegal reference to " + graph.getPossibleURI(getResource(graph, r)));
195                                 return -2;
196                             }
197                         }
198                         --indent;
199                         String name = graph.getRelatedValue(res, L0.HasName);
200                         // Record the external entry
201                         externalParents.add(pid);
202             externalNames.add(name);
203                         state.ids.put(r, state.id);
204                         // Ensure that this resource is included into the set of externals to maintain the total number of externals 
205                         state.externals.add(r);
206                         String download = graph.getPossibleRelatedValue(res, L0.Ontology_download, Bindings.STRING);
207                         if(download != null) {
208                                 String uri = graph.getURI(res);
209                                 downloads.put(uri, download);
210                         }
211                         return state.id++;
212                 }
213         }
214         
215         @Override
216         public DataContainer getHeader() throws Exception {
217             return null;
218         }
219         
220         @Override
221         public int getResourceCount() {
222                 return resourceCount;
223         }
224         
225         @Override
226         public int getIdentityCount() {
227                 return configuration.roots.size() + state.externals.size() + 1;
228         }
229         
230         @Override
231         public int getStatementCount() {
232                 return state.statementCount;
233         }
234         
235         @Override
236         public int getValueCount() {
237                 return state.valueCount;
238         }
239
240         @Override
241         public void forStatements(ReadGraph graph, TransferableGraphSourceProcedure<int[]> procedure) throws Exception {
242                 
243                 int[] value = new int[4];
244                 long length = state.otherStatementsInput.length();
245                 state.otherStatementsInput.position(0);
246                 
247                 while(state.otherStatementsInput.position() < length && !state.monitor.isCanceled()) {
248                         
249                         int s = state.otherStatementsInput.readInt();
250                         int subjectId = state.ids.get(s);
251                         
252                         boolean exclude = subjectId == -1;
253                         
254                         int size = state.otherStatementsInput.readInt();
255                         for(int i=0;i<size;i++) {
256                                 int p = state.otherStatementsInput.readInt();
257                                 int o = state.otherStatementsInput.readInt();
258                                 if(!exclude) {
259                                         if(state.pending.contains(o)) {
260                                                 System.err.println("excluding garbage statement " + s + " " + p + " " + o + ", object resource is garbage");
261                                         } else if(state.excludedShared.contains(o)) {
262                                                 System.err.println("excluding shared " + s + " " + p + " " + o);
263                                         } else {
264                                                 
265                                                 int objectId = getExistingId(graph, o);
266                                                 // The statement can be denied still
267                                                 if(objectId != -2) {
268                                                         value[0] = subjectId;
269                                                         value[1] = getExistingId(graph, p);
270                                                         int inverse = state.inverses.get(p);
271                                                         if(inverse != 0) {
272                                                                 value[2] = getExistingId(graph, inverse);
273                                                         } else {
274                                                                 value[2] = -1;
275                                                         }
276                                                         value[3] = objectId;
277
278                                                         procedure.execute(value);
279                                                         
280                                                 } else {
281                                                         System.err.println("Denied (" + NameUtils.getSafeName(graph, getResource(graph, s)) + ", " + NameUtils.getSafeName(graph, getResource(graph, p)) + "," + NameUtils.getSafeName(graph, getResource(graph, o)) + ")");
282                                                 }                                               
283                                                 
284                                         }
285                                 } else {
286                                         System.err.println("excluding shared " + s);
287                                 }
288                         }
289                 }
290         }
291
292         @Override
293         public void forValues(ReadGraph graph, TransferableGraphSourceProcedure<Value> procedure) throws Exception {
294
295                 Serializer variantSerializer = graph.getService(Databoard.class).getSerializerUnchecked(Bindings.VARIANT);
296                 List<Object> idContext = new ArrayList<>();
297                 long length = state.valueInput.length();
298                 state.valueInput.position(0);
299
300                 while(state.valueInput.position() < length && !state.monitor.isCanceled()) {
301
302                         // Ignore value type tag
303                         int s = state.valueInput.readInt();
304                         byte valueType = state.valueInput.readByte();
305                         switch (valueType) {
306                         case TAG_RAW_COPY_VARIANT_VALUE:
307                                 state.valueInput.readInt();
308                                 // Intentional fallthrough.
309
310                         case TAG_POTENTIALLY_MODIFIED_VARIANT_VALUE: {
311                                 idContext.clear();
312                                 Variant variant = (Variant) variantSerializer.deserialize((DataInput)state.valueInput, idContext);
313                                 if (valueModifier.mayNeedModification(variant.type())) {
314                                         Object currentObject = variant.getValue();
315                                         Object newObject = valueModifier.modify(state, variant.getBinding(), currentObject);
316                                         if (newObject != currentObject)
317                                                 variant = new Variant(variant.getBinding(), newObject);
318                                 }
319                                 procedure.execute(new Value(state.ids.get(s), variant));
320                                 break;
321                         }
322
323                         default:
324                                 throw new IllegalArgumentException("Unrecognized variant value type encountered: " + valueType);
325                         }
326                 }
327         }
328
329     @Override
330     public void forValues2(ReadGraph graph, TransferableGraphSourceValueProcedure procedure) throws Exception {
331
332         Serializer datatypeSerializer = graph.getService(Databoard.class).getSerializerUnchecked(Bindings.getBindingUnchecked(Datatype.class));
333         List<Object> idContext = new ArrayList<>();
334         long length = state.valueInput.length();
335         state.valueInput.position(0);
336
337         while(state.valueInput.position() < length && !state.monitor.isCanceled()) {
338
339             int s = state.valueInput.readInt();
340             byte valueType = state.valueInput.readByte();
341
342             switch (valueType) {
343             case TAG_RAW_COPY_VARIANT_VALUE: {
344                 // Variant data could be copied raw, no need for modifications
345                 // Note that the variant data is a concatenation of the
346                 // variant datatype serialization and the value serialization.
347                 // variantLength contains both datatype and value data.
348                 int variantLength = state.valueInput.readInt();
349                 procedure.rawCopy(state.ids.get(s), variantLength, state.valueInput);
350                 break;
351             }
352
353             case TAG_POTENTIALLY_MODIFIED_VARIANT_VALUE: {
354                 // Variant data may need to be modified.
355                 // Cannot optimize this case with raw copying.
356                 idContext.clear();
357                 Datatype type = (Datatype)datatypeSerializer.deserialize((DataInput)state.valueInput, idContext);
358
359                 if (valueModifier.mayNeedModification(type)) {
360                     Binding binding = Bindings.getBinding(type);
361                     Serializer serializer = Bindings.getSerializerUnchecked(binding);
362                     Object value = serializer.deserialize((DataInput)state.valueInput);
363                     value = valueModifier.modify(state, binding, value);
364                     byte[] bytes = serializer.serialize(value);
365                     procedure.execute(state.ids.get(s), type, new ByteBufferReadable(bytes));
366                 } else {
367                     procedure.execute(state.ids.get(s), type, state.valueInput);
368                 }
369                 break;
370             }
371
372             default:
373                 throw new IllegalArgumentException("Unrecognized variant value type encountered: " + valueType);
374             }
375         }
376     }
377
378         protected Identity getRootIdentity(DomainProcessorState state, SerialisationSupport support, Resource rootLibrary) throws DatabaseException {
379                 return new Identity(state.ids.get(support.getTransientId(rootLibrary)), new External(-1, ""));
380         }
381
382         @Override
383         public void forIdentities(ReadGraph graph, TransferableGraphSourceProcedure<Identity> procedure) throws Exception {
384                 
385                 SerialisationSupport support = graph.getService(SerialisationSupport.class);
386                 Layer0 L0 = Layer0.getInstance(graph);
387
388                 // TODO: this should be Root with name ""
389                 procedure.execute(getRootIdentity(state, support, graph.getRootLibrary()));
390
391                 // Declare internal and external roots
392                 for(RootSpec r : configuration.roots) {
393                         Resource type = graph.getPossibleType(r.resource, L0.Entity);
394                         if(type == null) type = L0.Entity;
395                         procedure.execute(new Identity(
396                                         state.ids.get(support.getTransientId(r.resource)),
397                                         new Root(r.name, graph.getURI(type))));
398                 }
399
400                 for(int i = 0; i < state.externals.size() ; i++) {
401                         int parent = externalParents.get(i);
402                         String name = externalNames.get(i);
403                         procedure.execute(new Identity(externalBase + i, new External(parent,name)));
404         }
405         
406         }
407
408         @Override
409         public TreeMap<String, Variant> getExtensions() {
410                 return state.extensions;
411         }
412
413         public File[] getFiles() {
414             return files;
415         }
416
417         private static <T> T tryClose(T c) throws IOException {
418                 if (c != null && c instanceof Closeable)
419                         ((Closeable) c).close();
420                 return null;
421         }
422
423         public void closeStreams() throws IOException {
424                 state.valueInput = tryClose(state.valueInput);
425                 state.otherStatementsInput = tryClose(state.otherStatementsInput);
426                 state.statementsOutput = tryClose(state.statementsOutput);
427                 state.valueOutput = tryClose(state.valueOutput);
428         }
429
430         @Override
431         public void reset() throws Exception {
432             throw new UnsupportedOperationException();
433         }
434         
435         public long[] getResourceArray(ReadGraph graph) throws DatabaseException {
436                 final SerialisationSupport ss = graph.getService(SerialisationSupport.class);
437                 final long[] result = new long[state.ids.size()];
438                 state.ids.forEachEntry(new TIntIntProcedure() {
439
440                         @Override
441                         public boolean execute(int a, int b) {
442                                 
443                                 try {
444                                         Resource r = ss.getResource(a);
445                                         result[b] = r.getResourceId();
446                                 } catch (DatabaseException e) {
447                                         e.printStackTrace();
448                                 }
449                                 
450                                 return true;
451                                 
452                         }
453                 });
454                 return result;
455         }
456         
457         public DomainProcessorState getState() {
458                 return state;
459         }
460         
461         public void forResourceStatements(ReadGraph graph, TransferableGraphSourceProcedure<int[]> procedure) throws Exception {
462                 
463                 int[] value = new int[4];
464                 long length = state.otherStatementsInput.length();
465                 state.otherStatementsInput.position(0);
466                 
467                 while(state.otherStatementsInput.position() < length) {
468                         
469                         int s = state.otherStatementsInput.readInt();
470                         int subjectId = state.ids.get(s);
471                         
472                         boolean exclude = subjectId == -1;
473                         
474                         int size = state.otherStatementsInput.readInt();
475                         for(int i=0;i<size;i++) {
476                                 int p = state.otherStatementsInput.readInt();
477                                 int o = state.otherStatementsInput.readInt();
478                                 if(!exclude) {
479                                         if(state.excludedShared.contains(o)) {
480                                                 System.err.println("excluding shared " + s + " " + p + " " + o);
481                                         } else {
482                                                 
483                                                 int objectId = getExistingId(graph, o);
484                                                 // The statement can be denied still
485                                                 if(objectId != -2) {
486                                                         value[0] = s;
487                                                         value[1] = p;
488                                                         int inverse = state.inverses.get(p);
489                                                         if(inverse != 0) {
490                                                                 value[2] = inverse;
491                                                         } else {
492                                                                 value[2] = -1;
493                                                         }
494                                                         value[3] = o;
495
496                                                         procedure.execute(value);
497                                                         
498                                                 } else {
499                                                         System.err.println("Denied (" + NameUtils.getSafeName(graph, getResource(graph, s)) + ", " + NameUtils.getSafeName(graph, getResource(graph, p)) + "," + NameUtils.getSafeName(graph, getResource(graph, o)) + ")");
500                                                 }
501                                                 
502                                         }
503                                 } else {
504                                         System.err.println("excluding shared " + s);
505                                 }
506                         }
507                 }
508         }
509
510         public void forValueResources(ReadGraph graph, TransferableGraphSourceProcedure<int[]> procedure) throws Exception {
511                 int[] value = { 0 };
512                 long length = state.valueInput.length();
513                 while (state.valueInput.position() < length) {
514                         value[0] = state.valueInput.readInt();
515                         procedure.execute(value);
516                 }
517         }
518
519         public TransferableGraphConfiguration2 getConfiguration() {
520                 return configuration;
521         }
522
523         @Override
524         public void close() throws IOException {
525                 synchronized (this) {
526                         if (closed)
527                                 return;
528                         closed = true;
529                 }
530                 closeStreams();
531                 if (files != null) {
532                         for (File f : files) {
533                                 Files.deleteIfExists(f.toPath());
534                         }
535                 }
536         }
537
538 }