Skip to content

Commit e10f886

Browse files
authored
Merge pull request #1195 from tulinkry/messages
messaging addition
2 parents 2de8586 + 7321b96 commit e10f886

32 files changed

+2625
-130
lines changed

README.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,41 @@ Each of these directories was created with 'cvs checkout' command (with
9292
appropriate arguments to get given branch) and will be treated by OpenGrok
9393
as a project.
9494

95+
3.2 Messages
96+
------------
97+
98+
Deployed OpenGrok can receive couple of messages through the active socket which
99+
usually listens for the main configuration file. These are used in the web
100+
application and displayed to the users. One can easily notify users about some
101+
important events, for example that the reindex is being in progress and that
102+
the searched information can be inconsistent.
103+
104+
The OpenGrok comes with a tool which allows you to send these messages without
105+
any problem. It is called Messages and it is located under the tools directory.
106+
See the file for usage and more information.
107+
108+
3.2.1 Tags
109+
----------
110+
111+
Any message can use tags which makes it more specific for the application.
112+
Messages which tag match some OpenGrok project are considered project specific
113+
and the information contained in them are displayed only for the specific projects.
114+
115+
There is a key tag "main" which is exclusive for displaying
116+
messages on the OpenGrok landing page - like a common information.
117+
118+
3.2.2 Types
119+
-----------
120+
121+
Currently supported message types:
122+
1) NormalMessage (normal)
123+
This message is designed to display some information in the web application.
124+
Use tags to target a specific project.
125+
2) AbortMessage (abort)
126+
This message can delete some already published information in
127+
the web application.
128+
Use tags to restrict the deletion only to specific projects.
129+
95130

96131
4. OpenGrok install
97132
-----------------

platform/solaris/ips/create.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ PKG pkgsend add file platform/solaris/smf/svc-opengrok mode=0555 owner=root grou
198198
PKG pkgsend add file platform/solaris/smf/ogindexd mode=0555 owner=root group=bin path=/usr/opengrok/lib/ogindexd
199199

200200
PKG pkgsend add file OpenGrok mode=0555 owner=root group=bin path=/usr/opengrok/bin/OpenGrok
201-
PKG pkgsend add file tools/Groups mode=0555 owner=root group=sys path=/usr/opengrok/bin/Groups
201+
PKG pkgsend add file tools/Groups mode=0555 owner=root group=bin path=/usr/opengrok/bin/Groups
202+
PKG pkgsend add file tools/Messages mode=0555 owner=root group=bin path=/usr/opengrok/bin/Messages
202203

203204
PKG pkgsend add file dist/opengrok.jar mode=0444 owner=root group=bin path=/usr/opengrok/lib/opengrok.jar
204205

platform/solaris/pkgdef/prototype

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ f manifest var/svc/manifest/application/opengrok.xml=platform/solaris/smf/opengr
4949
f none lib/svc/method/svc-opengrok=platform/solaris/smf/svc-opengrok 555 root bin
5050
f none usr/opengrok/bin/OpenGrok=OpenGrok 0555 bin bin
5151
f none usr/opengrok/bin/Groups=tools/Groups 0555 bin bin
52+
f none usr/opengrok/bin/Groups=tools/Messages 0555 bin bin
5253
f none usr/opengrok/doc/logging.properties=logging.properties 0444 root sys
5354
f none usr/opengrok/doc/README.txt=README.txt 0444 root sys
5455
f none usr/opengrok/doc/CHANGES.txt=CHANGES.txt 0444 root sys

src/org/opensolaris/opengrok/configuration/RuntimeEnvironment.java

