Skip to content

Commit b84df05

Browse files
committed
[search] Overhaul UserSearchManager and add integration test
1 parent e0d0c39 commit b84df05

4 files changed

Lines changed: 138 additions & 101 deletions

File tree

smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818

1919
import java.io.IOException;
2020

21-
import org.jivesoftware.smack.SmackException.NoResponseException;
22-
import org.jivesoftware.smack.SmackException.NotConnectedException;
23-
import org.jivesoftware.smack.XMPPConnection;
24-
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
2521
import org.jivesoftware.smack.packet.IQ;
2622
import org.jivesoftware.smack.packet.IqData;
2723
import org.jivesoftware.smack.packet.SimpleIQ;
@@ -32,10 +28,6 @@
3228
import org.jivesoftware.smack.xml.XmlPullParser;
3329
import org.jivesoftware.smack.xml.XmlPullParserException;
3430

35-
import org.jivesoftware.smackx.xdata.packet.DataForm;
36-
37-
import org.jxmpp.jid.DomainBareJid;
38-
3931
/**
4032
* Implements the protocol currently used to search information repositories on the Jabber network. To date, the jabber:iq:search protocol
4133
* has been used mainly to search for people who have registered with user directories (e.g., the "Jabber User Directory" hosted at users.jabber.org).
@@ -58,70 +50,6 @@ public UserSearch() {
5850
super(ELEMENT, NAMESPACE);
5951
}
6052

61-
/**
62-
* Returns the form for all search fields supported by the search service.
63-
*
64-
* @param con the current XMPPConnection.
65-
* @param searchService the search service to use. (ex. search.jivesoftware.com)
66-
* @return the search form received by the server.
67-
* @throws XMPPErrorException if there was an XMPP error returned.
68-
* @throws NoResponseException if there was no response from the remote entity.
69-
* @throws NotConnectedException if the XMPP connection is not connected.
70-
* @throws InterruptedException if the calling thread was interrupted.
71-
*/
72-
public DataForm getSearchForm(XMPPConnection con, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
73-
UserSearch search = new UserSearch();
74-
search.setType(IQ.Type.get);
75-
search.setTo(searchService);
76-
77-
IQ response = con.sendIqRequestAndWaitForResponse(search);
78-
return DataForm.from(response, NAMESPACE);
79-
}
80-
81-
/**
82-
* Sends the filled out answer form to be sent and queried by the search service.
83-
*
84-
* @param con the current XMPPConnection.
85-
* @param searchForm the <code>Form</code> to send for querying.
86-
* @param searchService the search service to use. (ex. search.jivesoftware.com)
87-
* @return ReportedData the data found from the query.
88-
* @throws XMPPErrorException if there was an XMPP error returned.
89-
* @throws NoResponseException if there was no response from the remote entity.
90-
* @throws NotConnectedException if the XMPP connection is not connected.
91-
* @throws InterruptedException if the calling thread was interrupted.
92-
*/
93-
public ReportedData sendSearchForm(XMPPConnection con, DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
94-
UserSearch search = new UserSearch();
95-
search.setType(IQ.Type.set);
96-
search.setTo(searchService);
97-
search.addExtension(searchForm);
98-
99-
IQ response = con.sendIqRequestAndWaitForResponse(search);
100-
return ReportedData.getReportedDataFrom(response);
101-
}
102-
103-
/**
104-
* Sends the filled out answer form to be sent and queried by the search service.
105-
*
106-
* @param con the current XMPPConnection.
107-
* @param searchForm the <code>Form</code> to send for querying.
108-
* @param searchService the search service to use. (ex. search.jivesoftware.com)
109-
* @return ReportedData the data found from the query.
110-
* @throws XMPPErrorException if there was an XMPP error returned.
111-
* @throws NoResponseException if there was no response from the remote entity.
112-
* @throws NotConnectedException if the XMPP connection is not connected.
113-
* @throws InterruptedException if the calling thread was interrupted.
114-
*/
115-
public ReportedData sendSimpleSearchForm(XMPPConnection con, DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
116-
SimpleUserSearch search = new SimpleUserSearch();
117-
search.setForm(searchForm);
118-
search.setType(IQ.Type.set);
119-
search.setTo(searchService);
120-
121-
SimpleUserSearch response = con.sendIqRequestAndWaitForResponse(search);
122-
return response.getReportedData();
123-
}
124-
12553
/**
12654
* Internal Search service Provider.
12755
*/
Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2003-2007 Jive Software.
3+
* Copyright 2003-2007 Jive Software, 2025 Florian Schmaus.
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -17,11 +17,15 @@
1717
package org.jivesoftware.smackx.search;
1818

