Skip to content

Commit ef36929

Browse files
committed
Merging #79.
1 parent e885104 commit ef36929

18 files changed

Lines changed: 1899 additions & 4 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<groupId>org.buddycloud</groupId>
55
<artifactId>channelserver</artifactId>
66
<packaging>jar</packaging>
7-
<version>0.12.0</version>
7+
<version>0.12.0-search</version>
88
<name>Buddycloud Java Server</name>
99
<scm>
1010
<url>https://github.com/buddycloud/buddycloud-server-java</url>

src/main/java/org/buddycloud/channelserver/channel/ChannelManager.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.buddycloud.channelserver.channel;
22

3+
import java.util.List;
4+
5+
import org.buddycloud.channelserver.db.CloseableIterator;
36
import org.buddycloud.channelserver.db.NodeStore;
47
import org.buddycloud.channelserver.db.exception.NodeStoreException;
58
import org.buddycloud.channelserver.pubsub.affiliation.Affiliations;
9+
import org.buddycloud.channelserver.pubsub.model.NodeItem;
610
import org.xmpp.packet.JID;
711

812
public interface ChannelManager extends NodeStore {
@@ -47,9 +51,25 @@ public interface ChannelManager extends NodeStore {
4751

4852
/**
4953
* Gets the default affiliation for a node
50-
* @return
54+
*
55+
* @return
5156
*
5257
* @throws NodeStoreException
5358
*/
54-
Affiliations getDefaultNodeAffiliation(String nodeId) throws NodeStoreException;
59+
Affiliations getDefaultNodeAffiliation(String nodeId)
60+
throws NodeStoreException;
61+
62+
/**
63+
* Searches for the provided content or author, or both, across nodes
64+
* the searcher has access to
65+
*
66+
* @param searcher
67+
* @param content
68+
* @param author
69+
* @param page
70+
* @param rpp
71+
* @return
72+
*/
73+
CloseableIterator<NodeItem> performSearch(JID searcher, List content, JID author, int page,
74+
int rpp) throws NodeStoreException;
5575
}

src/main/java/org/buddycloud/channelserver/channel/ChannelManagerImpl.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.ArrayList;
44
import java.util.Date;
5+
import java.util.List;
56
import java.util.Map;
67
import java.util.Properties;
78

@@ -444,6 +445,12 @@ public Affiliations getDefaultNodeAffiliation(String nodeId)
444445
return Affiliations.member;
445446
}
446447

448+
@Override
449+
public CloseableIterator<NodeItem> performSearch(JID searcher,
450+
List content, JID author, int page, int rpp) throws NodeStoreException {
451+
return nodeStore.performSearch(searcher, content, author, page, rpp);
452+
}
453+
447454
@Override
448455
public ResultSet<NodeItem> getUserItems(JID userJid) throws NodeStoreException {
449456
return nodeStore.getUserItems(userJid);
@@ -473,5 +480,6 @@ public ResultSet<NodeThread> getNodeThreads(String node, String afterId,
473480
@Override
474481
public int countNodeThreads(String node) throws NodeStoreException {
475482
return nodeStore.countNodeThreads(node);
476-
}
483+
}
484+
477485
}

src/main/java/org/buddycloud/channelserver/db/NodeStore.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.ArrayList;
44
import java.util.Date;
55
import java.util.Iterator;
6+
import java.util.List;
67
import java.util.Map;
78

89
import org.buddycloud.channelserver.db.exception.NodeStoreException;
@@ -558,6 +559,20 @@ void deleteNodeItemById(String nodeId, String nodeItemId)
558559
*/
559560
ArrayList<String> getNodeList() throws NodeStoreException;
560561