Lines changed: 220 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,45 +50,44 @@
5050
import java.util.List;
5151
import java.util.Map;
5252
import java.util.Set;
53+
import java.util.SortedSet;
54+
import java.util.Timer;
55+
import java.util.TimerTask;
5356
import java.util.TreeMap;
5457
import java.util.TreeSet;
58+
import java.util.concurrent.ConcurrentHashMap;
59+
import java.util.concurrent.ConcurrentMap;
60+
import java.util.concurrent.ConcurrentSkipListSet;
5561
import java.util.concurrent.ExecutorService;
5662
import java.util.concurrent.Executors;
5763
import java.util.concurrent.ThreadFactory;
5864
import java.util.concurrent.TimeUnit;
5965
import java.util.function.Predicate;
6066
import java.util.logging.Level;
6167
import java.util.logging.Logger;
68+
import java.util.stream.Collectors;
69+
import org.apache.lucene.index.IndexReader;
70+
import org.apache.lucene.index.MultiReader;
71+
import org.apache.lucene.search.IndexSearcher;
72+
import org.apache.lucene.search.SearcherManager;
73+
import org.apache.lucene.store.AlreadyClosedException;
74+
import org.apache.lucene.store.Directory;
75+
import org.apache.lucene.store.FSDirectory;
6276
import org.opensolaris.opengrok.authorization.AuthorizationFramework;
77+
import org.opensolaris.opengrok.configuration.messages.Message;
6378
import org.opensolaris.opengrok.history.HistoryGuru;
6479
import org.opensolaris.opengrok.history.RepositoryInfo;
6580
import org.opensolaris.opengrok.index.Filter;
6681
import org.opensolaris.opengrok.index.IgnoredNames;
82+
import org.opensolaris.opengrok.index.IndexDatabase;
6783
import org.opensolaris.opengrok.logger.LoggerFactory;
6884
import org.opensolaris.opengrok.util.Executor;
6985
import org.opensolaris.opengrok.util.IOUtils;
70-
import org.opensolaris.opengrok.configuration.ThreadpoolSearcherFactory;
7186

7287
import static java.nio.file.FileVisitResult.CONTINUE;
7388
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
7489
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
7590
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
76-
import java.util.Collections;
77-
import java.util.Iterator;
78-
import java.util.SortedSet;
79-
import java.util.concurrent.ConcurrentHashMap;
80-
import java.util.stream.Collectors;
81-
import org.apache.lucene.index.DirectoryReader;
82-
import org.apache.lucene.index.IndexReader;
83-
import org.apache.lucene.index.MultiReader;
84-
import org.apache.lucene.search.IndexSearcher;
85-
import org.apache.lucene.search.SearcherFactory;
86-
import org.apache.lucene.search.SearcherManager;
87-
import org.apache.lucene.store.AlreadyClosedException;
88-
import org.apache.lucene.store.Directory;
89-
import org.apache.lucene.store.FSDirectory;
90-
import org.opensolaris.opengrok.index.IndexDatabase;
91-
9291

9392
/**
9493
* The RuntimeEnvironment class is used as a placeholder for the current
@@ -108,6 +107,17 @@ public final class RuntimeEnvironment {
108107
private final Map<Project, List<RepositoryInfo>> repository_map = new TreeMap<>();
109108
private final Map<Project, Set<Group>> project_group_map = new TreeMap<>();
110109
private final Map<String, SearcherManager> searcherManagerMap = new ConcurrentHashMap<>();
110+
111+
private static final String MESSAGES_MAIN_PAGE_TAG = "main";
112+
/*
113+
initial capacity - default 16
114+
initial load factor - default 0.75f
115+
initial concurrency level - number of concurrently updating threads (default 16)
116+
- just two (the timer, configuration listener) so set it to small value
117+
*/
118+
private final ConcurrentMap<String, SortedSet<Message>> tagMessages = new ConcurrentHashMap<>(16, 0.75f, 5);
119+
private static final int MESSAGE_LIMIT = 500;
120+
private int messagesInTheSystem = 0;
111121

