| 1 7 package org.jboss.webservice.deployment; 8 9 11 import org.jboss.axis.Constants; 12 import org.jboss.axis.encoding.DefaultTypeMappingImpl; 13 import org.jboss.axis.encoding.TypeMapping; 14 import org.jboss.axis.enums.Style; 15 import org.jboss.axis.enums.Use; 16 import org.jboss.logging.Logger; 17 import org.jboss.webservice.metadata.jaxrpcmapping.ExceptionMapping; 18 import org.jboss.webservice.metadata.jaxrpcmapping.JavaWsdlMapping; 19 import org.jboss.webservice.metadata.jaxrpcmapping.JavaWsdlMappingFactory; 20 import org.jboss.webservice.metadata.jaxrpcmapping.JavaXmlTypeMapping; 21 import org.jboss.webservice.metadata.jaxrpcmapping.MethodParamPartsMapping; 22 import org.jboss.webservice.metadata.jaxrpcmapping.ServiceEndpointInterfaceMapping; 23 import org.jboss.webservice.metadata.jaxrpcmapping.ServiceEndpointMethodMapping; 24 import org.jboss.webservice.metadata.jaxrpcmapping.WsdlMessageMapping; 25 import org.jboss.webservice.metadata.wsdl.WSDL11DefinitionFactory; 26 import org.jboss.webservice.util.DOMUtils; 27 import org.jboss.xb.binding.NamespaceRegistry; 28 import org.w3c.dom.Element ; 29 import org.w3c.dom.NodeList ; 30 31 import javax.wsdl.Binding; 32 import javax.wsdl.BindingInput; 33 import javax.wsdl.BindingOperation; 34 import javax.wsdl.BindingOutput; 35 import javax.wsdl.Definition; 36 import javax.wsdl.Input; 37 import javax.wsdl.Message; 38 import javax.wsdl.Output; 39 import javax.wsdl.Part; 40 import javax.wsdl.Port; 41 import javax.wsdl.PortType; 42 import javax.wsdl.Service; 43 import javax.wsdl.WSDLException; 44 import javax.wsdl.extensions.ExtensibilityElement; 45 import javax.wsdl.extensions.soap.SOAPBinding; 46 import javax.wsdl.extensions.soap.SOAPBody; 47 import javax.wsdl.extensions.soap.SOAPHeader; 48 import javax.wsdl.factory.WSDLFactory; 49 import javax.wsdl.xml.WSDLWriter; 50 import javax.xml.namespace.QName ; 51 import javax.xml.parsers.DocumentBuilder ; 52 import javax.xml.parsers.DocumentBuilderFactory ; 53 import javax.xml.rpc.ServiceException ; 54 import java.io.IOException ; 55 import java.io.InputStream ; 56 import java.io.OutputStream ; 57 import java.net.URI ; 58 import java.net.URISyntaxException ; 59 import java.net.URL ; 60 import java.util.ArrayList ; 61 import java.util.HashMap ; 62 import java.util.HashSet ; 63 import java.util.Iterator ; 64 import java.util.List ; 65 import java.util.Properties ; 66 import java.util.StringTokenizer ; 67 68 74 public class ServiceDescription 75 { 76 private final Logger log = Logger.getLogger(ServiceDescription.class); 78 79 private Definition wsdlDefinition; 80 private Service wsdlService; 81 private Binding wsdlBinding; 82 private JavaWsdlMapping javaWsdlMapping; 83 84 private Style style; 85 private Use use; 86 private HashMap operations = new HashMap (); 87 private HashMap typeMappings = new HashMap (); 88 89 private NamespaceRegistry nsRegistry = new NamespaceRegistry(); 91 92 private HashSet userTypes = new HashSet (); 94 95 private Properties callProperties; 96 97 100 public ServiceDescription(Definition wsdlDefinition, JavaWsdlMapping javaMapping, URL ws4eeMetaData, String portName) throws ServiceException  101 { 102 this.wsdlDefinition = wsdlDefinition; 103 this.wsdlService = getWsdlService(wsdlDefinition, portName); 104 this.wsdlBinding = getWsdlBinding(wsdlService, portName); 105 106 this.javaWsdlMapping = javaMapping; 107 108 initServiceDescription(); 109 110 mergeDeploymentMetaData(ws4eeMetaData, portName); 111 } 112 113 116 public ServiceDescription(URL wsdlLocation, URL jaxrpcLocation, String portName) throws Exception  117 { 118 WSDL11DefinitionFactory wsdlFactory = WSDL11DefinitionFactory.newInstance(); 119 wsdlFactory.setFeature(WSDL11DefinitionFactory.FEATURE_VERBOSE, true); 120 121 this.wsdlDefinition = wsdlFactory.parse(wsdlLocation); 122 this.wsdlService = getWsdlService(wsdlDefinition, portName); 123 this.wsdlBinding = getWsdlBinding(wsdlService, portName); 124 125 if (jaxrpcLocation != null) 126 { 127 JavaWsdlMappingFactory mappingFactory = JavaWsdlMappingFactory.newInstance(); 128 this.javaWsdlMapping = mappingFactory.parse(jaxrpcLocation); 129 } 130 initServiceDescription(); 131 } 132 133 136 private void initServiceDescription() throws ServiceException  137 { 138 initServiceStyle(); 139 initServiceUse(); 140 initOperations(); 141 initTypeMappings(); 142 } 143 144 public Definition getWsdlDefinition() 145 { 146 return wsdlDefinition; 147 } 148 149 public JavaWsdlMapping getJavaWsdlMapping() 150 { 151 return javaWsdlMapping; 152 } 153 154 public Service getWsdlService() 155 { 156 return wsdlService; 157 } 158 159 public Binding getWsdlBinding() 160 { 161 return wsdlBinding; 162 } 163 164 public NamespaceRegistry getNamespaceRegistry() 165 { 166 return nsRegistry; 167 } 168 169 public Style getStyle() 170 { 171 return style; 172 } 173 174 public Use getUse() 175 { 176 return use; 177 } 178 179 public String [] getOperationNames() 180 { 181 String [] names = new String [operations.size()]; 182 return (String [])operations.keySet().toArray(names); 183 } 184 185 public OperationDescription getOperation(String name) 186 { 187 return (OperationDescription)operations.get(name); 188 } 189 190 public Iterator getOperations() 191 { 192 return operations.values().iterator(); 193 } 194 195 public QName [] getTypMappingNames() 196 { 197 QName [] names = new QName [typeMappings.size()]; 198 return (QName [])typeMappings.keySet().toArray(names); 199 } 200 201 public Iterator getTypMappings() 202 { 203 return typeMappings.values().iterator(); 204 } 205 206 public TypeMappingDescription getTypMapping(QName qname) 207 { 208 return (TypeMappingDescription)typeMappings.get(qname); 209 } 210 211 public void dumpWsdlDefinition(OutputStream out) throws WSDLException, IOException  212 { 213 WSDLFactory wsdlFactory = WSDLFactory.newInstance(); 214 WSDLWriter wsdlWriter = wsdlFactory.newWSDLWriter(); 215 wsdlWriter.writeWSDL(wsdlDefinition, out); 216 } 217 218 public Properties getCallProperties() 219 { 220 return callProperties; 221 } 222 223 public void setCallProperties(Properties callProperties) 224 { 225 this.callProperties = callProperties; 226 } 227 228 231 private void mergeDeploymentMetaData(URL ws4eeMetaData, String portName) throws ServiceException  232 { 233 if (ws4eeMetaData == null) 234 { 235 log.debug("No ws4ee deployment meta data available"); 236 return; 237 } 238 239 log.debug("Merging with ws4ee deployment meta data: " + ws4eeMetaData); 240 241 try 242 { 243 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 244 factory.setNamespaceAware(true); 245 DocumentBuilder builder = factory.newDocumentBuilder(); 246 247 Element rootElement = null; 248 InputStream is = ws4eeMetaData.openStream(); 249 try 250 { 251 rootElement = builder.parse(is).getDocumentElement(); 252 } 253 finally 254 { 255 is.close(); 256 } 257 258 Element portElement = null; 260 NodeList nlistRoot = rootElement.getElementsByTagName("port"); 261 for (int i = 0; portElement == null && i < nlistRoot.getLength(); i++) 262 { 263 Element el = (Element )nlistRoot.item(i); 264 String name = el.getAttribute("name"); 265 if (name.equals(portName)) 266 portElement = el; 267 } 268 269 if (portElement != null) 271 { 272 log.debug("Using port element: " + portName); 273 Element importElement = DOMUtils.getFirstChildElement(portElement); 274 if (importElement.getLocalName().equals("import")) 275 { 276 String importFile = importElement.getFirstChild().getNodeValue(); 277 log.debug("Using import: " + importFile); 278 279 String resource = ws4eeMetaData.toExternalForm(); 280 resource = resource.substring(0, resource.lastIndexOf("/")) + "/" + importFile; 281 InputStream isImport = new URL (resource).openStream(); 282 try 283 { 284 portElement = builder.parse(isImport).getDocumentElement(); 285 } 286 finally 287 { 288 is.close(); 289 } 290 } 291 } 292 293 if (portElement == null) 295 portElement = rootElement; 296 297 mergeTypeMappings(portElement); 298 299 mergeOperations(portElement); 300 301 } 302 catch (Exception e) 303 { 304 throw new ServiceException (e); 305 } 306 } 307 308 private void mergeTypeMappings(Element portElement) 309 { 310 ArrayList mergedTypeMappings = new ArrayList (); 311 312 NodeList nlistPort = portElement.getElementsByTagName("typeMapping"); 313 for (int i = 0; i < nlistPort.getLength(); i++) 314 { 315 Element el = (Element )nlistPort.item(i); 316 QName qname = DOMUtils.getAttributeValueAsQName(el, "qname"); 317 String javaType = DOMUtils.getAttributeValue(el, "type"); 318 319 if (mergedTypeMappings.contains(qname)) 320 throw new IllegalArgumentException ("Cannot add/replace the same type mapping twice: " + qname); 321 322 mergedTypeMappings.add(qname); 323 324 if (javaType.startsWith("java:")) 325 javaType = javaType.substring(5); 326 327 TypeMappingDescription tm = (TypeMappingDescription)typeMappings.get(qname); 328 if (tm != null) 329 log.debug("Replacing typeMapping: " + qname); 330 else 331 log.debug("Addinging typeMapping: " + qname); 332 333 tm = new TypeMappingDescription(qname, null, javaType, use, null); 334 tm.setEncodingURI(el.getAttribute("encodingStyle")); 335 tm.setSerializerFactoryName(el.getAttribute("serializer")); 336 tm.setDeserializerFactoryName(el.getAttribute("deserializer")); 337 tm.setUserDefined(true); 338 339 typeMappings.put(qname, tm); 340 341 BeanXMLMetaData metaData = BeanXMLMetaData.parse(DOMUtils.getFirstChildElement(el)); 342 tm.setMetaData(metaData); 343 } 344 } 345 346 private void mergeOperations(Element portElement) 347 { 348 ArrayList mergedOperations = new ArrayList (); 349 350 NodeList nlistOperation = portElement.getElementsByTagName("operation"); 351 for (int i = 0; i < nlistOperation.getLength(); i++) 352 { 353 Element elOp = (Element )nlistOperation.item(i); 354 String opName = elOp.getAttribute("name"); 355 QName opQName = DOMUtils.getAttributeValueAsQName(elOp, "qname"); 356 QName returnQName = DOMUtils.getAttributeValueAsQName(elOp, "returnQName"); 357 QName returnType = DOMUtils.getAttributeValueAsQName(elOp, "returnType"); 358 359 if (mergedOperations.contains(opName)) 360 throw new IllegalArgumentException ("Cannot add/replace the same operation twice: " + opName); 361 362 mergedOperations.add(opName); 363 364 OperationDescription op = (OperationDescription)operations.get(opName); 365 if (op != null) 366 log.debug("Replacing operation: " + opName); 367 else 368 log.debug("Addinging operation: " + opName); 369 370 371 if (opQName == null) 372 opQName = new QName (opName); 373 374 op = new OperationDescription(opName, opQName); 375 op.setReturnQName(returnQName); 376 op.setReturnType(returnType); 377 operations.put(opName, op); 378 379 NodeList paramList = elOp.getElementsByTagName("parameter"); 380 for (int j = 0; j < paramList.getLength(); j++) 381 { 382 Element elParam = (Element )paramList.item(j); 383 String paramName = elParam.getAttribute("name"); 384 QName paramQName = DOMUtils.getAttributeValueAsQName(elParam, "qname"); 385 String mode = elParam.getAttribute("mode"); 386 QName type = DOMUtils.getAttributeValueAsQName(elParam, "type"); 387 boolean inHeader = DOMUtils.getAttributeValueAsBoolean(elParam, "inHeader"); 388 boolean outHeader = DOMUtils.getAttributeValueAsBoolean(elParam, "outHeader"); 389 390 if (paramQName == null) 391 paramQName = new QName (paramName); 392 393 OperationDescription.Parameter param = new OperationDescription.Parameter(paramName, paramQName, mode, type); 394 param.setInHeader(inHeader); 395 param.setOutHeader(outHeader); 396 op.addParameter(param); 397 } 398 399 NodeList faultList = elOp.getElementsByTagName("fault"); 400 for (int j = 0; j < faultList.getLength(); j++) 401 { 402 Element elFault = (Element )faultList.item(j); 403 String faultName = DOMUtils.getAttributeValue(elFault, "name"); 404 QName faultQName = DOMUtils.getAttributeValueAsQName(elFault, "qname"); 405 String javaType = DOMUtils.getAttributeValue(elFault, "class"); 406 QName faultType = DOMUtils.getAttributeValueAsQName(elFault, "type"); 407 408 OperationDescription.Fault fault = new OperationDescription.Fault(faultName, faultQName, javaType, faultType); 409 op.addFault(fault); 410 } 411 } 412 } 413 414 417 private void initServiceStyle() 418 { 419 Iterator itBinding = wsdlBinding.getExtensibilityElements().iterator(); 420 while (itBinding.hasNext()) 421 { 422 ExtensibilityElement exElement = (ExtensibilityElement)itBinding.next(); 423 if (exElement instanceof SOAPBinding) 424 { 425 SOAPBinding soapBinding = (SOAPBinding)exElement; 426 Style bindingStyle = Style.getStyle(soapBinding.getStyle()); 427 if (style == null && bindingStyle != null) 428 style = bindingStyle; 429 430 if (style != null && bindingStyle != null && style.equals(bindingStyle) == false) 431 throw new IllegalArgumentException ("Unsupported mix of style attributes " + style + "/" + bindingStyle); 432 } 433 } 434 435 if (style == null) 436 { 437 log.warn("Cannot find any style attribute for binding: " + wsdlBinding.getQName()); 438 style = Style.RPC; 439 } 440 } 441 442 445 private void initServiceUse() 446 { 447 Iterator itOp = wsdlBinding.getBindingOperations().iterator(); 448 while (itOp.hasNext()) 449 { 450 BindingOperation wsdlBindingOperation = (BindingOperation)itOp.next(); 451 452 BindingInput wsdlBindingInput = wsdlBindingOperation.getBindingInput(); 453 if (wsdlBindingInput != null) 454 { 455 Iterator itIn = wsdlBindingInput.getExtensibilityElements().iterator(); 456 while (itIn.hasNext()) 457 { 458 Object obj = itIn.next(); 459 if (obj instanceof SOAPBody) 460 { 461 SOAPBody soapBody = (SOAPBody)obj; 462 Use soapBodyUse = Use.getUse(soapBody.getUse()); 463 if (use == null) 464 { 465 if (soapBodyUse != null) 466 { 467 use = soapBodyUse; 468 } 469 else 470 { 471 use = Use.getUse("literal"); 472 } 473 } 474 if (use != null && soapBodyUse != null && use.equals(soapBodyUse) == false) 475 throw new IllegalArgumentException ("Unsupported mix of use attributes " + use + "/" + soapBodyUse); 476 } 477 } 478 } 479 480 BindingOutput wsdlBindingOutput = wsdlBindingOperation.getBindingOutput(); 481 if (wsdlBindingOutput != null) 482 { 483 Iterator itOut = wsdlBindingOutput.getExtensibilityElements().iterator(); 484 while (itOut.hasNext()) 485 { 486 Object obj = itOut.next(); 487 if (obj instanceof SOAPBody) 488 { 489 SOAPBody soapBody = (SOAPBody)obj; 490 Use soapBodyUse = Use.getUse(soapBody.getUse()); 491 if (use == null) 492 { 493 if (soapBodyUse != null) 494 { 495 use = soapBodyUse; 496 } 497 else 498 { 499 use = Use.getUse("literal"); 500 } 501 } 502 if (use != null && soapBodyUse != null && use.equals(soapBodyUse) == false) 503 throw new IllegalArgumentException ("Unsupported mix of use attributes " + use + "/" + soapBodyUse); 504 } 505 } 506 } 507 } 508 509 if (use == null) 510 { 511 log.warn("Cannot find any use attribute for binding: " + wsdlBinding.getQName()); 512 use = Use.LITERAL; 513 } 514 } 515 516 519 private void initOperations() throws ServiceException  520 { 521 PortType wsdlPortType = wsdlBinding.getPortType(); 522 QName portTypeQName = wsdlPortType.getQName(); 523 524 Iterator itOp = wsdlPortType.getOperations().iterator(); 525 while (itOp.hasNext()) 526 { 527 javax.wsdl.Operation wsdlOperation = (javax.wsdl.Operation)itOp.next(); 528 String opWsdlName = wsdlOperation.getName(); 529 String opJavaName = opWsdlName; 530 531 QName opQName = null; 532 533 if (portTypeQName.getNamespaceURI().length() > 0) 535 { 536 opQName = new QName (portTypeQName.getNamespaceURI(), opWsdlName); 537 opQName = nsRegistry.registerQName(opQName); 538 } 539 else 540 { 541 opQName = new QName (opWsdlName); 542 } 543 544 ServiceEndpointMethodMapping seMethodMapping = getServiceEndpointMethodMapping(wsdlPortType, opWsdlName); 546 if (seMethodMapping != null) 547 { 548 opJavaName = seMethodMapping.getJavaMethodName(); 549 } 550 551 OperationDescription opDesc = new OperationDescription(opJavaName, opQName); 552 operations.put(opJavaName, opDesc); 553 554 Output wsdlOutput = wsdlOperation.getOutput(); 555 Input wsdlInput = wsdlOperation.getInput(); 556 557 if (wsdlInput != null && wsdlOutput == null) 558 { 559 log.debug("Using one-way call semantics: " + opWsdlName); 560 opDesc.setOneWay(true); 561 } 562 563 if (style == Style.DOCUMENT && seMethodMapping != null) 566 { 567 boolean inParams = seMethodMapping.getMethodParamPartsMappings().length > 0; 568 if (wsdlInput != null && wsdlInput.getMessage().getParts().size() > 0 && inParams == false) 569 { 570 log.debug("jaxrpc-mapping does not have any <method-param-parts-mapping>, ignoring wsdl parts"); 571 wsdlInput = null; 572 } 573 574 boolean outParams = seMethodMapping.getWsdlReturnValueMapping() != null; 575 if (wsdlOutput != null && wsdlOutput.getMessage().getParts().size() > 0 && outParams == false) 576 { 577 log.debug("jaxrpc-mapping does not have any <wsdl-return-value-mapping>, ignoring wsdl parts"); 578 wsdlOutput = null; 579 } 580 } 581 582 if (wsdlOutput != null) 583 { 584 Message wsdlMessageOut = wsdlOutput.getMessage(); 585 Iterator outParts = wsdlMessageOut.getOrderedParts(null).iterator(); 586 while (outParts.hasNext()) 587 { 588 Part wsdlPart = (Part)outParts.next(); 589 String paramName = wsdlPart.getName(); 590 QName typeQName = wsdlPart.getTypeName(); 591 592 boolean outHeader = isHeaderParam(opWsdlName, paramName); 593 594 QName element = wsdlPart.getElementName(); 595 if (typeQName == null && element != null) 596 typeQName = element; 597 598 if (typeQName != null) 599 { 600 typeQName = nsRegistry.registerQName(typeQName); 601 userTypes.add(typeQName); 602 } 603 604 String paramMode = null; 606 if (seMethodMapping != null) 607 { 608 MethodParamPartsMapping[] mppMappings = seMethodMapping.getMethodParamPartsMappings(); 609 for (int i = 0; paramMode == null && i < mppMappings.length; i++) 610 { 611 WsdlMessageMapping wmMapping = mppMappings[i].getWsdlMessageMapping(); 612 if (paramName.equals(wmMapping.getWsdlMessagePartName())) 613 { 614 paramMode = wmMapping.getParameterMode(); 615 } 616 } 617 } 618 619 if (opDesc.getReturnType() == null && wsdlOperation.getInput().getMessage().getPart(paramName) == null && paramMode == null) 622 { 623 QName returnQName = getParameterQName(wsdlPart); 624 opDesc.setReturnQName(returnQName); 625 opDesc.setReturnType(typeQName); 626 } 627 628 else 630 { 631 if (paramMode == null) 632 paramMode = "OUT"; 633 634 QName paramQName = getParameterQName(wsdlPart); 635 OperationDescription.Parameter param = new OperationDescription.Parameter(paramName, paramQName, paramMode, typeQName); 636 param.setOutHeader(outHeader); 637 opDesc.addParameter(param); 638 } 639 } 640 } 641 642 if (wsdlInput != null) 644 { 645 Message wsdlMessageIn = wsdlInput.getMessage(); 646 Iterator inParts = wsdlMessageIn.getOrderedParts(null).iterator(); 647 while (inParts.hasNext()) 648 { 649 Part wsdlPart = (Part)inParts.next(); 650 String paramName = wsdlPart.getName(); 651 QName typeQName = wsdlPart.getTypeName(); 652 653 boolean inHeader = isHeaderParam(opWsdlName, paramName); 654 655 if (typeQName == null && wsdlPart.getElementName() != null) 656 typeQName = wsdlPart.getElementName(); 657 658 if (typeQName != null) 659 { 660 typeQName = nsRegistry.registerQName(typeQName); 661 userTypes.add(typeQName); 662 } 663 664 OperationDescription.Parameter param = opDesc.getParameterForName(paramName); 665 if (param != null) 666 { 667 param.setMode("INOUT"); 668 param.setInHeader(inHeader); 669 } 670 else 671 { 672 QName paramQName = getParameterQName(wsdlPart); 673 param = new OperationDescription.Parameter(paramName, paramQName, "IN", typeQName); 674 param.setInHeader(inHeader); 675 opDesc.addParameter(param); 676 } 677 } 678 } 679 680 reorderOperationParameters(opDesc, wsdlOperation); 682 683 Iterator inFaults = wsdlOperation.getFaults().values().iterator(); 685 while (inFaults.hasNext()) 686 { 687 javax.wsdl.Fault wsdlFault = (javax.wsdl.Fault)inFaults.next(); 688 Part wsdlPart = (Part)wsdlFault.getMessage().getParts().values().iterator().next(); 689 String partName = wsdlPart.getName(); 690 QName typeQName = wsdlPart.getTypeName(); 691 QName faultQName = wsdlPart.getElementName(); 692 693 if (typeQName == null && faultQName != null) 694 typeQName = faultQName; 695 696 if (typeQName != null) 697 typeQName = nsRegistry.registerQName(typeQName); 698 699 if (faultQName != null) 700 faultQName = nsRegistry.registerQName(faultQName); 701 702 String javaType = null; 703 if (javaWsdlMapping != null) 704 { 705 QName wsdlMessageName = wsdlFault.getMessage().getQName(); 707 ExceptionMapping exceptionMapping = javaWsdlMapping.getExceptionMappingForMessageQName(wsdlMessageName); 708 if (exceptionMapping != null) 709 { 710 javaType = exceptionMapping.getExceptionType(); 711 } 712 else 714 { 715 JavaXmlTypeMapping javaMapping = javaWsdlMapping.getTypeMappingForQName(typeQName); 716 if (javaMapping != null) 717 javaType = javaMapping.getJavaType(); 718 } 719 } 720 721 if (javaType == null) 723 { 724 TypeMapping defMapping = DefaultTypeMappingImpl.getSingleton(); 725 Class typeClass = defMapping.getClassForQName(typeQName); 726 if (typeClass != null) 727 javaType = typeClass.getName(); 728 } 729 730 if (javaType == null) 731 { 732 String packageName = getPackageName(typeQName); 733 javaType = packageName + "." + typeQName.getLocalPart(); 734 log.warn("Guessing fault java type from qname: " + javaType); 735 } 736 737 OperationDescription.Fault fault = new OperationDescription.Fault(partName, faultQName, javaType, typeQName); 738 opDesc.addFault(fault); 739 } 740 } 741 742 if (operations.size() == 0) 743 log.warn("Cannot find any operations for portType: " + wsdlPortType.getQName()); 744 } 745 746 747 |