public void receive(org.apache.axis2.context.MessageContext mc) throws AxisFault { MessageContext synCtx = MessageContextCreatorForAxis2.getSynapseMessageContext(mc); StatisticsReporter.reportForComponent( synCtx, AspectConfigurationDetectionStrategy.getAspectConfiguration(synCtx), ComponentType.PROXYSERVICE); boolean traceOn = synCtx.getMainSequence().getTraceState() == SynapseConstants.TRACING_ON; boolean traceOrDebugOn = traceOn || log.isDebugEnabled(); if (traceOrDebugOn) { traceOrDebug(traceOn, "Synapse received a new message for message mediation..."); traceOrDebug( traceOn, "Received To: " + (mc.getTo() != null ? mc.getTo().getAddress() : "null")); traceOrDebug( traceOn, "SOAPAction: " + (mc.getSoapAction() != null ? mc.getSoapAction() : "null")); traceOrDebug( traceOn, "WSA-Action: " + (mc.getWSAAction() != null ? mc.getWSAAction() : "null")); if (traceOn && trace.isTraceEnabled()) { String[] cids = mc.getAttachmentMap().getAllContentIDs(); if (cids != null && cids.length > 0) { for (String cid : cids) { trace.trace("Attachment : " + cid); } } trace.trace("Envelope : " + mc.getEnvelope()); } } // get service log for this message and attach to the message context Log serviceLog = LogFactory.getLog( SynapseConstants.SERVICE_LOGGER_PREFIX + SynapseConstants.SYNAPSE_SERVICE_NAME); ((Axis2MessageContext) synCtx).setServiceLog(serviceLog); try { // invoke synapse message mediation through the main sequence synCtx.getEnvironment().injectMessage(synCtx); } catch (SynapseException syne) { if (!synCtx.getFaultStack().isEmpty()) { warn(traceOn, "Executing fault handler due to exception encountered", synCtx); ((FaultHandler) synCtx.getFaultStack().pop()).handleFault(synCtx, syne); } else { warn( traceOn, "Exception encountered but no fault handler found - " + "message dropped", synCtx); } } finally { StatisticsReporter.endReportForAllOnRequestProcessed(synCtx); } }
public static MessageContext sendUsingSwA(String fileName, String targetEPR) throws IOException { Options options = new Options(); options.setTo(new EndpointReference(targetEPR)); options.setAction("urn:uploadFileUsingSwA"); options.setProperty(Constants.Configuration.ENABLE_SWA, Constants.VALUE_TRUE); ServiceClient sender = new ServiceClient(); sender.setOptions(options); OperationClient mepClient = sender.createClient(ServiceClient.ANON_OUT_IN_OP); MessageContext mc = new MessageContext(); System.out.println("Sending file : " + fileName + " as SwA"); FileDataSource fileDataSource = new FileDataSource(new File(fileName)); DataHandler dataHandler = new DataHandler(fileDataSource); String attachmentID = mc.addAttachment(dataHandler); SOAPFactory factory = OMAbstractFactory.getSOAP11Factory(); SOAPEnvelope env = factory.getDefaultEnvelope(); OMNamespace ns = factory.createOMNamespace("http://services.samples/xsd", "m0"); OMElement payload = factory.createOMElement("uploadFileUsingSwA", ns); OMElement request = factory.createOMElement("request", ns); OMElement imageId = factory.createOMElement("imageId", ns); imageId.setText(attachmentID); request.addChild(imageId); payload.addChild(request); env.getBody().addChild(payload); mc.setEnvelope(env); mepClient.addMessageContext(mc); mepClient.execute(true); MessageContext response = mepClient.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE); SOAPBody body = response.getEnvelope().getBody(); String imageContentId = body.getFirstChildWithName( new QName("http://services.samples/xsd", "uploadFileUsingSwAResponse")) .getFirstChildWithName(new QName("http://services.samples/xsd", "response")) .getFirstChildWithName(new QName("http://services.samples/xsd", "imageId")) .getText(); Attachments attachment = response.getAttachmentMap(); dataHandler = attachment.getDataHandler(imageContentId); File tempFile = File.createTempFile("swa-", ".gif"); FileOutputStream fos = new FileOutputStream(tempFile); dataHandler.writeTo(fos); fos.flush(); fos.close(); System.out.println("Saved response to file : " + tempFile.getAbsolutePath()); return response; }
/** * Handle the response or error (during a failed send) message received for an outgoing request * * @param messageID Request message ID * @param response the Axis2 MessageContext that has been received and has to be handled * @param synapseOutMsgCtx the corresponding (outgoing) Synapse MessageContext for the above Axis2 * MC, that holds Synapse specific information such as the error handler stack and local * properties etc. * @throws AxisFault if the message cannot be processed */ private void handleMessage( String messageID, MessageContext response, org.apache.synapse.MessageContext synapseOutMsgCtx, AsyncCallback callback) throws AxisFault { // apply the tenant information to the out message context TenantInfoConfigurator configurator = synapseOutMsgCtx.getEnvironment().getTenantInfoConfigurator(); if (configurator != null) { configurator.applyTenantInfo(synapseOutMsgCtx); } Object o = response.getProperty(SynapseConstants.SENDING_FAULT); if (o != null && Boolean.TRUE.equals(o)) { StatisticsReporter.reportFaultForAll( synapseOutMsgCtx, ErrorLogFactory.createErrorLog(response)); // there is a sending fault. propagate the fault to fault handlers. Stack faultStack = synapseOutMsgCtx.getFaultStack(); if (faultStack != null && !faultStack.isEmpty()) { // if we have access to the full synapseOutMsgCtx.getEnvelope(), then let // it flow with the error details. Else, replace its envelope with the // fault envelope try { synapseOutMsgCtx.getEnvelope().build(); } catch (OMException x) { synapseOutMsgCtx.setEnvelope(response.getEnvelope()); } Exception e = (Exception) response.getProperty(SynapseConstants.ERROR_EXCEPTION); synapseOutMsgCtx.setProperty(SynapseConstants.SENDING_FAULT, Boolean.TRUE); synapseOutMsgCtx.setProperty( SynapseConstants.ERROR_CODE, response.getProperty(SynapseConstants.ERROR_CODE)); synapseOutMsgCtx.setProperty( SynapseConstants.ERROR_MESSAGE, response.getProperty(SynapseConstants.ERROR_MESSAGE)); synapseOutMsgCtx.setProperty( SynapseConstants.ERROR_DETAIL, response.getProperty(SynapseConstants.ERROR_DETAIL)); synapseOutMsgCtx.setProperty(SynapseConstants.ERROR_EXCEPTION, e); if (log.isDebugEnabled()) { log.debug( "[Failed Request Message ID : " + messageID + "]" + " [New to be Retried Request Message ID : " + synapseOutMsgCtx.getMessageID() + "]"); } int errorCode = (Integer) response.getProperty(SynapseConstants.ERROR_CODE); // If a timeout has occured and the timeout action of the callback is to discard the message if (errorCode == SynapseConstants.NHTTP_CONNECTION_TIMEOUT && callback.getTimeOutAction() == SynapseConstants.DISCARD) { // Do not execute any fault sequences. Discard message if (log.isWarnEnabled()) { log.warn( "Synapse timed out for the request with Message ID : " + messageID + ". Ignoring fault handlers since the timeout action is DISCARD"); } faultStack.removeAllElements(); } else { ((FaultHandler) faultStack.pop()).handleFault(synapseOutMsgCtx, null); } } } else { // there can always be only one instance of an Endpoint in the faultStack of a message // if the send was successful, so remove it before we proceed any further Stack faultStack = synapseOutMsgCtx.getFaultStack(); Endpoint successfulEndpoint = null; if (faultStack != null && !faultStack.isEmpty() && faultStack.peek() instanceof Endpoint) { successfulEndpoint = (Endpoint) faultStack.pop(); } if (log.isDebugEnabled()) { log.debug("Synapse received an asynchronous response message"); log.debug( "Received To: " + (response.getTo() != null ? response.getTo().getAddress() : "null")); log.debug( "SOAPAction: " + (response.getSoapAction() != null ? response.getSoapAction() : "null")); log.debug( "WSA-Action: " + (response.getWSAAction() != null ? response.getWSAAction() : "null")); String[] cids = response.getAttachmentMap().getAllContentIDs(); if (cids != null && cids.length > 0) { for (String cid : cids) { log.debug("Attachment : " + cid); } } log.debug("Body : \n" + response.getEnvelope()); } MessageContext axisOutMsgCtx = ((Axis2MessageContext) synapseOutMsgCtx).getAxis2MessageContext(); // Processes 'Accept-Encoding' ResponseAcceptEncodingProcessor.process(response, axisOutMsgCtx); response.setServiceContext(null); response.setOperationContext(axisOutMsgCtx.getOperationContext()); response.setAxisMessage( axisOutMsgCtx.getAxisOperation().getMessage(WSDLConstants.MESSAGE_LABEL_OUT_VALUE)); // set properties on response response.setServerSide(true); response.setProperty(SynapseConstants.ISRESPONSE_PROPERTY, Boolean.TRUE); response.setProperty( MessageContext.TRANSPORT_OUT, axisOutMsgCtx.getProperty(MessageContext.TRANSPORT_OUT)); response.setProperty( org.apache.axis2.Constants.OUT_TRANSPORT_INFO, axisOutMsgCtx.getProperty(org.apache.axis2.Constants.OUT_TRANSPORT_INFO)); response.setTransportIn(axisOutMsgCtx.getTransportIn()); response.setTransportOut(axisOutMsgCtx.getTransportOut()); // If request is REST assume that the response is REST too response.setDoingREST(axisOutMsgCtx.isDoingREST()); if (axisOutMsgCtx.isDoingMTOM()) { response.setDoingMTOM(true); response.setProperty( org.apache.axis2.Constants.Configuration.ENABLE_MTOM, org.apache.axis2.Constants.VALUE_TRUE); } if (axisOutMsgCtx.isDoingSwA()) { response.setDoingSwA(true); response.setProperty( org.apache.axis2.Constants.Configuration.ENABLE_SWA, org.apache.axis2.Constants.VALUE_TRUE); } // when axis2 receives a soap message without addressing headers it users // DISABLE_ADDRESSING_FOR_OUT_MESSAGES property to keep it and hence avoid addressing // headers on the response. this causes a problem for synapse if the original message // it receivs (from client) has addressing and the synaspse service invocation has not // engage addressing. in this case when synapse receives the response from the server // addessing In handler dissable addressing since that response does not have addressing // headers. synapse sends the response to its orignal client using the same message // context. Then this response does not have addressing headers since it already // disable. to avoid this we need to set the DISABLE_ADDRESSING_FOR_OUT_MESSAGES // property state to original state. if (axisOutMsgCtx.getProperty(AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES) != null) { response.setProperty( AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES, axisOutMsgCtx.getProperty(AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES)); } else { response.removeProperty(AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES); } Object messageType = axisOutMsgCtx.getProperty(org.apache.axis2.Constants.Configuration.MESSAGE_TYPE); if (!HTTPConstants.MEDIA_TYPE_X_WWW_FORM.equals(messageType)) { // copy the message type property that's used by the out message to the // response message response.setProperty(org.apache.axis2.Constants.Configuration.MESSAGE_TYPE, messageType); } // compare original received message (axisOutMsgCtx) soap version with the response // if they are different change to original version if (axisOutMsgCtx.isSOAP11() != response.isSOAP11()) { if (axisOutMsgCtx.isSOAP11()) { SOAPUtils.convertSOAP12toSOAP11(response); } else { SOAPUtils.convertSOAP11toSOAP12(response); } } if (axisOutMsgCtx.getMessageID() != null) { response.setRelationships(new RelatesTo[] {new RelatesTo(axisOutMsgCtx.getMessageID())}); } response.setReplyTo(axisOutMsgCtx.getReplyTo()); response.setFaultTo(axisOutMsgCtx.getFaultTo()); if (axisOutMsgCtx.isPropertyTrue(NhttpConstants.IGNORE_SC_ACCEPTED)) { response.setProperty(NhttpConstants.FORCE_SC_ACCEPTED, Constants.VALUE_TRUE); } // create the synapse message context for the response Axis2MessageContext synapseInMessageContext = new Axis2MessageContext( response, synapseOutMsgCtx.getConfiguration(), synapseOutMsgCtx.getEnvironment()); synapseInMessageContext.setResponse(true); Object obj = synapseOutMsgCtx.getProperty(SynapseConstants.FORCE_ERROR_PROPERTY); String errorOnSOAPFault = (String) obj; if (Constants.VALUE_TRUE.equals(errorOnSOAPFault) && successfulEndpoint != null) { if (log.isDebugEnabled()) { log.debug("FORCE_ERROR_ON_SOAP_FAULT is true, checking for SOAPFault"); } try { RelayUtils.buildMessage( ((Axis2MessageContext) synapseInMessageContext).getAxis2MessageContext(), true); } catch (Exception e) { // handleException("Error while building message", e, synapseInMessageContext); } if ((synapseInMessageContext.getEnvelope() != null) && synapseInMessageContext.getEnvelope().hasFault()) { if (log.isDebugEnabled()) { log.debug( "SOAPFault found in response message, forcing endpoint " + successfulEndpoint.getName() + " to fail"); } // setup new pipe configuration..if failure happens (this will be setup as the source // writer and during the TargetContext // clean up operation the writer will be reset and pull to the buffer MessageContext axis2OUTMC = ((Axis2MessageContext) synapseOutMsgCtx).getAxis2MessageContext(); NHttpServerConnection conn = (NHttpServerConnection) axis2OUTMC.getProperty("pass-through.Source-Connection"); if (conn != null) { SourceConfiguration sourceConfiguration = (SourceConfiguration) axis2OUTMC.getProperty("PASS_THROUGH_SOURCE_CONFIGURATION"); Pipe pipe = new Pipe( conn, sourceConfiguration.getBufferFactory().getBuffer(), "source", sourceConfiguration); axis2OUTMC.setProperty(PassThroughConstants.PASS_THROUGH_PIPE, pipe); } StatisticsReporter.reportFaultForAll( synapseOutMsgCtx, ErrorLogFactory.createErrorLog(response)); synapseOutMsgCtx.setProperty(SynapseConstants.SENDING_FAULT, Boolean.TRUE); synapseOutMsgCtx.setProperty( SynapseConstants.ERROR_CODE, SynapseConstants.ENDPOINT_CUSTOM_ERROR); ((FaultHandler) successfulEndpoint).handleFault(synapseOutMsgCtx, null); return; } else { successfulEndpoint.onSuccess(); } } else if (successfulEndpoint != null) { successfulEndpoint.onSuccess(); } synapseInMessageContext.setTo( new EndpointReference(AddressingConstants.Final.WSA_ANONYMOUS_URL)); synapseInMessageContext.setTracingState(synapseOutMsgCtx.getTracingState()); // set the properties of the original MC to the new MC for (Object key : synapseOutMsgCtx.getPropertyKeySet()) { synapseInMessageContext.setProperty( (String) key, synapseOutMsgCtx.getProperty((String) key)); } // Copy SequenceCallStack from original MC to the new MC if (synapseOutMsgCtx.isContinuationEnabled()) { // Set the message direction if (!synapseOutMsgCtx.isResponse()) { synapseInMessageContext.setResponse(false); } Stack<ContinuationState> seqContinuationStates = synapseOutMsgCtx.getContinuationStateStack(); for (int i = 0; i < seqContinuationStates.size(); i++) { synapseInMessageContext.pushContinuationState(seqContinuationStates.get(i)); } } // If this response is related to session affinity endpoints -Server initiated session Dispatcher dispatcher = (Dispatcher) synapseOutMsgCtx.getProperty(SynapseConstants.PROP_SAL_ENDPOINT_CURRENT_DISPATCHER); if (dispatcher != null && dispatcher.isServerInitiatedSession()) { dispatcher.updateSession(synapseInMessageContext); } StatisticsReporter.reportForAllOnResponseReceived(synapseInMessageContext); // send the response message through the synapse mediation flow try { synapseOutMsgCtx.getEnvironment().injectMessage(synapseInMessageContext); } catch (SynapseException syne) { Stack stack = synapseInMessageContext.getFaultStack(); if (stack != null && !stack.isEmpty()) { ((FaultHandler) stack.pop()).handleFault(synapseInMessageContext, syne); } else { log.error( "Synapse encountered an exception, " + "No error handlers found - [Message Dropped]\n" + syne.getMessage()); } } } }
public void receive(org.apache.axis2.context.MessageContext mc) throws AxisFault { boolean traceOn = proxy.getAspectConfiguration().isTracingEnabled(); boolean traceOrDebugOn = traceOn || log.isDebugEnabled(); CustomLogSetter.getInstance().setLogAppender(proxy.getArtifactContainerName()); String remoteAddr = (String) mc.getProperty(org.apache.axis2.context.MessageContext.REMOTE_ADDR); if (traceOrDebugOn) { traceOrDebug( traceOn, "Proxy Service " + name + " received a new message" + (remoteAddr != null ? " from : " + remoteAddr : "...")); traceOrDebug( traceOn, ("Message To: " + (mc.getTo() != null ? mc.getTo().getAddress() : "null"))); traceOrDebug( traceOn, ("SOAPAction: " + (mc.getSoapAction() != null ? mc.getSoapAction() : "null"))); traceOrDebug( traceOn, ("WSA-Action: " + (mc.getWSAAction() != null ? mc.getWSAAction() : "null"))); if (traceOn && trace.isTraceEnabled()) { String[] cids = mc.getAttachmentMap().getAllContentIDs(); if (cids != null && cids.length > 0) { for (String cid : cids) { trace.trace("With attachment content ID : " + cid); } } trace.trace("Envelope : " + mc.getEnvelope()); } } MessageContext synCtx = MessageContextCreatorForAxis2.getSynapseMessageContext(mc); Integer statisticReportingIndex = null; // Statistic reporting boolean isStatisticsEnabled = RuntimeStatisticCollector.isStatisticsEnabled(); if (isStatisticsEnabled) { statisticReportingIndex = OpenEventCollector.reportEntryEvent( synCtx, this.name, proxy.getAspectConfiguration(), ComponentType.PROXYSERVICE); } Object inboundServiceParam = proxy.getParameterMap().get(SynapseConstants.INBOUND_PROXY_SERVICE_PARAM); Object inboundMsgCtxParam = mc.getProperty(SynapseConstants.IS_INBOUND); // check whether the message is from Inbound EP if (inboundMsgCtxParam == null || !(boolean) inboundMsgCtxParam) { // check whether service parameter is set to true or null, then block this request if (inboundServiceParam != null && (Boolean.valueOf((String) inboundServiceParam))) { /* return because same proxy is exposed via InboundEP and service parameter(inbound.only) is set to true, which disable normal http transport proxy */ if (!synCtx.getFaultStack().isEmpty()) { if (log.isDebugEnabled()) { log.debug( "Executing fault handler - message discarded due to the proxy is allowed only via InboundEP"); } (synCtx.getFaultStack().pop()) .handleFault( synCtx, new Exception( "Proxy Service " + name + " message discarded due to the proxy is allowed only via InboundEP")); } else { if (log.isDebugEnabled()) { log.debug( "Proxy Service " + name + " message discarded due to the proxy is " + "allowed only via InboundEP"); } } return; } } TenantInfoConfigurator configurator = synCtx.getEnvironment().getTenantInfoConfigurator(); if (configurator != null) { configurator.extractTenantInfo(synCtx); } TransportInDescription trpInDesc = mc.getTransportIn(); if (trpInDesc != null) { synCtx.setProperty(SynapseConstants.TRANSPORT_IN_NAME, trpInDesc.getName()); } // get service log for this message and attach to the message context also set proxy name Log serviceLog = LogFactory.getLog(SynapseConstants.SERVICE_LOGGER_PREFIX + name); ((Axis2MessageContext) synCtx).setServiceLog(serviceLog); synCtx.setProperty(SynapseConstants.PROXY_SERVICE, name); // synCtx.setTracingState(proxy.getTraceState()); synCtx.setProperty(SynapseConstants.IS_CLIENT_DOING_REST, mc.isDoingREST()); synCtx.setProperty(SynapseConstants.IS_CLIENT_DOING_SOAP11, mc.isSOAP11()); try { if (synCtx.getEnvironment().isDebuggerEnabled()) { SynapseDebugManager debugManager = synCtx.getEnvironment().getSynapseDebugManager(); debugManager.acquireMediationFlowLock(); debugManager.advertiseMediationFlowStartPoint(synCtx); if (!synCtx.isResponse()) { SynapseWireLogHolder wireLogHolder = (SynapseWireLogHolder) ((Axis2MessageContext) synCtx) .getAxis2MessageContext() .getProperty(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_HOLDER_PROPERTY); if (wireLogHolder == null) { wireLogHolder = new SynapseWireLogHolder(); } if (synCtx.getProperty(SynapseConstants.PROXY_SERVICE) != null && !synCtx.getProperty(SynapseConstants.PROXY_SERVICE).toString().isEmpty()) { wireLogHolder.setProxyName( synCtx.getProperty(SynapseConstants.PROXY_SERVICE).toString()); } ((Axis2MessageContext) synCtx) .getAxis2MessageContext() .setProperty(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_HOLDER_PROPERTY, wireLogHolder); } } synCtx.setProperty(SynapseConstants.RESPONSE_STATE, new ResponseState()); List handlers = synCtx.getEnvironment().getSynapseHandlers(); Iterator<SynapseHandler> iterator = handlers.iterator(); while (iterator.hasNext()) { SynapseHandler handler = iterator.next(); if (!handler.handleRequestInFlow(synCtx)) { return; } } Mediator mandatorySeq = synCtx.getConfiguration().getMandatorySequence(); if (mandatorySeq != null) { if (log.isDebugEnabled()) { log.debug( "Start mediating the message in the " + "pre-mediate state using the mandatory sequence"); } if (!mandatorySeq.mediate(synCtx)) { if (log.isDebugEnabled()) { log.debug( "Request message for the proxy service " + name + " dropped in " + "the pre-mediation state by the mandatory sequence : \n" + synCtx); } return; } } // setup fault sequence - i.e. what happens when something goes wrong with this message proxy.registerFaultHandler(synCtx); boolean inSequenceResult = true; // Using inSequence for the incoming message mediation if (proxy.getTargetInSequence() != null) { Mediator inSequence = synCtx.getSequence(proxy.getTargetInSequence()); if (inSequence != null) { traceOrDebug( traceOn, "Using sequence named : " + proxy.getTargetInSequence() + " for incoming message mediation"); inSequenceResult = inSequence.mediate(synCtx); } else { handleException("Unable to find in-sequence : " + proxy.getTargetInSequence(), synCtx); } } else if (proxy.getTargetInLineInSequence() != null) { traceOrDebug( traceOn, "Using the anonymous " + "in-sequence of the proxy service for mediation"); inSequenceResult = proxy.getTargetInLineInSequence().mediate(synCtx); } // if inSequence returns true, forward message to endpoint if (inSequenceResult) { if (proxy.getTargetEndpoint() != null) { Endpoint endpoint = synCtx.getEndpoint(proxy.getTargetEndpoint()); if (endpoint != null) { traceOrDebug( traceOn, "Forwarding message to the endpoint : " + proxy.getTargetEndpoint()); endpoint.send(synCtx); } else { handleException( "Unable to find the endpoint specified : " + proxy.getTargetEndpoint(), synCtx); } } else if (proxy.getTargetInLineEndpoint() != null) { traceOrDebug( traceOn, "Forwarding the message to the anonymous " + "endpoint of the proxy service"); proxy.getTargetInLineEndpoint().send(synCtx); } } } catch (SynapseException syne) { if (!synCtx.getFaultStack().isEmpty()) { warn(traceOn, "Executing fault handler due to exception encountered", synCtx); ((FaultHandler) synCtx.getFaultStack().pop()).handleFault(synCtx, syne); } else { warn( traceOn, "Exception encountered but no fault handler found - " + "message dropped", synCtx); } } finally { // Statistic reporting if (isStatisticsEnabled) { CloseEventCollector.tryEndFlow( synCtx, this.name, ComponentType.PROXYSERVICE, statisticReportingIndex, true); } if (synCtx.getEnvironment().isDebuggerEnabled()) { SynapseDebugManager debugManager = synCtx.getEnvironment().getSynapseDebugManager(); debugManager.advertiseMediationFlowTerminatePoint(synCtx); debugManager.releaseMediationFlowLock(); } } }