112122
/* Get thread pool used for top-level repository history generation. */
113123
public static synchronized ExecutorService getHistoryExecutor() {
@@ -1210,6 +1220,167 @@ public void setConfiguration(Configuration configuration, List<String> subFileLi
12101220
public Configuration getConfiguration() {
12111221
return this.threadConfig.get();
12121222
}
1223+
1224+
private Timer expirationTimer;
1225+
1226+
private static SortedSet<Message> emptyMessageSet(SortedSet<Message> toRet) {
1227+
return toRet == null ? new TreeSet<>() : toRet;
1228+
}
1229+
1230+
/**
1231+
* Get the default set of messages for the main tag.
1232+
*
1233+
* @return set of messages
1234+
*/
1235+
public SortedSet<Message> getMessages() {
1236+
if (expirationTimer == null) {
1237+
expireMessages();
1238+
}
1239+
return emptyMessageSet(tagMessages.get(MESSAGES_MAIN_PAGE_TAG));
1240+
}
1241+
1242+
/**
1243+
* Get the set of messages for the arbitrary tag
1244+
*
1245+
* @param tag the message tag
1246+
* @return set of messages
1247+
*/
1248+
public SortedSet<Message> getMessages(String tag) {
1249+
if (expirationTimer == null) {
1250+
expireMessages();
1251+
}
1252+
return emptyMessageSet(tagMessages.get(tag));
1253+
}
1254+
1255+
/**
1256+
* Add a message to the application Also schedules a expirationTimer to
1257+
* remove this message after its expiration.
1258+
*
1259+
* @param m the message
1260+
*/
1261+
public void addMessage(Message m) {
1262+
if (!canAcceptMessage(m)) {
1263+
return;
1264+
}
1265+
1266+
if (expirationTimer == null) {
1267+
expireMessages();
1268+
}
1269+
1270+
boolean added = false;
1271+
for (String tag : m.getTags()) {
1272+
if (!tagMessages.containsKey(tag)) {
1273+
tagMessages.put(tag, new ConcurrentSkipListSet<>());
1274+
}
1275+
if (tagMessages.get(tag).add(m)) {
1276+
messagesInTheSystem++;
1277+
added = true;
1278+
}
1279+
}
1280+
1281+
if (added) {
1282+
if (expirationTimer != null) {
1283+
expirationTimer.schedule(new TimerTask() {
1284+
@Override
1285+
public void run() {
1286+
expireMessages();
1287+
}
1288+
}, new Date(m.getExpiration().getTime() + 10));
1289+
}
1290+
}
1291+
}
1292+
1293+
/**
1294+
* Immediately remove all messages in the application.
1295+
*/
1296+
public void removeAllMessages() {
1297+
tagMessages.clear();
1298+
messagesInTheSystem = 0;
1299+
}
1300+
1301+
/**
1302+
* Remove all messages containing at least on of the tags.
1303+
*
1304+
* @param tags set of tags
1305+
*/
1306+
public void removeAnyMessage(Set<String> tags) {
1307+
removeAnyMessage(new Predicate<Message>() {
1308+
@Override
1309+
public boolean test(Message t) {
1310+
return t.hasAny(tags);
1311+
}
1312+
});
1313+
}
1314+
1315+
/**
1316+
* Remove messages which have expired.
1317+
*/
1318+
private void expireMessages() {
1319+
removeAnyMessage(new Predicate<Message>() {
1320+
@Override
1321+
public boolean test(Message t) {
1322+
return t.isExpired();
1323+
}
1324+
});
1325+
}
1326+
1327+
/**
1328+
* Generic function to remove any message according to the result of the
1329+
* predicate.
1330+
*
1331+
* @param predicate the testing predicate
1332+
*/
1333+
private void removeAnyMessage(Predicate<Message> predicate) {
1334+
int size;
1335+
for (Map.Entry<String, SortedSet<Message>> set : tagMessages.entrySet()) {
1336+
size = set.getValue().size();
1337+
set.getValue().removeIf(predicate);
1338+
messagesInTheSystem -= size - set.getValue().size();
1339+
}
1340+
1341+
tagMessages.entrySet().removeIf(new Predicate<Map.Entry<String, SortedSet<Message>>>() {
1342+
@Override
1343+
public boolean test(Map.Entry<String, SortedSet<Message>> t) {
1344+
return t.getValue().isEmpty();
1345+
}
1346+
});
1347+
}
1348+
1349+
/**
1350+
* Test if the application can receive this messages.
1351+
*
1352+
* @param m the message
1353+
* @return true if it can
1354+
*/
1355+
public boolean canAcceptMessage(Message m) {
1356+
return messagesInTheSystem < getMessageLimit() && !m.isExpired();
1357+
}
1358+
1359+
/**
1360+
* Get the maximum number of messages in the application
1361+
*
1362+
* @return the number
1363+
*/
1364+
public int getMessageLimit() {
1365+
return MESSAGE_LIMIT;
1366+
}
1367+
1368+
/**
1369+
* Return number of messages present in the hash map.
1370+
*
1371+
* DISCLAIMER: This is not the real number of messages in the application
1372+
* because the same message is stored for all of the tags in the map. Also
1373+
* one can bypass the counter by not calling {@link #addMessage(Message)}
1374+
*
1375+
* @return number of messages
1376+
*/
1377+
public int getMessagesInTheSystem() {
1378+
if (expirationTimer == null) {
1379+
expireMessages();
1380+
}
1381+
return messagesInTheSystem;
1382+
}
1383+
12131384
private ServerSocket configServerSocket;
12141385

12151386
/**
@@ -1239,7 +1410,7 @@ public boolean startConfigurationListenerThread(SocketAddress endpoint) {
12391410
configurationListenerThread = new Thread(new Runnable() {
12401411
@Override
12411412
public void run() {
1242-
ByteArrayOutputStream bos = new ByteArrayOutputStream(1 << 13);
1413+
ByteArrayOutputStream bos = new ByteArrayOutputStream(1 << 15);
12431414
while (!sock.isClosed()) {
12441415
try (Socket s = sock.accept();
12451416
BufferedInputStream in = new BufferedInputStream(s.getInputStream())) {
@@ -1265,7 +1436,7 @@ public void run() {
12651436
((Configuration) obj).refreshDateForLastIndexRun();
12661437
setConfiguration((Configuration) obj);
12671438
LOGGER.log(Level.INFO, "Configuration updated: {0}",
1268-
configuration.getSourceRoot());
1439+
configuration.getSourceRoot());
12691440

12701441
// We are assuming that each update of configuration
12711442
// means reindex. If dedicated thread is introduced
@@ -1274,6 +1445,18 @@ public void run() {
12741445
// be moved there.
12751446
refreshSearcherManagerMap();
12761447
maybeRefreshIndexSearchers();
1448+
} else if (obj instanceof Message) {
1449+
Message m = ((Message) obj);
1450+
if (canAcceptMessage(m)) {
1451+
m.apply(RuntimeEnvironment.getInstance());
1452+
LOGGER.log(Level.FINER, "Message received: {0}",
1453+
m.getTags());
1454+
LOGGER.log(Level.FINER, "Messages in the system: {0}",
1455+
getMessagesInTheSystem());
1456+
} else {
1457+
LOGGER.log(Level.WARNING, "Message dropped: {0} - too many messages in the system",
1458+
m.getTags());
1459+
}
12771460
}
12781461
} catch (IOException e) {
12791462
LOGGER.log(Level.SEVERE, "Error reading config file: ", e);
@@ -1389,6 +1572,24 @@ public void stopWatchDogService() {
13891572
}
13901573
}
13911574

1575+
public void startExpirationTimer() {
1576+
if (expirationTimer != null) {
1577+
stopExpirationTimer();
1578+
}
1579+
expirationTimer = new Timer("expirationThread");
1580+
expireMessages();
1581+
}
1582+
1583+
/**
1584+
* Stops the watch dog service.
1585+
*/
1586+
public void stopExpirationTimer() {
1587+
if (expirationTimer != null) {
1588+
expirationTimer.cancel();
1589+
expirationTimer = null;
1590+
}
1591+
}
1592+
13921593
private Thread indexReopenThread;
13931594

13941595
public void maybeRefreshIndexSearchers() {

0 commit comments

Comments
 (0)