1919
import java.util.List;
20+
import java.util.Map;
21+
import java.util.WeakHashMap;
2022

23+
import org.jivesoftware.smack.Manager;
2124
import org.jivesoftware.smack.SmackException.NoResponseException;
2225
import org.jivesoftware.smack.SmackException.NotConnectedException;
2326
import org.jivesoftware.smack.XMPPConnection;
2427
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
28+
import org.jivesoftware.smack.packet.IQ;
2529

2630
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
2731
import org.jivesoftware.smackx.xdata.form.FillableForm;
@@ -36,32 +40,40 @@
3640
* searching (DataForms or No DataForms), but allows the user to simply use the DataForm model for both
3741
* types of support.
3842
* <pre>
39-
* XMPPConnection con = new XMPPTCPConnection("jabber.org");
40-
* con.login("john", "doe");
41-
* UserSearchManager search = new UserSearchManager(con, "users.jabber.org");
42-
* Form searchForm = search.getSearchForm();
43-
* FillableForm answerForm = searchForm.getFillableForm()
44-
* // Fill out the form.
45-
* answerForm.setAnswer("last", "DeMoro");
46-
* ReportedData data = search.getSearchResults(answerForm);
47-
* // Use Returned Data
43+
* XMPPConnection connection = …;
44+
* var searchService = UserSearchManager.getSearchServices(connection).get(0);
45+
* var searchManager = UserSearchManager.getInstanceFor(connection);
46+
* var sarchForm = searchManager.getSearchForm(searchService);
47+
* var fillableForm = searchForm.getFillableForm();
48+
*
49+
* // Check for the required fields in the form and fill them
50+
* fillableForm.setAnswer("search", "John");
51+
*
52+
* var results = searchOne.search(fillableForm, userSearchService);
53+
* // Use results
4854
* </pre>
4955
*
5056
* @author Derek DeMoro
5157
*/
52-
public class UserSearchManager {
58+
public final class UserSearchManager extends Manager {
5359

54-
private final XMPPConnection con;
55-
private final UserSearch userSearch;
60+
private static final Map<XMPPConnection, UserSearchManager> INSTANCES = new WeakHashMap<>();
5661

62+
public static synchronized UserSearchManager getInstanceFor(XMPPConnection connection) {
63+
var userSearchManager = INSTANCES.get(connection);
64+
if (userSearchManager == null) {
65+
userSearchManager = new UserSearchManager(connection);
66+
INSTANCES.put(connection, userSearchManager);
67+
}
68+
return userSearchManager;
69+
}
5770
/**
5871
* Creates a new UserSearchManager.
5972
*
60-
* @param con the XMPPConnection to use.
73+
* @param connection the XMPPConnection to use.
6174
*/
62-
public UserSearchManager(XMPPConnection con) {
63-
this.con = con;
64-
userSearch = new UserSearch();
75+
private UserSearchManager(XMPPConnection connection) {
76+
super(connection);
6577
}
6678

6779
/**
@@ -75,39 +87,70 @@ public UserSearchManager(XMPPConnection con) {
7587
* @throws InterruptedException if the calling thread was interrupted.
7688
*/
7789
public Form getSearchForm(DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
78-
DataForm dataForm = userSearch.getSearchForm(con, searchService);
90+
UserSearch search = new UserSearch();
91+
search.setType(IQ.Type.get);
92+
search.setTo(searchService);
93+
94+
IQ response = connection().sendIqRequestAndWaitForResponse(search);
95+
var dataForm = DataForm.from(response, UserSearch.NAMESPACE);
7996
return new Form(dataForm);
8097
}
8198

8299
/**
83-
* Submits a search form to the server and returns the resulting information
84-
* in the form of <code>ReportedData</code>.
100+
* Sends the filled out answer form to be sent and queried by the search service.
85101
*
86-
* @param searchForm the <code>Form</code> to submit for searching.
87-
* @param searchService the name of the search service to use.
88-
* @return the ReportedData returned by the server.
102+
* @param filledForm the filled form with the query instructions.
103+
* @param searchService the search service to use. (ex. search.jivesoftware.com)
104+
* @return ReportedData the data found from the query.
89105
* @throws XMPPErrorException if there was an XMPP error returned.
90106
* @throws NoResponseException if there was no response from the remote entity.
91107
* @throws NotConnectedException if the XMPP connection is not connected.
92108
* @throws InterruptedException if the calling thread was interrupted.
93109
*/
94-
public ReportedData getSearchResults(FillableForm searchForm, DomainBareJid searchService)
110+
public ReportedData search(FillableForm filledForm, DomainBareJid searchService)
95111
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
96-
DataForm dataForm = searchForm.getDataFormToSubmit();
97-
return userSearch.sendSearchForm(con, dataForm, searchService);
112+
UserSearch search = new UserSearch();
113+
search.setType(IQ.Type.set);
114+
search.setTo(searchService);
115+
search.addExtension(filledForm.getDataFormToSubmit());
116+
117+
IQ response = connection().sendIqRequestAndWaitForResponse(search);
118+
return ReportedData.getReportedDataFrom(response);
119+
}
120+
121+
/**
122+
* Sends the filled out answer form to be sent and queried by the search service.
123+
*
124+
* @param searchForm the <code>Form</code> to send for querying.
125+
* @param searchService the search service to use. (ex. search.jivesoftware.com)
126+
* @return ReportedData the data found from the query.
127+
* @throws XMPPErrorException if there was an XMPP error returned.
128+
* @throws NoResponseException if there was no response from the remote entity.
129+
* @throws NotConnectedException if the XMPP connection is not connected.
130+
* @throws InterruptedException if the calling thread was interrupted.
131+
*/
132+
public ReportedData sendSimpleSearchForm(DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
133+
SimpleUserSearch search = new SimpleUserSearch();
134+
search.setForm(searchForm);
135+
search.setType(IQ.Type.set);
136+
search.setTo(searchService);
137+
138+
SimpleUserSearch response = connection().sendIqRequestAndWaitForResponse(search);
139+
return response.getReportedData();
98140
}
99141

100142
/**
101143
* Returns a collection of search services found on the server.
102144
*
145+
* @param connection the connection to query for search services.
103146
* @return a Collection of search services found on the server.
104147
* @throws XMPPErrorException if there was an XMPP error returned.
105148
* @throws NoResponseException if there was no response from the remote entity.
106149
* @throws NotConnectedException if the XMPP connection is not connected.
107150
* @throws InterruptedException if the calling thread was interrupted.
108151
*/
109-
public List<DomainBareJid> getSearchServices() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
110-
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(con);
111-
return discoManager.findServices(UserSearch.NAMESPACE, false, false);
152+
public static List<DomainBareJid> getSearchServices(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
153+
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
154+
return discoManager.findServices(UserSearch.NAMESPACE, false, true);
112155
}
113156
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
*
3+
* Copyright 2025 Florian Schmaus
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.jivesoftware.smackx.search;
18+
19+
import static org.junit.jupiter.api.Assertions.assertNotNull;
20+
21+
import org.jivesoftware.smack.SmackException.NoResponseException;
22+
import org.jivesoftware.smack.SmackException.NotConnectedException;
23+
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
24+
25+
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
26+
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
27+
import org.igniterealtime.smack.inttest.TestNotPossibleException;
28+
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
29+
30+
import org.jxmpp.jid.DomainBareJid;
31+
32+
public class UserSearchManagerIntegrationTest extends AbstractSmackIntegrationTest {
33+
34+
private final DomainBareJid userSearchService;
35+
36+
private final UserSearchManager searchOne;
37+
38+
public UserSearchManagerIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException,
39+
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException {
40+
super(environment);
41+
42+
var searchServices = UserSearchManager.getSearchServices(conOne);
43+
if (searchServices.isEmpty()) {
44+
throw new TestNotPossibleException("No user seearch services (XEP-0055) found");
45+
}
46+
47+
// Since we are only performing a very primitive test, where we don't expect actual results from the service,
48+
// using any of the returned services should be fine.
49+
userSearchService = searchServices.get(0);
50+
51+
searchOne = UserSearchManager.getInstanceFor(conOne);
52+
}
53+
54+
@SmackIntegrationTest
55+
public void simple() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
56+
var searchForm = searchOne.getSearchForm(userSearchService);
57+
var fillableForm = searchForm.getFillableForm();
58+
for (var r : fillableForm.getMissingRequiredFields()) {
59+
fillableForm.setAnswer(r, "sinttest");
60+
}
61+
62+
var reportedData = searchOne.search(fillableForm, userSearchService);
63+
assertNotNull(reportedData);
64+
}
65+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/search/package-info.java

0 commit comments

Comments
 (0)