/** * Processes IQ request stanzas (IQ stanzas of type <tt>get</tt> or * <tt>set</tt>. This method will, in order: * <p/> * <ol> * <li>check if the stanza is a valid request stanza. If not, an IQ stanza * of type <tt>error</tt>, condition 'bad-request' is returned;</li> * <li>process Service Discovery requests by calling * {@link #handleDiscoInfo(IQ)} and {@link #handleDiscoItems(IQ)} where * appropriate;</li> * <li>call the abstract methods {@link #handleIQGet()} or * {@link #handleIQSet()} if the above actions did not apply to the request. * </li> * </ol> * <p/> * Note that if this method returns <tt>null</tt>, an IQ stanza of type * <tt>error</tt>, condition <tt>feature-not-implemented</tt> will be * returned to the sender of the original request. This behavior can be * disabled by setting the <tt>enforceIQResult</tt> argument in the * constructor to <tt>false</tt>. * <p/> * Note that if this method throws an Exception, an IQ stanza of type * <tt>error</tt>, condition 'internal-server-error' will be returned to the * sender of the original request. * <p/> * Note that if you want to add or adjust functionality, you should * <strong>not</strong> override this method. Instead, you probably want to * override any of these methods: {@link #handleIQGet()}, * {@link #handleIQSet()}, {@link #handleDiscoInfo(IQ)} and/or * {@link #handleDiscoItems(IQ)} * * @param iq * The IQ request stanza. * @return Response to the request, or null to indicate a * 'feature-not-implemented' error. */ private sealed IQ processIQRequest(IQ iq) { log.Debug("(serving component '{}') Processing IQ " + "request (packetId {}).", getName(), iq.getID()); // IQ get (and set) stanza's MUST be replied to. XElement childElement = iq.getChildElement(); string @namespace = null; if (childElement != null) { @namespace = childElement.getNamespaceURI(); } if (@namespace == null) { log.Debug("(serving component '{}') Invalid XMPP " + "- no child element or namespace in IQ " + "request (packetId {})", getName(), iq.getID()); // this isn't valid XMPP. IQ response = IQ.createResultIQ(iq); response.setError(Condition.bad_request); return response; } // check if this is a component for local users only. if (servesLocalUsersOnly() && !sentByLocalEntity(iq)) { log.Info("(serving component '{}') Returning " + "'not-authorized' IQ error to a user from " + "another domain: {}", getName(), iq.getFrom()); log.Debug("(serving component '{}') Returning " + "'not-authorized' IQ error to a user from " + "another domain: {}", getName(), iq.toXML()); IQ error = IQ.createResultIQ(iq); error.setError(Condition.not_authorized); return error; } Type type = iq.getType(); if (type == Type.get) { if (NAMESPACE_DISCO_INFO.equals(@namespace)) { log.Trace("(serving component '{}') " + "Calling #handleDiscoInfo() (packetId {}).", getName(), iq.getID()); return handleDiscoInfo(iq); } else if (NAMESPACE_DISCO_ITEMS.equals(@namespace)) { log.Trace("(serving component '{}') " + "Calling #handleDiscoItems() (packetId {}).", getName(), iq.getID()); return handleDiscoItems(iq); } else if (NAMESPACE_XMPP_PING.equals(@namespace)) { log.Trace("(serving component '{}') " + "Calling #handlePing() (packetId {}).", getName(), iq .getID()); return handlePing(iq); } else if (NAMESPACE_LAST_ACTIVITY.equals(@namespace)) { log.Trace("(serving component '{}') " + "Calling #handleLastActivity() (packetId {}).", getName(), iq .getID()); return handleLastActivity(iq); } else if (NAMESPACE_ENTITY_TIME.equals(@namespace)) { log.Trace("(serving component '{}') " + "Calling #handleEntityTime() (packetId {}).", getName(), iq .getID()); return handleEntityTime(iq); } else { return handleIQGet(iq); } } if (type == Type.set) { return handleIQSet(iq); } // If by now we didn't do anything to the packet, we don't know what to // do with this. Return error (as it is a SET or GET stanza, which MUST // be replied to). return null; }
/** * This method applies default processing to received IQ stanzas. This * method: * <p/> * <ul> * <li>calls methods to process IQ requests (type <tt>get</tt> and * <tt>set</tt>). If no response to these request are returned, this method * will respond to the request with an IQ stanza of type <tt>error</tt>, * containing an error condition <tt>feature-not-implemented</tt> (this * behavior can be disabled by setting the <tt>enforceIQResult</tt> argument * in the constructor to <tt>false</tt>);</li> * <li>calls methods to process IQ results (type <tt>result</tt> and * <tt>error</tt>). No response to these stanzas are expected;</li> * <li>returns an IQ stanza of type error, condition 'internal-server-error' * if the processing of an IQ request (type <tt>get</tt> or <tt>set</tt>) * resulted in an Exception being thrown.</li> * </ul> * <p/> * Note that if you want to add or adjust functionality, you should * <strong>not</strong> override this method. Instead, you probably want to * override any of these methods: {@link #handleIQGet()}, * {@link #handleIQSet()}, {@link #handleIQResult(IQ)}, * {@link #handleIQError(IQ)} {@link #handleDiscoInfo(IQ)} and/or * {@link #handleDiscoItems(IQ)} * * @param iq * The IQ stanza that was received by this component. */ private sealed void processIQ(IQ iq) { log.Debug("(serving component '{}') Processing IQ (packetId {}): {}", new Object[] {getName(), iq.getID(), iq.toXML() }); IQ response = null; Type type = iq.getType(); try { switch (type) { case get: // intended fall-through case set: // cache the id, to prevent the extending implementation from // modifying it. String requestID = iq.getID(); response = processIQRequest(iq); // validate the response IQ stanza. if (response == null) { // A request (IQ type 'get' or 'set') MUST be responded to. // If no response was generated, create an 'error' type // response. if (enforceIQResult) { response = IQ.createResultIQ(iq); response.setError(Condition.feature_not_implemented); } } else { // responses MUST be of type 'result' or 'error'. Everything // else is invalid. if (!response.isResponse()) { throw new IllegalStateException("Responses to IQ " + "of type <tt>get</tt> or <tt>set</tt> can " + "only be IQ stanza's of type <tt>error</tt> " + "or <tt>result</tt>. The response to this " + "packet was incorrect: " + iq.toXML() + ". The response was: " + response.toXML()); } // responses must have the same packet ID as the request if (!requestID.equals(response.getID())) { throw new IllegalStateException("The response to " + "an request IQ must have the same packet " + "ID. If this was done intentionally, " + "#send(Packet) should have been used " + "instead. The response to this packet " + "was incorrect: " + iq.toXML() + ". The response was: " + response.toXML()); } log.debug("(serving component '{}') Responding to IQ (packetId {}) with: {}", new Object[] { getName(), iq.getID(), response.toXML() }); } break; case result: if (servesLocalUsersOnly() && !sentByLocalEntity(iq)) { log.info("(serving component '{}') Dropping IQ " + "stanza sent by a user from another domain: {}", getName(), iq.getFrom()); log.debug("(serving component '{}') Dropping IQ " + "stanza sent by a user from another domain: {}", getName(), iq.toXML()); return; } handleIQResult(iq); break; case error: if (servesLocalUsersOnly() && !sentByLocalEntity(iq)) { log.info("(serving component '{}') Dropping IQ " + "stanza sent by a user from another domain: {}", getName(), iq.getFrom()); log.debug("(serving component '{}') Dropping IQ " + "stanza sent by a user from another domain: {}", getName(), iq.toXML()); return; } handleIQError(iq); break; } } catch (Exception ex) { log.warn("(serving component '" + getName() + "') Unexpected exception while processing IQ stanza: " + iq.toXML(), ex); if (iq.isRequest()) { // if the received IQ stanza was a 'get' or 'set' request, // return an error, as some kind of response MUST be sent back // to those stanzas. response = IQ.createResultIQ(iq); response.setError(Condition.internal_server_error); } } // send the response, if there's any. if (response != null) { send(response); } }