001/*
002 *
003 * Copyright 2003-2007 Jive Software, 2025 Florian Schmaus.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.search;
018
019import java.util.List;
020import java.util.Map;
021import java.util.WeakHashMap;
022
023import org.jivesoftware.smack.Manager;
024import org.jivesoftware.smack.SmackException.NoResponseException;
025import org.jivesoftware.smack.SmackException.NotConnectedException;
026import org.jivesoftware.smack.XMPPConnection;
027import org.jivesoftware.smack.XMPPException.XMPPErrorException;
028import org.jivesoftware.smack.packet.IQ;
029
030import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
031import org.jivesoftware.smackx.xdata.form.FillableForm;
032import org.jivesoftware.smackx.xdata.form.Form;
033import org.jivesoftware.smackx.xdata.packet.DataForm;
034
035import org.jxmpp.jid.DomainBareJid;
036
037/**
038 * The UserSearchManager is a facade built upon Jabber Search Services (XEP-055) to allow for searching
039 * repositories on a Jabber Server. This implementation allows for transparency of implementation of
040 * searching (DataForms or No DataForms), but allows the user to simply use the DataForm model for both
041 * types of support.
042 * <pre>
043 * XMPPConnection connection = …;
044 * var searchService = UserSearchManager.getSearchServices(connection).get(0);
045 * var searchManager = UserSearchManager.getInstanceFor(connection);
046 * var sarchForm = searchManager.getSearchForm(searchService);
047 * var fillableForm = searchForm.getFillableForm();
048 *
049 * // Check for the required fields in the form and fill them
050 * fillableForm.setAnswer("search", "John");
051 *
052 * var results = searchOne.search(fillableForm, userSearchService);
053 * // Use results
054 * </pre>
055 *
056 * @author Derek DeMoro
057 */
058public final class UserSearchManager extends Manager {
059
060    private static final Map<XMPPConnection, UserSearchManager> INSTANCES = new WeakHashMap<>();
061
062    public static synchronized UserSearchManager getInstanceFor(XMPPConnection connection) {
063        var userSearchManager = INSTANCES.get(connection);
064        if (userSearchManager == null) {
065            userSearchManager = new UserSearchManager(connection);
066            INSTANCES.put(connection, userSearchManager);
067        }
068        return userSearchManager;
069    }
070    /**
071     * Creates a new UserSearchManager.
072     *
073     * @param connection the XMPPConnection to use.
074     */
075    private UserSearchManager(XMPPConnection connection) {
076        super(connection);
077    }
078
079    /**
080     * Returns the form to fill out to perform a search.
081     *
082     * @param searchService the search service to query.
083     * @return the form to fill out to perform a search.
084     * @throws XMPPErrorException if there was an XMPP error returned.
085     * @throws NoResponseException if there was no response from the remote entity.
086     * @throws NotConnectedException if the XMPP connection is not connected.
087     * @throws InterruptedException if the calling thread was interrupted.
088     */
089    public Form getSearchForm(DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
090        UserSearch search = new UserSearch();
091        search.setType(IQ.Type.get);
092        search.setTo(searchService);
093
094        IQ response = connection().sendIqRequestAndWaitForResponse(search);
095        var dataForm = DataForm.from(response, UserSearch.NAMESPACE);
096        return new Form(dataForm);
097    }
098
099    /**
100     * Sends the filled out answer form to be sent and queried by the search service.
101     *
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.
105     * @throws XMPPErrorException if there was an XMPP error returned.
106     * @throws NoResponseException if there was no response from the remote entity.
107     * @throws NotConnectedException if the XMPP connection is not connected.
108     * @throws InterruptedException if the calling thread was interrupted.
109     */
110    public ReportedData search(FillableForm filledForm, DomainBareJid searchService)
111                    throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
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();
140    }
141
142    /**
143     * Returns a collection of search services found on the server.
144     *
145     * @param connection the connection to query for search services.
146     * @return a Collection of search services found on the server.
147     * @throws XMPPErrorException if there was an XMPP error returned.
148     * @throws NoResponseException if there was no response from the remote entity.
149     * @throws NotConnectedException if the XMPP connection is not connected.
150     * @throws InterruptedException if the calling thread was interrupted.
151     */
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);
155    }
156}