562+
/**
563+
* Search subscribed nodes for content
564+
*
565+
* @param searcher JID of user performing the search
566+
* @param content Keywords upon which to search
567+
* @param author JID of the content author
568+
* @param page Page number of results (>= 1)
569+
* @param rpp Results per page (>= 1)
570+
* @return
571+
* @throws NodeStoreException
572+
*/
573+
CloseableIterator<NodeItem> performSearch(JID searcher, List content,
574+
JID author, int page, int rpp) throws NodeStoreException;
575+
561576
/**
562577
* Retrieves a list of items from public channels "firehose"
563578
*

src/main/java/org/buddycloud/channelserver/db/jdbc/JDBCNodeStore.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Deque;
1212
import java.util.HashMap;
1313
import java.util.LinkedList;
14+
import java.util.List;
1415
import java.util.Map;
1516
import java.util.Map.Entry;
1617

@@ -1532,6 +1533,66 @@ public void purgeNodeItems(String nodeId) throws NodeStoreException {
15321533
close(stmt); // Will implicitly close the resultset if required
15331534
}
15341535
}
1536+
1537+
@Override
1538+
public CloseableIterator<NodeItem> performSearch(JID searcher,
1539+
List content, JID author, int page, int rpp) throws NodeStoreException {
1540+
PreparedStatement stmt = null;
1541+
try {
1542+
String sql = "SELECT * FROM \"items\" " +
1543+
"LEFT JOIN \"subscriptions\" ON \"items\".\"node\" = \"subscriptions\".\"node\" " +
1544+
"WHERE " +
1545+
"\"subscriptions\".\"user\" = ? " +
1546+
"AND \"subscriptions\".\"subscription\" = 'subscribed' " +
1547+
"AND RIGHT(\"items\".\"node\", 6) = '/posts' " +
1548+
" $searchParameters " +
1549+
"ORDER BY \"items\".\"updated\" DESC " +
1550+
"LIMIT ? OFFSET ?;";
1551+
ArrayList<String> parameterValues = new ArrayList<String>();
1552+
parameterValues.add(searcher.toBareJID());
1553+
String searchParameters = "";
1554+
1555+
for (String term : (List<String>) content) {
1556+
searchParameters += "AND (\"items\".\"xml\" LIKE ?) ";
1557+
parameterValues.add("%<content%>%" + term + "%</content>%");
1558+
}
1559+
1560+
if (null != author) {
1561+
searchParameters += "AND (\"items\".\"xml\" LIKE ?)";
1562+
parameterValues.add("%<name>" + author.toBareJID() + "</name>%");
1563+
}
1564+
stmt = conn.prepareStatement(sql.replace("$searchParameters", searchParameters));
1565+
1566+
int counter = 0;
1567+
for (String value : parameterValues) {
1568+
++counter;
1569+
stmt.setString(counter, value);
1570+
}
1571+
stmt.setInt(parameterValues.size() + 1, rpp);
1572+
stmt.setInt(parameterValues.size() + 2, (page - 1) * rpp);
1573+
1574+
java.sql.ResultSet rs = stmt.executeQuery();
1575+
1576+
stmt = null; // Prevent the finally block from closing the
1577+
// statement
1578+
1579+
return new ResultSetIterator<NodeItem>(rs,
1580+
new ResultSetIterator.RowConverter<NodeItem>() {
1581+
@Override
1582+
public NodeItem convertRow(java.sql.ResultSet rs)
1583+
throws SQLException {
1584+
return new NodeItemImpl(rs.getString(1),
1585+
rs.getString(2), rs.getTimestamp(3),
1586+
rs.getString(4), rs.getString(5));
1587+
}
1588+
});
1589+
} catch (SQLException e) {
1590+
e.printStackTrace();
1591+
throw new NodeStoreException(e);
1592+
} finally {
1593+
close(stmt); // Will implicitly close the resultset if required
1594+
}
1595+
}
15351596

15361597
@Override
15371598
public ArrayList<String> getNodeList() throws NodeStoreException {

src/main/java/org/buddycloud/channelserver/packetprocessor/iq/IQProcessor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.buddycloud.channelserver.packetprocessor.iq.namespace.mam.MessageArchiveManagement;
1414
import org.buddycloud.channelserver.packetprocessor.iq.namespace.pubsub.JabberPubsub;
1515
import org.buddycloud.channelserver.packetprocessor.iq.namespace.register.JabberRegister;
16+
import org.buddycloud.channelserver.packetprocessor.iq.namespace.search.Search;
1617
import org.buddycloud.channelserver.queue.FederatedQueueManager;
1718
import org.buddycloud.channelserver.queue.UnknownFederatedPacketException;
1819
import org.xmpp.packet.IQ;
@@ -49,6 +50,8 @@ public IQProcessor(BlockingQueue<Packet> outQueue, Configuration conf,
4950
processorsPerNamespace.put(JabberPubsub.NS_PUBSUB_OWNER, ps);
5051
processorsPerNamespace.put(MessageArchiveManagement.NAMESPACE_MAM,
5152
new MessageArchiveManagement(outQueue, channelManager));
53+
processorsPerNamespace.put(Search.NAMESPACE_URI,
54+
new Search(outQueue, channelManager));
5255
}
5356

5457
@Override

src/main/java/org/buddycloud/channelserver/packetprocessor/iq/namespace/discoinfo/DiscoInfoGet.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ private void sendServerDiscoInfo()
129129

130130
query.addElement("feature").addAttribute("var",
131131
"http://jabber.org/protocol/disco#info");
132+
133+
query.addElement("feature").addAttribute("var",
134+
"http://jabber.org/protocol/disco#items");
135+
136+
query.addElement("feature").addAttribute("var",
137+
"jabber:iq:search");
138+
132139
outQueue.put(result);
133140
}
134141

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.buddycloud.channelserver.packetprocessor.iq.namespace.search;
2+
3+
import java.util.Collection;
4+
import java.util.concurrent.BlockingQueue;
5+
6+
import org.apache.log4j.Logger;
7+
import org.buddycloud.channelserver.Configuration;
8+
import org.buddycloud.channelserver.channel.ChannelManager;
9+
import org.buddycloud.channelserver.channel.Conf;
10+
import org.buddycloud.channelserver.db.exception.NodeStoreException;
11+
import org.buddycloud.channelserver.packetprocessor.PacketProcessor;
12+
import org.buddycloud.channelserver.packetprocessor.iq.namespace.pubsub.JabberPubsub;
13+
import org.buddycloud.channelserver.pubsub.accessmodel.AccessModels;
14+
import org.buddycloud.channelserver.pubsub.model.impl.NodeSubscriptionImpl;
15+
import org.buddycloud.channelserver.pubsub.subscription.Subscriptions;
16+
import org.dom4j.Element;
17+
import org.dom4j.QName;
18+
import org.xmpp.packet.IQ;
19+
import org.xmpp.packet.IQ.Type;
20+
import org.xmpp.packet.JID;
21+
import org.xmpp.packet.Packet;
22+
import org.xmpp.packet.PacketError;
23+
24+
public class Search implements PacketProcessor<IQ> {
25+
26+
public static final String ELEMENT_NAME = "query";
27+
private static final Logger logger = Logger.getLogger(Search.class);
28+
29+
public static final String NAMESPACE_URI = "jabber:iq:search";
30+
31+
private final BlockingQueue<Packet> outQueue;
32+
private final ChannelManager channelManager;
33+
private IQ request;
34+
35+
private SearchGet searchGet;
36+
private SearchSet searchSet;
37+
38+
public Search(BlockingQueue<Packet> outQueue,
39+
ChannelManager channelManager) {
40+
this.outQueue = outQueue;
41+
this.channelManager = channelManager;
42+
43+
this.searchGet = new SearchGet(outQueue, channelManager);
44+
this.searchSet = new SearchSet(outQueue, channelManager);
45+
}
46+
47+
@Override
48+
public void process(IQ reqIQ) throws Exception {
49+
request = reqIQ;
50+
if (request.getType().equals(Type.get)) {
51+
logger.trace("Using search processor: SearchGet");
52+
this.searchGet.process(request);
53+
return;
54+
} else if (request.getType().equals(Type.set)) {
55+
logger.trace("Using search processor: SearchSet");
56+
this.searchSet.process(request);
57+
return;
58+
}
59+
IQ response = IQ.createResultIQ(request);
60+
response.setType(IQ.Type.error);
61+
PacketError error = new PacketError(
62+
PacketError.Condition.bad_request,
63+
PacketError.Type.modify
64+
);
65+
response.setError(error);
66+
outQueue.put(response);
67+
}
68+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.buddycloud.channelserver.packetprocessor.iq.namespace.search;
2+
3+
import java.util.concurrent.BlockingQueue;
4+
5+
import org.buddycloud.channelserver.channel.ChannelManager;
6+
import org.buddycloud.channelserver.packetprocessor.PacketProcessor;
7+
import org.dom4j.Element;
8+
import org.xmpp.forms.DataForm;
9+
import org.xmpp.packet.IQ;
10+
import org.xmpp.packet.Packet;
11+
import org.xmpp.packet.PacketError;
12+
import org.xmpp.packet.PacketError.Type;
13+
14+
public class SearchGet implements PacketProcessor<IQ> {
15+
16+
public static final String INSTRUCTIONS = "Search for content/hashtags/mentions";
17+
18+
public static final String TITLE = "Please populate one or more of the following fields";
19+
public static final String CONTENT_FIELD_LABEL = "Content search";
20+
public static final String AUTHOR_FIELD_LABEL = "Author";
21+
public static final String RPP_FIELD_LABEL = "Results per page";
22+
public static final String PAGE_FIELD_LABEL = "Page";
23+
24+
private ChannelManager channelManager;
25+
private BlockingQueue<Packet> outQueue;
26+
private IQ response;
27+
28+
private Element x;
29+
30+
public SearchGet(BlockingQueue<Packet> outQueue,
31+
ChannelManager channelManager) {
32+
this.channelManager = channelManager;
33+
this.outQueue = outQueue;
34+
}
35+
36+
@Override
37+
public void process(IQ request) throws Exception {
38+
response = IQ.createResultIQ(request);
39+
40+
if (false == channelManager.isLocalJID(request.getFrom())) {
41+
sendErrorResponse(PacketError.Type.cancel,
42+
PacketError.Condition.not_allowed);
43+
return;
44+
}
45+
46+
Element query = response.getElement().addElement("query");
47+
query.addAttribute("xmlns", Search.NAMESPACE_URI);
48+
query.addElement("instructions").addText(INSTRUCTIONS);
49+
x = query.addElement("x");
50+
addFields();
51+
outQueue.put(response);
52+
}
53+
54+
private void addFields() {
55+
x.addAttribute("xmlns", DataForm.NAMESPACE);
56+
x.addElement("title").addText(TITLE);
57+
x.addElement("instructions").addText(INSTRUCTIONS);
58+
59+
Element formType = x.addElement("field");
60+
formType.addAttribute("type", "hidden");
61+
formType.addAttribute("var", "FORM_TYPE");
62+
formType.addElement("value").addText(Search.NAMESPACE_URI);
63+
64+
Element content = x.addElement("field");
65+
content.addAttribute("type", "text-multi");
66+
content.addAttribute("var", "content");
67+
content.addAttribute("label", CONTENT_FIELD_LABEL);
68+
69+
Element author = x.addElement("field");
70+
author.addAttribute("type", "jid-single");
71+
author.addAttribute("var", "author");
72+
author.addAttribute("label", AUTHOR_FIELD_LABEL);
73+
74+
Element rpp = x.addElement("field");
75+
rpp.addAttribute("type", "fixed");
76+
rpp.addAttribute("var", "rpp");
77+
rpp.addAttribute("label", RPP_FIELD_LABEL);
78+
79+
Element page = x.addElement("field");
80+
page.addAttribute("type", "fixed");
81+
page.addAttribute("var", "page");
82+
page.addAttribute("label", PAGE_FIELD_LABEL);
83+
}
84+
85+
private void sendErrorResponse(PacketError.Type type,
86+
PacketError.Condition condition) throws InterruptedException {
87+
response.setType(IQ.Type.error);
88+
PacketError error = new PacketError(PacketError.Condition.not_allowed,
89+
PacketError.Type.cancel);
90+
response.setError(error);
91+
outQueue.put(response);
92+
}
93+
94+
}

0 commit comments

Comments
 (0)