source: josm/trunk/test/functional/org/openstreetmap/josm/io/OsmServerBackreferenceReaderTest.java@ 17275

Last change on this file since 17275 was 17275, checked in by Don-vip, 3 years ago

see #16567 - upgrade almost all tests to JUnit 5, except those depending on WiremockRule

See https://github.com/tomakehurst/wiremock/issues/684

  • Property svn:eol-style set to native
File size: 23.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.junit.jupiter.api.Assertions.assertEquals;
5import static org.junit.jupiter.api.Assertions.assertFalse;
6import static org.junit.jupiter.api.Assertions.assertNotNull;
7import static org.junit.jupiter.api.Assertions.assertTrue;
8import static org.junit.jupiter.api.Assertions.fail;
9import static org.junit.jupiter.api.Assumptions.assumeTrue;
10
11import java.io.File;
12import java.io.FileInputStream;
13import java.io.FileNotFoundException;
14import java.io.FileOutputStream;
15import java.io.IOException;
16import java.io.OutputStreamWriter;
17import java.io.PrintWriter;
18import java.nio.charset.StandardCharsets;
19import java.text.MessageFormat;
20import java.util.HashSet;
21import java.util.Locale;
22import java.util.Set;
23import java.util.logging.Logger;
24
25import org.junit.jupiter.api.BeforeAll;
26import org.junit.jupiter.api.BeforeEach;
27import org.junit.jupiter.api.Test;
28import org.openstreetmap.josm.JOSMFixture;
29import org.openstreetmap.josm.TestUtils;
30import org.openstreetmap.josm.data.APIDataSet;
31import org.openstreetmap.josm.data.coor.LatLon;
32import org.openstreetmap.josm.data.osm.Changeset;
33import org.openstreetmap.josm.data.osm.CyclicUploadDependencyException;
34import org.openstreetmap.josm.data.osm.DataSet;
35import org.openstreetmap.josm.data.osm.Node;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
38import org.openstreetmap.josm.data.osm.Relation;
39import org.openstreetmap.josm.data.osm.RelationMember;
40import org.openstreetmap.josm.data.osm.Way;
41import org.openstreetmap.josm.data.projection.ProjectionRegistry;
42import org.openstreetmap.josm.data.projection.Projections;
43import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
44import org.openstreetmap.josm.spi.preferences.Config;
45import org.openstreetmap.josm.tools.Logging;
46
47import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
48
49/**
50 * Reads primitives referring to a particular primitive (ways including a node, relations referring to a relation)
51 * @since 1806
52 */
53@SuppressFBWarnings(value = "CRLF_INJECTION_LOGS")
54class OsmServerBackreferenceReaderTest {
55 private static final Logger logger = Logger.getLogger(OsmServerBackreferenceReader.class.getName());
56
57 protected static Node lookupNode(DataSet ds, int i) {
58 for (Node n : ds.getNodes()) {
59 if (("node-" + i).equals(n.get("name"))) return n;
60 }
61 fail("Cannot find node "+i);
62 return null;
63 }
64
65 protected static Way lookupWay(DataSet ds, int i) {
66 for (Way w : ds.getWays()) {
67 if (("way-" + i).equals(w.get("name"))) return w;
68 }
69 fail("Cannot find way "+i);
70 return null;
71 }
72
73 protected static Relation lookupRelation(DataSet ds, int i) {
74 for (Relation r : ds.getRelations()) {
75 if (("relation-" + i).equals(r.get("name"))) return r;
76 }
77 fail("Cannot find relation "+i);
78 return null;
79 }
80
81 protected static void populateTestDataSetWithNodes(DataSet ds) {
82 for (int i = 0; i < 100; i++) {
83 Node n = new Node();
84 n.setCoor(new LatLon(-36.6, 47.6));
85 n.put("name", "node-"+i);
86 ds.addPrimitive(n);
87 }
88 }
89
90 protected static void populateTestDataSetWithWays(DataSet ds) {
91 for (int i = 0; i < 20; i++) {
92 Way w = new Way();
93 for (int j = 0; j < 10; j++) {
94 w.addNode(lookupNode(ds, i+j));
95 }
96 w.put("name", "way-"+i);
97 ds.addPrimitive(w);
98 }
99 }
100
101 protected static void populateTestDataSetWithRelations(DataSet ds) {
102 for (int i = 0; i < 10; i++) {
103 Relation r = new Relation();
104 r.put("name", "relation-" +i);
105 for (int j = 0; j < 10; j++) {
106 RelationMember member = new RelationMember("node-" + j, lookupNode(ds, i + j));
107 r.addMember(member);
108 }
109 for (int j = 0; j < 5; j++) {
110 RelationMember member = new RelationMember("way-" + j, lookupWay(ds, i + j));
111 r.addMember(member);
112 }
113 if (i > 5) {
114 for (int j = 0; j < 3; j++) {
115 RelationMember member = new RelationMember("relation-" + j, lookupRelation(ds, j));
116 logger.info(MessageFormat.format("adding relation {0} to relation {1}", j, i));
117 r.addMember(member);
118 }
119 }
120 ds.addPrimitive(r);
121 }
122 }
123
124 protected static DataSet buildTestDataSet() {
125 DataSet ds = new DataSet();
126 ds.setVersion("0.6");
127
128 populateTestDataSetWithNodes(ds);
129 populateTestDataSetWithWays(ds);
130 populateTestDataSetWithRelations(ds);
131 return ds;
132 }
133
134 /**
135 * creates the dataset on the server.
136 *
137 * @param ds the data set
138 * @throws OsmTransferException if something goes wrong
139 * @throws CyclicUploadDependencyException if a cyclic dependency is detected
140 */
141 public static void createDataSetOnServer(APIDataSet ds) throws OsmTransferException, CyclicUploadDependencyException {
142 logger.info("creating data set on the server ...");
143 ds.adjustRelationUploadOrder();
144 OsmServerWriter writer = new OsmServerWriter();
145 Changeset cs = new Changeset();
146 writer.uploadOsm(
147 new UploadStrategySpecification().setStrategy(UploadStrategy.SINGLE_REQUEST_STRATEGY),
148 ds.getPrimitives(), cs, NullProgressMonitor.INSTANCE);
149 OsmApi.getOsmApi().closeChangeset(cs, NullProgressMonitor.INSTANCE);
150 }
151
152 static DataSet testDataSet;
153
154 /**
155 * Setup test.
156 * @throws OsmTransferException if something goes wrong
157 * @throws CyclicUploadDependencyException if a cyclic dependency is detected
158 * @throws IOException if an I/O error occurs
159 */
160 @BeforeAll
161 public static void setUpBeforeClass() throws OsmTransferException, CyclicUploadDependencyException, IOException {
162 if (!TestUtils.areCredentialsProvided()) {
163 logger.severe("OSM DEV API credentials not provided. Please define them with -Dosm.username and -Dosm.password");
164 return;
165 }
166 logger.info("initializing ...");
167
168 JOSMFixture.createFunctionalTestFixture().init();
169
170 Config.getPref().put("osm-server.auth-method", "basic");
171
172 // don't use atomic upload, the test API server can't cope with large diff uploads
173 //
174 Config.getPref().putBoolean("osm-server.atomic-upload", false);
175 ProjectionRegistry.setProjection(Projections.getProjectionByCode("EPSG:3857")); // Mercator
176 Logging.setLogLevel(Logging.LEVEL_DEBUG);
177
178 File dataSetCacheOutputFile = new File(System.getProperty("java.io.tmpdir"),
179 MultiFetchServerObjectReaderTest.class.getName() + ".dataset");
180
181 String p = System.getProperty("useCachedDataset");
182 if (p != null && Boolean.parseBoolean(p.trim().toLowerCase(Locale.ENGLISH))) {
183 logger.info(MessageFormat.format("property ''{0}'' set, using cached dataset", "useCachedDataset"));
184 return;
185 }
186
187 logger.info(MessageFormat.format(
188 "property ''{0}'' not set to true, creating test dataset on the server. property is ''{1}''", "useCachedDataset", p));
189
190 // build and upload the test data set
191 //
192 logger.info("creating test data set ....");
193 testDataSet = buildTestDataSet();
194 logger.info("uploading test data set ...");
195 createDataSetOnServer(new APIDataSet(testDataSet));
196
197 try (
198 PrintWriter pw = new PrintWriter(
199 new OutputStreamWriter(new FileOutputStream(dataSetCacheOutputFile), StandardCharsets.UTF_8)
200 )) {
201 logger.info(MessageFormat.format("caching test data set in ''{0}'' ...", dataSetCacheOutputFile.toString()));
202 try (OsmWriter w = new OsmWriter(pw, false, testDataSet.getVersion())) {
203 w.header();
204 w.writeDataSources(testDataSet);
205 w.writeContent(testDataSet);
206 w.footer();
207 }
208 }
209 }
210
211 private DataSet ds;
212
213 /**
214 * Setup test.
215 * @throws IOException if any I/O error occurs
216 * @throws IllegalDataException if an error was found while parsing the OSM data
217 * @throws FileNotFoundException if the dataset file cannot be found
218 */
219 @BeforeEach
220 public void setUp() throws IOException, IllegalDataException, FileNotFoundException {
221 if (!TestUtils.areCredentialsProvided()) {
222 return;
223 }
224 File f = new File(System.getProperty("java.io.tmpdir"), MultiFetchServerObjectReaderTest.class.getName() + ".dataset");
225 logger.info(MessageFormat.format("reading cached dataset ''{0}''", f.toString()));
226 ds = new DataSet();
227 try (FileInputStream fis = new FileInputStream(f)) {
228 ds = OsmReader.parseDataSet(fis, NullProgressMonitor.INSTANCE);
229 }
230 }
231
232 /**
233 * Test reading references for a node.
234 * @throws OsmTransferException if an error occurs
235 */
236 @Test
237 void testBackreferenceForNode() throws OsmTransferException {
238 assumeTrue(TestUtils.areCredentialsProvided());
239 Node n = lookupNode(ds, 0);
240 assertNotNull(n);
241 Way w = lookupWay(ds, 0);
242 assertNotNull(w);
243
244 DataSet referrers = new OsmServerBackreferenceReader(n).setReadFull(false).parseOsm(NullProgressMonitor.INSTANCE);
245 printNumberOfPrimitives(referrers);
246
247 Set<Long> expectedNodeIds = new HashSet<>();
248 Set<Long> expectedWayIds = new HashSet<>();
249 Set<Long> expectedRelationIds = new HashSet<>();
250
251 for (OsmPrimitive ref : n.getReferrers()) {
252 if (ref instanceof Way) {
253 expectedWayIds.add(ref.getId());
254 expectedNodeIds.addAll(getNodeIdsInWay((Way) ref));
255 } else if (ref instanceof Relation) {
256 expectedRelationIds.add(ref.getId());
257 expectedWayIds.addAll(getWayIdsInRelation((Relation) ref, false));
258 expectedNodeIds.addAll(getNodeIdsInRelation((Relation) ref, false));
259 }
260 }
261
262 assertEquals(expectedNodeIds.size(), referrers.getNodes().size());
263 assertEquals(expectedWayIds.size(), referrers.getWays().size());
264 assertEquals(expectedRelationIds.size(), referrers.getRelations().size());
265
266 for (Node node : referrers.getNodes()) {
267 assertTrue(expectedNodeIds.contains(node.getId()));
268 assertFalse(node.isIncomplete());
269 }
270
271 for (Way way : referrers.getWays()) {
272 assertTrue(expectedWayIds.contains(way.getId()));
273 assertEquals(n.getReferrers().contains(way), !way.isIncomplete());
274 }
275
276 for (Relation relation : referrers.getRelations()) {
277 assertTrue(expectedRelationIds.contains(relation.getId()));
278 assertFalse(relation.isIncomplete());
279 }
280 }
281
282 private void printNumberOfPrimitives(DataSet referrers) {
283 System.out.println("#nodes=" + referrers.getNodes().size() +
284 " #ways=" + referrers.getWays().size() +
285 " #relations=" + referrers.getRelations().size());
286 }
287
288 /**
289 * Test reading full references for a node.
290 * @throws OsmTransferException if an error occurs
291 */
292 @Test
293 void testBackreferenceForNodeFull() throws OsmTransferException {
294 assumeTrue(TestUtils.areCredentialsProvided());
295 Node n = lookupNode(ds, 0);
296 assertNotNull(n);
297
298 DataSet referrers = new OsmServerBackreferenceReader(n).setReadFull(true).parseOsm(NullProgressMonitor.INSTANCE);
299 printNumberOfPrimitives(referrers);
300
301 Set<Long> expectedNodeIds = new HashSet<>();
302 Set<Long> expectedWayIds = new HashSet<>();
303 Set<Long> expectedRelationIds = new HashSet<>();
304 for (OsmPrimitive ref : n.getReferrers()) {
305 if (ref instanceof Way) {
306 expectedWayIds.add(ref.getId());
307 expectedNodeIds.addAll(getNodeIdsInWay((Way) ref));
308 } else if (ref instanceof Relation) {
309 expectedRelationIds.add(ref.getId());
310 expectedWayIds.addAll(getWayIdsInRelation((Relation) ref, true));
311 expectedNodeIds.addAll(getNodeIdsInRelation((Relation) ref, true));
312 }
313 }
314
315 assertEquals(expectedNodeIds.size(), referrers.getNodes().size());
316 assertEquals(expectedWayIds.size(), referrers.getWays().size());
317 assertEquals(expectedRelationIds.size(), referrers.getRelations().size());
318
319 for (Node node : referrers.getNodes()) {
320 assertTrue(expectedNodeIds.contains(node.getId()));
321 assertFalse(node.isIncomplete());
322 }
323
324 for (Way way : referrers.getWays()) {
325 assertTrue(expectedWayIds.contains(way.getId()));
326 assertFalse(way.isIncomplete());
327 }
328
329 for (Relation relation : referrers.getRelations()) {
330 assertTrue(expectedRelationIds.contains(relation.getId()));
331 assertFalse(relation.isIncomplete());
332 }
333 }
334
335 /**
336 * Test reading references for a way.
337 * @throws OsmTransferException if an error occurs
338 */
339 @Test
340 void testBackreferenceForWay() throws OsmTransferException {
341 assumeTrue(TestUtils.areCredentialsProvided());
342 Way w = lookupWay(ds, 1);
343 assertNotNull(w);
344 // way with name "way-1" is referred to by two relations
345 //
346
347 DataSet referrers = new OsmServerBackreferenceReader(w).setReadFull(false).parseOsm(NullProgressMonitor.INSTANCE);
348 printNumberOfPrimitives(referrers);
349
350 Set<Long> expectedNodeIds = new HashSet<>();
351 Set<Long> expectedWayIds = new HashSet<>();
352 Set<Long> expectedRelationIds = new HashSet<>();
353
354 for (OsmPrimitive ref : w.getReferrers()) {
355 if (ref instanceof Relation) {
356 expectedRelationIds.add(ref.getId());
357 expectedWayIds.addAll(getWayIdsInRelation((Relation) ref, false));
358 expectedNodeIds.addAll(getNodeIdsInRelation((Relation) ref, false));
359 }
360 }
361
362 assertEquals(expectedNodeIds.size(), referrers.getNodes().size());
363 assertEquals(expectedWayIds.size(), referrers.getWays().size());
364 assertEquals(expectedRelationIds.size(), referrers.getRelations().size());
365
366 for (Way w1 : referrers.getWays()) {
367 assertTrue(w1.isIncomplete());
368 }
369 assertEquals(2, referrers.getRelations().size()); // two relations referring to w
370
371 Relation r = lookupRelation(referrers, 0);
372 assertNotNull(r);
373 assertFalse(r.isIncomplete());
374 r = lookupRelation(referrers, 1);
375 assertFalse(r.isIncomplete());
376 }
377
378 /**
379 * Test reading full references for a way.
380 * @throws OsmTransferException if an error occurs
381 */
382 @Test
383 void testBackreferenceForWayFull() throws OsmTransferException {
384 assumeTrue(TestUtils.areCredentialsProvided());
385 Way w = lookupWay(ds, 1);
386 assertNotNull(w);
387 // way with name "way-1" is referred to by two relations
388 //
389
390 DataSet referrers = new OsmServerBackreferenceReader(w).setReadFull(true).parseOsm(NullProgressMonitor.INSTANCE);
391 assertEquals(6, referrers.getWays().size()); // 6 ways referred by two relations
392 for (Way w1 : referrers.getWays()) {
393 assertFalse(w1.isIncomplete());
394 }
395 assertEquals(2, referrers.getRelations().size()); // two relations referring to
396 Set<Long> expectedNodeIds = new HashSet<>();
397 for (Way way : referrers.getWays()) {
398 Way orig = (Way) ds.getPrimitiveById(way);
399 for (Node n : orig.getNodes()) {
400 expectedNodeIds.add(n.getId());
401 }
402 }
403 assertEquals(expectedNodeIds.size(), referrers.getNodes().size());
404 for (Node n : referrers.getNodes()) {
405 assertTrue(expectedNodeIds.contains(n.getId()));
406 }
407
408 Relation r = lookupRelation(referrers, 0);
409 assertNotNull(r);
410 assertFalse(r.isIncomplete());
411 r = lookupRelation(referrers, 1);
412 assertFalse(r.isIncomplete());
413 }
414
415 /**
416 * Test reading references for a relation.
417 * @throws OsmTransferException if an error occurs
418 */
419 @Test
420 void testBackreferenceForRelation() throws OsmTransferException {
421 assumeTrue(TestUtils.areCredentialsProvided());
422 Relation r = lookupRelation(ds, 1);
423 assertNotNull(r);
424 // way with name "relation-1" is referred to by four relations:
425 // relation-6, relation-7, relation-8, relation-9
426 //
427
428 DataSet referrers = new OsmServerBackreferenceReader(r).setReadFull(false).parseOsm(NullProgressMonitor.INSTANCE);
429 printNumberOfPrimitives(referrers);
430
431 Set<Long> referringRelationsIds = new HashSet<>();
432 Relation r6 = lookupRelation(referrers, 6);
433 assertNotNull(r6);
434 assertFalse(r6.isIncomplete());
435 referringRelationsIds.add(r6.getId());
436 Relation r7 = lookupRelation(referrers, 7);
437 assertNotNull(r7);
438 assertFalse(r7.isIncomplete());
439 referringRelationsIds.add(r7.getId());
440 Relation r8 = lookupRelation(referrers, 8);
441 assertNotNull(r8);
442 assertFalse(r8.isIncomplete());
443 referringRelationsIds.add(r8.getId());
444 Relation r9 = lookupRelation(referrers, 9);
445 assertNotNull(r9);
446 assertFalse(r9.isIncomplete());
447 referringRelationsIds.add(r9.getId());
448
449 for (Relation r1 : referrers.getRelations()) {
450 if (!referringRelationsIds.contains(r1.getId())) {
451 assertTrue(r1.isIncomplete());
452 }
453 }
454
455 // make sure we read all ways referred to by parent relations. These
456 // ways are incomplete after reading.
457 //
458 Set<Long> expectedWayIds = new HashSet<>();
459 for (RelationMember m : lookupRelation(ds, 6).getMembers()) {
460 if (m.isWay()) {
461 expectedWayIds.add(m.getMember().getId());
462 }
463 }
464 for (RelationMember m : lookupRelation(ds, 7).getMembers()) {
465 if (m.isWay()) {
466 expectedWayIds.add(m.getMember().getId());
467 }
468 }
469 for (RelationMember m : lookupRelation(ds, 8).getMembers()) {
470 if (m.isWay()) {
471 expectedWayIds.add(m.getMember().getId());
472 }
473 }
474 for (RelationMember m : lookupRelation(ds, 9).getMembers()) {
475 if (m.isWay()) {
476 expectedWayIds.add(m.getMember().getId());
477 }
478 }
479
480 assertEquals(expectedWayIds.size(), referrers.getWays().size());
481 for (Way w1 : referrers.getWays()) {
482 assertTrue(expectedWayIds.contains(w1.getId()));
483 assertTrue(w1.isIncomplete());
484 }
485
486 // make sure we read all nodes referred to by parent relations.
487 Set<Long> expectedNodeIds = new HashSet<>();
488 for (OsmPrimitive ref : r.getReferrers()) {
489 if (ref instanceof Relation) {
490 expectedNodeIds.addAll(getNodeIdsInRelation((Relation) ref, false));
491 }
492 }
493 assertEquals(expectedNodeIds.size(), referrers.getNodes().size());
494 }
495
496 protected static Set<Long> getNodeIdsInWay(Way way) {
497 HashSet<Long> ret = new HashSet<>();
498 if (way == null) return ret;
499 for (Node n: way.getNodes()) {
500 ret.add(n.getId());
501 }
502 return ret;
503 }
504
505 protected static Set<Long> getNodeIdsInRelation(Relation r, boolean children) {
506 HashSet<Long> ret = new HashSet<>();
507 if (r == null) return ret;
508 for (RelationMember m: r.getMembers()) {
509 if (m.isNode()) {
510 ret.add(m.getMember().getId());
511 } else if (m.isWay() && children) {
512 ret.addAll(getNodeIdsInWay(m.getWay()));
513 } else if (m.isRelation() && children) {
514 ret.addAll(getNodeIdsInRelation(m.getRelation(), true));
515 }
516 }
517 return ret;
518 }
519
520 protected static Set<Long> getWayIdsInRelation(Relation r, boolean children) {
521 HashSet<Long> ret = new HashSet<>();
522 if (r == null) return ret;
523 for (RelationMember m: r.getMembers()) {
524 if (m.isWay()) {
525 ret.add(m.getMember().getId());
526 } else if (m.isRelation() && children) {
527 ret.addAll(getWayIdsInRelation(m.getRelation(), true));
528 }
529 }
530 return ret;
531 }
532
533 /**
534 * Test reading full references for a relation.
535 * @throws OsmTransferException if an error occurs
536 */
537 @Test
538 void testBackreferenceForRelationFull() throws OsmTransferException {
539 assumeTrue(TestUtils.areCredentialsProvided());
540 Relation r = lookupRelation(ds, 1);
541 assertNotNull(r);
542 // way with name "relation-1" is referred to by four relations:
543 // relation-6, relation-7, relation-8, relation-9
544 //
545
546 DataSet referrers = new OsmServerBackreferenceReader(r).setReadFull(true).parseOsm(NullProgressMonitor.INSTANCE);
547
548 r = lookupRelation(referrers, 6);
549 assertNotNull(r);
550 assertFalse(r.isIncomplete());
551 r = lookupRelation(referrers, 7);
552 assertNotNull(r);
553 assertFalse(r.isIncomplete());
554 r = lookupRelation(referrers, 8);
555 assertNotNull(r);
556 assertFalse(r.isIncomplete());
557 r = lookupRelation(referrers, 9);
558 assertNotNull(r);
559 assertFalse(r.isIncomplete());
560
561 // all relations are fully loaded
562 //
563 for (Relation r1 : referrers.getRelations()) {
564 assertFalse(r1.isIncomplete());
565 }
566
567 // make sure we read all ways referred to by parent relations. These
568 // ways are completely read after reading the relations
569 //
570 Set<Long> expectedWayIds = new HashSet<>();
571 for (RelationMember m : lookupRelation(ds, 6).getMembers()) {
572 if (m.isWay()) {
573 expectedWayIds.add(m.getMember().getId());
574 }
575 }
576 for (RelationMember m : lookupRelation(ds, 7).getMembers()) {
577 if (m.isWay()) {
578 expectedWayIds.add(m.getMember().getId());
579 }
580 }
581 for (RelationMember m : lookupRelation(ds, 8).getMembers()) {
582 if (m.isWay()) {
583 expectedWayIds.add(m.getMember().getId());
584 }
585 }
586 for (RelationMember m : lookupRelation(ds, 9).getMembers()) {
587 if (m.isWay()) {
588 expectedWayIds.add(m.getMember().getId());
589 }
590 }
591 for (long id : expectedWayIds) {
592 Way w = (Way) referrers.getPrimitiveById(id, OsmPrimitiveType.WAY);
593 assertNotNull(w);
594 assertFalse(w.isIncomplete());
595 }
596
597 Set<Long> expectedNodeIds = new HashSet<>();
598 for (int i = 6; i < 10; i++) {
599 Relation r1 = lookupRelation(ds, i);
600 expectedNodeIds.addAll(getNodeIdsInRelation(r1, true));
601 }
602
603 assertEquals(expectedNodeIds.size(), referrers.getNodes().size());
604 for (Node n : referrers.getNodes()) {
605 assertTrue(expectedNodeIds.contains(n.getId()));
606 }
607 }
608}
Note: See TracBrowser for help on using the repository browser.