root / trunk / applications / appCatalogYNomenclatorClient / src / es / gva / cit / catalogClient / drivers / CSWCatalogServiceDriver.java @ 2749
History | View | Annotate | Download (17.8 KB)
1 |
package es.gva.cit.catalogClient.drivers; |
---|---|
2 |
|
3 |
import es.gva.cit.catalogClient.metadataXML.XMLTree; |
4 |
import es.gva.cit.catalogClient.metadataXML.XMLTreeNumberOfRecordsAnswer; |
5 |
import es.gva.cit.catalogClient.parsers.SOAPMessageParser; |
6 |
import es.gva.cit.catalogClient.parsers.csw.cswCapabilitiesParser; |
7 |
import es.gva.cit.catalogClient.protocols.HTTPGetProtocol; |
8 |
import es.gva.cit.catalogClient.protocols.HTTPPostProtocol; |
9 |
import es.gva.cit.catalogClient.protocols.SOAPProtocol; |
10 |
import es.gva.cit.catalogClient.querys.CSWQuery; |
11 |
import es.gva.cit.catalogClient.querys.Query; |
12 |
import es.gva.cit.catalogClient.utils.Strings; |
13 |
|
14 |
import org.apache.commons.httpclient.NameValuePair; |
15 |
|
16 |
import org.w3c.dom.Node; |
17 |
|
18 |
import java.net.URL; |
19 |
|
20 |
|
21 |
/* Generated by Together */
|
22 |
public class CSWCatalogServiceDriver extends AbstractCatalogServiceDriver { |
23 |
private String responseHandler = null; |
24 |
private String hopCount = null; |
25 |
private String distributedSearch = null; |
26 |
private String constraint = null; |
27 |
private String[] CONSTRAINTLANGUAGE = null; |
28 |
private String[] elementSetName = null; |
29 |
private String[] typeNames = null; |
30 |
private String[] resultType = null; |
31 |
private String[] NAMESPACE = null; |
32 |
private String ServerProfile = "ebRIM"; //or ISO |
33 |
private String version = "2.0.0"; |
34 |
|
35 |
/*
|
36 |
* (non-Javadoc)
|
37 |
*
|
38 |
* @see ICatalogServerDriver#GetCapabilities(java.lang.String,
|
39 |
* java.lang.String)
|
40 |
*/
|
41 |
public Node[] getCapabilities(URL url) { |
42 |
Node[] nodes = null; |
43 |
|
44 |
System.out.println("**************GET*************"); |
45 |
nodes = new HTTPGetProtocol().doQuery(url, getMessageCapabilities(), 0); |
46 |
|
47 |
if (nodes == null) { |
48 |
System.out.println("**************POST*************"); |
49 |
nodes = new HTTPPostProtocol().doQuery(url,
|
50 |
getPOSTMessageCapabilities(), 0);
|
51 |
|
52 |
if (nodes == null) { |
53 |
System.out.println("**************SOAP*************"); |
54 |
nodes = new SOAPProtocol().doQuery(url,
|
55 |
getSOAPMessageCapabilities(), 0);
|
56 |
|
57 |
if (nodes == null) { |
58 |
return null; |
59 |
} else {
|
60 |
setCommunicationProtocol("SOAP");
|
61 |
} |
62 |
} else {
|
63 |
setCommunicationProtocol("POST");
|
64 |
} |
65 |
} else {
|
66 |
setCommunicationProtocol("GET");
|
67 |
} |
68 |
|
69 |
return nodes;
|
70 |
} |
71 |
|
72 |
/**
|
73 |
* this method implements the describeRecords operation
|
74 |
* @param url
|
75 |
* @return
|
76 |
*/
|
77 |
public Node[] describeRecords(URL url) { |
78 |
//return new HTTPPostProtocol().doQuery(url, getMessageDescribeRecords(),0);
|
79 |
return null; |
80 |
} |
81 |
|
82 |
/*
|
83 |
* (non-Javadoc)
|
84 |
*
|
85 |
* @see ICatalogServerDriver#GetRecords(java.lang.String, java.lang.String,
|
86 |
* java.lang.String, int, int, java.lang.String, java.lang.String)
|
87 |
*/
|
88 |
public Node[] getRecords(URL url, Query query, int firstRecord) { |
89 |
setQuery(query); |
90 |
|
91 |
Node[] answerNodes = null; |
92 |
|
93 |
System.out.println("**************SOAP*************"); |
94 |
|
95 |
Node[] nodes = new SOAPProtocol().doQuery(url, |
96 |
getSOAPMessageRecords(getQuery(), firstRecord), firstRecord); |
97 |
|
98 |
if (nodes == null) { |
99 |
System.out.println("**************POST*************"); |
100 |
nodes = new HTTPPostProtocol().doQuery(url,
|
101 |
getPOSTMessageRecords(getQuery(), firstRecord), firstRecord); |
102 |
|
103 |
if (nodes == null) { |
104 |
System.out.println("**************GET*************"); |
105 |
nodes = new HTTPGetProtocol().doQuery(url,
|
106 |
getMessageRecords(getQuery()), firstRecord); |
107 |
|
108 |
if (nodes == null) { |
109 |
return null; |
110 |
} else {
|
111 |
setCommunicationProtocol("GET");
|
112 |
} |
113 |
} else {
|
114 |
setCommunicationProtocol("POST");
|
115 |
} |
116 |
} else {
|
117 |
setCommunicationProtocol("SOAP");
|
118 |
|
119 |
//We must cut the SOAP Head
|
120 |
nodes[0] = SOAPMessageParser.cutHead(nodes[0]); |
121 |
} |
122 |
|
123 |
|
124 |
int numberOfRecords = getNumberOfRecords(nodes[0]); |
125 |
|
126 |
if (numberOfRecords == -1) { |
127 |
return null; |
128 |
} |
129 |
|
130 |
answerNodes = createAnswerTree(numberOfRecords, firstRecord, nodes[0],
|
131 |
url); |
132 |
|
133 |
return answerNodes;
|
134 |
} |
135 |
|
136 |
/**
|
137 |
* It returns an array of nodes with:
|
138 |
* 1st position -> tree with information of the answer
|
139 |
* Next positions -> One result for each position
|
140 |
* @param numberOfRecords
|
141 |
* Number of records returned by the query
|
142 |
* @param firstRecord
|
143 |
* Number of the first record
|
144 |
* @param node
|
145 |
* Tree returned by the server
|
146 |
* @param url
|
147 |
* Server URL
|
148 |
* @return
|
149 |
*/
|
150 |
public Node[] createAnswerTree(int numberOfRecords, int firstRecord, |
151 |
Node node, URL url) {
|
152 |
Node[] answerNodes = null; |
153 |
|
154 |
if (numberOfRecords > 10) { |
155 |
answerNodes = new Node[11]; |
156 |
} else {
|
157 |
answerNodes = new Node[numberOfRecords + 1]; |
158 |
} |
159 |
|
160 |
answerNodes[0] = XMLTreeNumberOfRecordsAnswer.getNode(numberOfRecords,
|
161 |
firstRecord, firstRecord + numberOfRecords); |
162 |
|
163 |
Node[] auxNodes = cutMetadata(node);
|
164 |
|
165 |
System.out.print(firstRecord);
|
166 |
|
167 |
if (getServerProfile().equals("ebRIM")) { |
168 |
auxNodes = getEbRIMNodes(auxNodes, url); |
169 |
} |
170 |
|
171 |
for (int i = 1; |
172 |
(i <= numberOfRecords) && (i <= 10) &&
|
173 |
(i <= (numberOfRecords - firstRecord + 1)); i++)
|
174 |
answerNodes[i] = auxNodes[i - 1];
|
175 |
|
176 |
return answerNodes;
|
177 |
} |
178 |
|
179 |
/**
|
180 |
* This function retrieve the nodes for one ebRIM answer
|
181 |
* @param nodes
|
182 |
* Nodes of the ebRIM answer
|
183 |
* @param nodes
|
184 |
* Server URL
|
185 |
* @return
|
186 |
* Medatada Nodes.
|
187 |
*/
|
188 |
public Node[] getEbRIMNodes(Node[] nodes, URL url) { |
189 |
Node[] auxNodes = new Node[nodes.length]; |
190 |
|
191 |
for (int i = 0; i < nodes.length; i++) { |
192 |
String id = XMLTree.searchAtribute(nodes[i], "id"); |
193 |
auxNodes[i] = new HTTPGetProtocol().doQuery(url,
|
194 |
getEbRIMRequestParameters(id), 0)[0]; |
195 |
} |
196 |
|
197 |
return auxNodes;
|
198 |
} |
199 |
|
200 |
/**
|
201 |
* It Returns the parameters needed by getExtrinsicContent
|
202 |
* @param id
|
203 |
* Record id
|
204 |
* @return
|
205 |
*/
|
206 |
NameValuePair[] getEbRIMRequestParameters(String id) { |
207 |
NameValuePair nvp1 = new NameValuePair("request", "getExtrinsicContent"); |
208 |
NameValuePair nvp2 = new NameValuePair("id", id); |
209 |
|
210 |
return new NameValuePair[] { nvp1, nvp2 }; |
211 |
} |
212 |
|
213 |
/**
|
214 |
* This function returns the number of records that have been retrieved.
|
215 |
* It Reads a Node value.
|
216 |
* @param node
|
217 |
* The answer tree
|
218 |
* @return
|
219 |
* The number of records
|
220 |
*/
|
221 |
public int getNumberOfRecords(Node node){ |
222 |
int numberOfRecords = getNumberOfRecords(node,"csw:SearchResults","numberOfRecordsMatched"); |
223 |
if (numberOfRecords == -1) |
224 |
numberOfRecords = getNumberOfRecords(node,"SearchResults","numberOfRecordsMatched"); |
225 |
|
226 |
return numberOfRecords;
|
227 |
} |
228 |
|
229 |
|
230 |
/**
|
231 |
* This function finds and separates metadata from a full tree
|
232 |
* @param node
|
233 |
* full tree
|
234 |
* @return
|
235 |
* An array of trees that contain metadata.
|
236 |
*/
|
237 |
public Node[] cutMetadata(Node node) { |
238 |
Node[] auxNodes = XMLTree.searchMultipleNode(node,
|
239 |
"csw:SearchResults->brief:MD_Metadata");
|
240 |
|
241 |
if (auxNodes.length == 0) { |
242 |
auxNodes = XMLTree.searchMultipleNode(node, |
243 |
"SearchResults->ebrim:Organization");
|
244 |
} |
245 |
|
246 |
if (auxNodes.length == 0) { |
247 |
auxNodes = XMLTree.searchMultipleNode(node, |
248 |
"SearchResults->wrs:WRSExtrinsicObject");
|
249 |
} |
250 |
|
251 |
return auxNodes;
|
252 |
} |
253 |
|
254 |
/**
|
255 |
* @return Devuelve los pares con los atributos para hacer la invocaci?n a
|
256 |
* la llamada getCapabilities usando el m?todo de HTTP Get
|
257 |
*/
|
258 |
public NameValuePair[] getMessageCapabilities() { |
259 |
NameValuePair nvp1 = new NameValuePair("request", "getCapabilities"); |
260 |
NameValuePair nvp2 = new NameValuePair("service", "CSW"); |
261 |
NameValuePair nvp3 = new NameValuePair("acceptversion", this.version); |
262 |
|
263 |
return new NameValuePair[] { nvp1, nvp2, nvp3 }; |
264 |
} |
265 |
|
266 |
/**
|
267 |
*
|
268 |
* @return
|
269 |
*/
|
270 |
public NameValuePair[] getMessageDescribeRecords() { |
271 |
NameValuePair nvp1 = new NameValuePair("request", "describeRecord"); |
272 |
NameValuePair nvp2 = new NameValuePair("version", this.version); |
273 |
NameValuePair nvp3 = new NameValuePair("outputformat", |
274 |
this.getOutputFormat());
|
275 |
NameValuePair nvp4 = new NameValuePair("schemalanguage", "XMLSCHEMA"); |
276 |
NameValuePair nvp5 = new NameValuePair("typename", |
277 |
this.getTypeNames()[0]); |
278 |
NameValuePair nvp6 = new NameValuePair("namespace", |
279 |
"csw:http://www.opengis.org/csw");
|
280 |
|
281 |
return new NameValuePair[] { nvp1, nvp2, nvp3, nvp4, nvp5, nvp6 }; |
282 |
} |
283 |
|
284 |
/**
|
285 |
* @param query
|
286 |
* @return
|
287 |
*/
|
288 |
public NameValuePair[] getMessageRecords(Query query) { |
289 |
CSWQuery cswq = new CSWQuery(query);
|
290 |
|
291 |
NameValuePair nvp1 = new NameValuePair("request", "getRecords"); |
292 |
NameValuePair nvp2 = new NameValuePair("version", this.version); |
293 |
NameValuePair nvp3 = new NameValuePair("service", "CSW"); |
294 |
NameValuePair nvp4 = new NameValuePair("outputformat", |
295 |
this.getOutputFormat());
|
296 |
System.out.println(this.getOutputFormat()); |
297 |
|
298 |
NameValuePair nvp5 = new NameValuePair("outputschema", |
299 |
this.getOutputSchema()[0]); |
300 |
|
301 |
//NameValuePair nvp6= new NameValuePair("resulttype",this.getResultType()[0]);
|
302 |
NameValuePair nvp7 = new NameValuePair("namespace", |
303 |
"csw:http://www.opengis.org/csw");
|
304 |
NameValuePair nvp8 = new NameValuePair("typename", |
305 |
Strings.getComaSeparated(this.getTypeNames()));
|
306 |
NameValuePair nvp9 = new NameValuePair("elementsetname", "full"); |
307 |
NameValuePair nvp10 = new NameValuePair("constraintlanguage", "FILTER"); |
308 |
NameValuePair nvp11 = new NameValuePair("constraint", |
309 |
cswq.getQuery(this.getServerProfile()));
|
310 |
|
311 |
System.out.println(cswq.getQuery(this.getServerProfile())); |
312 |
|
313 |
return new NameValuePair[] { |
314 |
nvp1, nvp2, nvp3, nvp4, nvp5, nvp7, nvp8, nvp9, nvp10, nvp11 |
315 |
}; |
316 |
} |
317 |
|
318 |
public NameValuePair[] getPOSTMessageCapabilities() { |
319 |
String message = "<csw:GetCapabilities service=\"CSW\" version=\"2.0.0\" " + |
320 |
"xmlns:csw=\"http://www.opengis.net/cat/csw\" " +
|
321 |
"xmlns:ogc=\"http://www.opengis.net/ogc\">" +
|
322 |
"<AcceptVersions xmlns=\"http://www.opengis.net/ows\">" +
|
323 |
"<Version>" + version + "</Version>" + "</AcceptVersions>" + |
324 |
"<AcceptFormats xmlns=\"http://www.opengis.net/ows\">" +
|
325 |
"<OutputFormat>text/xml</OutputFormat>" + "</AcceptFormats>" + |
326 |
"</csw:GetCapabilities>";
|
327 |
|
328 |
System.out.println(message);
|
329 |
|
330 |
NameValuePair nvp1 = new NameValuePair("body", message); |
331 |
|
332 |
return new NameValuePair[] { nvp1 }; |
333 |
} |
334 |
|
335 |
public NameValuePair[] getPOSTDescribeRecords() { |
336 |
String message = "<csw:DescribeRecord " + |
337 |
"xmlns:csw=\"http://www.opengis.net/cat/csw\" " +
|
338 |
"xmlns:ogc=\"http://www.opengis.net/ogc\" " + "service=\"CSW\" " + |
339 |
"version=" + version + " " + "outputFormat=\"test/xml\" " + |
340 |
"schemaLanguage=\"XMLSCHEMA\">" +
|
341 |
"<csw:TypeName>csw:record</csw:TypeName>" +
|
342 |
"</csw:DescribeRecord>";
|
343 |
|
344 |
System.out.println(message);
|
345 |
|
346 |
NameValuePair nvp1 = new NameValuePair("body", message); |
347 |
|
348 |
return new NameValuePair[] { nvp1 }; |
349 |
} |
350 |
|
351 |
public NameValuePair[] getPOSTMessageRecords(Query query, int firstRecord) { |
352 |
CSWQuery cswq = new CSWQuery(query);
|
353 |
|
354 |
String message = "<GetRecords " + "service='CSW' " + |
355 |
"version='2.0.0' " + "xmlns='http://www.opengis.net/cat/csw' " + |
356 |
"xmlns:ogc='http://www.opengis.net/ogc' " +
|
357 |
"xmlns:gml='http://www.opengis.net/gml' " + "startPosition='" + |
358 |
firstRecord + "' " + "maxRecords='10' " + |
359 |
"outputFormat='text/xml' " + "resultType='results'>" + |
360 |
"<Query typeNames='Dataset Classification Association Geometry'>" +
|
361 |
"<ElementSetName typeNames='Dataset'>summary</ElementSetName>" +
|
362 |
"<Constraint version='1.0.20'>" +
|
363 |
cswq.getQuery(this.getServerProfile()) + "</Constraint>" + |
364 |
"</Query>" + "</GetRecords>"; |
365 |
|
366 |
/*
|
367 |
String message = "<csw:GetRecords " +
|
368 |
"xmlns:csw=\"http://www.opengis.net/cat/csw\" " +
|
369 |
"xmlns:ogc=\"http://www.opengis.net/ogc\" " + "service=\"CSW\" " +
|
370 |
"version=\"2.0.0\" " + "resultType=\"results\">" +
|
371 |
"<csw:Query typeNames=\"Dataset\">" +
|
372 |
"<csw:ElementSetName>brief</csw:ElementSetName>" +
|
373 |
"<csw:Constraint version=\"2.0.0\">" +
|
374 |
cswq.getQuery(this.getServerProfile()) + "</csw:Constraint>" +
|
375 |
"</csw:Query>" + "</csw:GetRecords>";
|
376 |
*/
|
377 |
System.out.println(message);
|
378 |
|
379 |
NameValuePair nvp1 = new NameValuePair("body", message); |
380 |
|
381 |
return new NameValuePair[] { nvp1 }; |
382 |
} |
383 |
|
384 |
public String getSOAPMessageCapabilities() { |
385 |
return SOAPProtocol.setSOAPMessage(getPOSTMessageCapabilities()[0].getValue()); |
386 |
} |
387 |
|
388 |
public String getSOAPMessageDescribeRecords() { |
389 |
return SOAPProtocol.setSOAPMessage(getPOSTDescribeRecords()[0].getValue()); |
390 |
} |
391 |
|
392 |
public String getSOAPMessageRecords(Query query, int firstRecord) { |
393 |
return SOAPProtocol.setSOAPMessage(getPOSTMessageRecords(query,
|
394 |
firstRecord)[0].getValue());
|
395 |
} |
396 |
|
397 |
/*
|
398 |
* (non-Javadoc)
|
399 |
*
|
400 |
* @see catalogClient.ICatalogServerDriver#setParameters(java.util.Properties)
|
401 |
*/
|
402 |
public boolean setParameters(Node[] nodes) { |
403 |
return new cswCapabilitiesParser(this).parse(nodes[0]); |
404 |
} |
405 |
|
406 |
/**
|
407 |
* @return Returns the distributedSearch.
|
408 |
*/
|
409 |
public String getDistributedSearch() { |
410 |
return distributedSearch;
|
411 |
} |
412 |
|
413 |
/**
|
414 |
* @param distributedSearch
|
415 |
* The distributedSearch to set.
|
416 |
*/
|
417 |
public void setDistributedSearch(String distributedSearch) { |
418 |
this.distributedSearch = distributedSearch;
|
419 |
} |
420 |
|
421 |
/**
|
422 |
* @return Returns the hopCount.
|
423 |
*/
|
424 |
public String getHopCount() { |
425 |
return hopCount;
|
426 |
} |
427 |
|
428 |
/**
|
429 |
* @param hopCount
|
430 |
* The hopCount to set.
|
431 |
*/
|
432 |
public void setHopCount(String hopCount) { |
433 |
this.hopCount = hopCount;
|
434 |
} |
435 |
|
436 |
/**
|
437 |
* @return Returns the nAMESPACE.
|
438 |
*/
|
439 |
public String[] getNAMESPACE() { |
440 |
return NAMESPACE;
|
441 |
} |
442 |
|
443 |
/**
|
444 |
* @param namespace
|
445 |
* The nAMESPACE to set.
|
446 |
*/
|
447 |
public void setNAMESPACE(String[] namespace) { |
448 |
NAMESPACE = namespace; |
449 |
} |
450 |
|
451 |
/**
|
452 |
* @return Returns the responseHandler.
|
453 |
*/
|
454 |
public String getResponseHandler() { |
455 |
return responseHandler;
|
456 |
} |
457 |
|
458 |
/**
|
459 |
* @param responseHandler
|
460 |
* The responseHandler to set.
|
461 |
*/
|
462 |
public void setResponseHandler(String responseHandler) { |
463 |
this.responseHandler = responseHandler;
|
464 |
} |
465 |
|
466 |
/**
|
467 |
* @return Returns the resultType.
|
468 |
*/
|
469 |
public String[] getResultType() { |
470 |
return resultType;
|
471 |
} |
472 |
|
473 |
/**
|
474 |
* @param resultType
|
475 |
* The resultType to set.
|
476 |
*/
|
477 |
public void setResultType(String[] resultType) { |
478 |
this.resultType = resultType;
|
479 |
} |
480 |
|
481 |
/**
|
482 |
* @return Returns the typeNames.
|
483 |
*/
|
484 |
public String[] getTypeNames() { |
485 |
return typeNames;
|
486 |
} |
487 |
|
488 |
/**
|
489 |
* @param typeNames
|
490 |
* The typeNames to set.
|
491 |
*/
|
492 |
public void setTypeNames(String[] typeNames) { |
493 |
this.typeNames = typeNames;
|
494 |
|
495 |
//Depending of this value, the CSW server is an ebRIM or an
|
496 |
//ISO19115/19119 server. We must to set it.
|
497 |
for (int i = 0; i < typeNames.length; i++) |
498 |
if (typeNames[i].equals("csw:Record") || |
499 |
typeNames[i].equals("Record")) {
|
500 |
setServerProfile("ISO");
|
501 |
|
502 |
break;
|
503 |
} else {
|
504 |
setServerProfile("ebRIM");
|
505 |
} |
506 |
} |
507 |
|
508 |
/**
|
509 |
* @return Returns the version.
|
510 |
*/
|
511 |
public String getVersion() { |
512 |
return version;
|
513 |
} |
514 |
|
515 |
/**
|
516 |
* @param version
|
517 |
* The version to set.
|
518 |
*/
|
519 |
public void setVersion(String version) { |
520 |
this.version = version;
|
521 |
} |
522 |
|
523 |
/**
|
524 |
* @return Returns the constraint.
|
525 |
*/
|
526 |
public String getConstraint() { |
527 |
return constraint;
|
528 |
} |
529 |
|
530 |
/**
|
531 |
* @param constraint
|
532 |
* The constraint to set.
|
533 |
*/
|
534 |
public void setConstraint(String constraint) { |
535 |
this.constraint = constraint;
|
536 |
} |
537 |
|
538 |
/**
|
539 |
* @return Returns the cONSTRAINTLANGUAGE.
|
540 |
*/
|
541 |
public String[] getCONSTRAINTLANGUAGE() { |
542 |
return CONSTRAINTLANGUAGE;
|
543 |
} |
544 |
|
545 |
/**
|
546 |
* @param constraintlanguage
|
547 |
* The cONSTRAINTLANGUAGE to set.
|
548 |
*/
|
549 |
public void setCONSTRAINTLANGUAGE(String[] constraintlanguage) { |
550 |
CONSTRAINTLANGUAGE = constraintlanguage; |
551 |
} |
552 |
|
553 |
/**
|
554 |
* @return Returns the elementSetName.
|
555 |
*/
|
556 |
public String[] getElementSetName() { |
557 |
return elementSetName;
|
558 |
} |
559 |
|
560 |
/**
|
561 |
* @param elementSetName
|
562 |
* The elementSetName to set.
|
563 |
*/
|
564 |
public void setElementSetName(String[] elementSetName) { |
565 |
this.elementSetName = elementSetName;
|
566 |
} |
567 |
|
568 |
/**
|
569 |
* @return Returns the serverProfile.
|
570 |
*/
|
571 |
public String getServerProfile() { |
572 |
return ServerProfile;
|
573 |
} |
574 |
|
575 |
/**
|
576 |
* @param serverProfile The serverProfile to set.
|
577 |
*/
|
578 |
public void setServerProfile(String serverProfile) { |
579 |
ServerProfile = serverProfile; |
580 |
} |
581 |
|
582 |
/* (non-Javadoc)
|
583 |
* @see es.gva.cit.catalogClient.drivers.ICatalogServiveDriver#isTheProtocol(java.net.URL)
|
584 |
*/
|
585 |
public boolean isProtocolSupported(URL url) { |
586 |
return true; |
587 |
} |
588 |
} |