KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > applications > community > builders > Channel


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10
11 package org.mmbase.applications.community.builders;
12
13 import java.util.*;
14 import java.io.*;
15
16 import org.mmbase.module.core.*;
17 import org.mmbase.applications.community.modules.*;
18 import org.mmbase.util.*;
19 import org.mmbase.util.logging.*;
20
21 /**
22  * The channel builder maintains channels for forums and chats.
23  * Each channel is linked to a {@link Community}, which defines some properties for
24  * the channel (such as whether it is a forum or a chatbox).
25  * Users can join a channel (up to a registered amount of users), and post
26  * messages to it (stored in the {@link Message} builder).
27  * <br />
28  * The main tasks defined in this builder are ways to open and close a channel,
29  * join users, generate sequence numbers (which defines the order for a message),
30  * and remove messages belonging to a channel.
31  * Optionally, a recorder (java.io.Writer) object can be opened for a
32  * channel. The Message builder uses these writers for logging chats.
33  * (Note that is would be possible to make a Writer that stores data in a node
34  * or a builder, but this has not been implemented yet).
35  * XXX: Currently, recorder info is NOT stored in the channel. A recorder has to
36  * be activated manually (using $MOD-channelnr-RECORD-FILE-filname).
37  *
38  * @author Dirk-Jan Hoekstra
39  * @author Pierre van Rooden
40  * @version $Id: Channel.java,v 1.29 2005/11/23 15:45:13 pierre Exp $
41  */

42
43 public class Channel extends MMObjectBuilder {
44
45     /** Open state : the channel is closed. */
46     public static final int CLOSED = -1;
47     /** Open state : the channel is waiting to be opened. */
48     public static final int WANT_OPEN = 0;
49     /** Open state : the channel is open for read-only. */
50     public static final int READ_ONLY = 1;
51     /** Open state : the channel is open. */
52     public static final int OPEN = 2;
53
54     /** Join state : the user cannot be connected to the channel. */
55     public static final int FAILED = -1;
56     /** Join state : the channel is full. */
57     public static final int CHANNEL_FULL = 0;
58     /** Join state : the user is connected to the channel. */
59     public static final int CONNECTED = 1;
60     /** Join state : the user is already connected to the channel. */
61     public static final int ALREADY_CONNECTED = 2;
62     /** Join state : the user is logged out of the channel. */
63     public static final int DISCONNECTED = 3;
64
65     /** Field : open */
66     public static final String JavaDoc F_OPEN = "open";
67     /** Field : maxusers */
68     public static final String JavaDoc F_MAXUSERS = "maxusers";
69     /** Field : state */
70     public static final String JavaDoc F_STATE = "state";
71     /** Field : session */
72     public static final String JavaDoc F_SESSION = "session";
73
74     /** Virtual Field : readlogin */
75     public static final String JavaDoc F_READLOGIN = "readlogin";
76     /** Virtual Field : writelogin */
77     public static final String JavaDoc F_WRITELOGIN = "writelogin";
78     /** Virtual Field or Function : hasmoods or hasmoods() */
79     public static final String JavaDoc F_HASMOODS = "hasmoods";
80
81     /** state : read login bit */
82     public static final int STATE_READ_LOGIN = 1;
83     /** state : write login bit */
84     public static final int STATE_WRITE_LOGIN = 2;
85
86     // string constants for Gui representation
87
// returned by isOpen().
88
private static final String JavaDoc STR_OPEN = "open";
89     private static final String JavaDoc STR_CLOSED = "closed";
90     private static final String JavaDoc STR_READ_ONLY = "readonly";
91
92     // logger
93
private static Logger log = Logging.getLoggerInstance(Channel.class.getName());
94
95     // Temporary node manager
96
private TemporaryNodeManager tmpNodeManager = null;
97
98     // Message builder
99
private Message messageBuilder;
100     // object type of the community builder
101
private Community communityBuilder;
102     // default expiration time for relation breaker
103
private int expireTime = 5 * 60 * 1000;
104     // NodeBreaker for maintaining temporary relations with users
105
private NodeBreaker chatboxConnections = null;
106     // default chat owner
107
private String JavaDoc tOwner = "system";
108     // indicates whether this builder has been activated for the community application
109
private boolean active = false;
110
111     /**
112      * This Hashtable contains all open channels with their highest sequence
113      * number for which the highseq is keeped track of in memory.
114      */

115     private Hashtable openChannels = new Hashtable();
116
117     // Holds the recorders associated with their channels.
118
private Hashtable recorders = new Hashtable();
119     // The base file path for log files.
120
private String JavaDoc baseRecordPath = null;
121     // The default filename for log files.
122
private String JavaDoc defaultrecordfile = "chat.log";
123     // The default suer type used in searches
124
private String JavaDoc defaultUserType = null;
125
126     /**
127      * Constructor
128      */

129     public Channel() {
130     }
131
132     /**
133      * Initializes the Channel builder.
134      * <br />
135      * Creates references to other required builders and relation roles.
136      * <br />
137      * Also opens all channels whose <code>open</code> field is set to {@link #OPEN} or
138      * {@link #WANT_OPEN}. This step can be skipped, to speed up startup of the server,
139      * by specifying the property <code>open-channels-on-startup</code> in the
140      * Channel builder configuration, and setting it to <code>false</code>.
141      */

142     public boolean init() {
143         boolean result = super.init();
144         // for recorders: find out basic logpath
145
baseRecordPath=getInitParameter("baserecordpath");
146         if ((baseRecordPath!=null) && (baseRecordPath.length()!=0)) {
147             if (baseRecordPath.charAt(baseRecordPath.length()-1)!=File.separatorChar) {
148                 baseRecordPath+=File.separator;
149             }
150         }
151         defaultUserType=getInitParameter("defaultusertype");
152         if ((defaultUserType==null) || (defaultUserType.length()==0)){
153             defaultUserType="chatter";
154         }
155
156         activate();
157         return result;
158     }
159
160     /**
161      * Activates the channel builder for the community application by associating it with other
162      * community builders and opening all communities.
163      * @return true if activation worked
164      */

165     public boolean activate() {
166         if (!active) {
167             // get message builder
168
messageBuilder = (Message) mmb.getMMObject("message");
169             // get community object type
170
communityBuilder = (Community) mmb.getMMObject("community");
171             if (messageBuilder != null && communityBuilder != null) {
172                 // obtain temporary node manager
173
tmpNodeManager = new TemporaryNodeManager(mmb);
174                 // create relation breaker for maintaining temporary relations
175
chatboxConnections = new NodeBreaker(2 * expireTime, tmpNodeManager);
176                 active = true;
177                 try {
178                     // open-channels-on-startup property:
179
String JavaDoc strOpenChannelsOnStartup = getInitParameter("open-channels-on-startup");
180                     boolean openChannelsOnStartup = !"false".equals(strOpenChannelsOnStartup);
181                     // Open all channels, unless the configuration specifies not to
182
if (openChannelsOnStartup) {
183                         openChannels();
184                     }
185                 } catch (Exception JavaDoc e) {
186                     // opening all channels may throw an error if the community is not properly installed
187
active = false;
188                 }
189             }
190         }
191         return active;
192     }
193
194     /**
195      * Lookup all channels in the cloud of who the field open is set to
196      * (want)open and try to open all these channels.
197      */

198
199     private void openChannels() {
200         if (communityBuilder != null) {
201             communityBuilder.openAllCommunities();
202         }
203 /*
204         log.debug("Try to open all channels who are set open in the database");
205         String where="WHERE "+mmb.getDatabase().getAllowedField(F_OPEN)+"=" + OPEN +
206                      " OR "+mmb.getDatabase().getAllowedField(F_OPEN)+"=" + WANT_OPEN;
207         Enumeration openChannels = search(where);
208         while (openChannels.hasMoreElements()) {
209             open((MMObjectNode)openChannels.nextElement());
210         }
211 */

212     }
213
214     /**
215      * Opens the channel.
216      *
217      * @param channel The channel to open.
218      * @return <code>true</code> if opening the channel was successfull.
219      */

220     public boolean open(MMObjectNode channel) {
221         MMObjectNode community = communityParent(channel);
222         if (community==null) {
223             log.error("open(): Can't open channel " + channel.getNumber()+" : no relation with a community");
224             return false;
225         }
226         return open(channel,community);
227     }
228
229     /**
230      * Opens the channel.
231      *
232      * @param channel The channel to open.
233      * @param community The community with this channel's settings
234      * @return <code>true</code> if opening the channel was successfull.
235      */

236     public boolean open(MMObjectNode channel, MMObjectNode community) {
237         // Try to open the channel, when the channel is part of a chatbox put
238
// the channel with his highest sequence in the openChannels HashTable.
239
Integer JavaDoc channelnr=new Integer JavaDoc(channel.getNumber());
240         if (log.isDebugEnabled())
241             log.debug("open(): Opening channel "+channelnr+" ("+channel.getValue("name")+")");
242
243         channel.setValue(F_OPEN, OPEN);
244         int highestSequence = channel.getIntValue("highseq");
245         boolean isChatBox = community.getStringValue("kind").equalsIgnoreCase(Community.STR_CHATBOX);
246         if (isChatBox) {
247             // this way we can check if the channel gets closed properly
248
channel.setValue("highseq", -1);
249         }
250         if (channel.commit()) {
251             if (isChatBox) {
252                 if (openChannels.get(channelnr) == null) {
253                     openChannels.put(channelnr,new Integer JavaDoc(highestSequence));
254                  }
255             }
256             log.debug("open(): channel "+channelnr+" opened");
257             return true;
258         }
259         log.error("open(): Can't open channel "+channelnr);
260         return false;
261     }
262
263     /**
264      * Closes the channel.
265      *
266      * @param channel The channel to close.
267      * @return <code>true</code> if closing the channel was successfull.
268      */

269     public boolean close(MMObjectNode channel) {
270         Integer JavaDoc channelnr=new Integer JavaDoc(channel.getNumber());
271         if (channel.getIntValue(F_OPEN) != CLOSED) {
272             channel.setValue(F_OPEN, CLOSED);
273             Integer JavaDoc highseq = (Integer JavaDoc)openChannels.get(channelnr);
274             if (highseq != null) channel.setValue("highseq", highseq);
275             if (channel.commit()) {
276                 log.debug("close(): channel "+channelnr+"("+channel.getValue("name")+") closed.");
277                 openChannels.remove(channelnr);
278                 return true;
279             }
280             log.error("close(): Can't close channel "+channelnr);
281             return false;
282         }
283         return true;
284     }
285
286     /**
287      * Makes a channel read only.
288      * @param channel The channel to affect.
289      * @return <code>true</code> if changing the channel open status was successfull.
290      */

291     public boolean readonly(MMObjectNode channel) {
292         Integer JavaDoc channelnr=new Integer JavaDoc(channel.getNumber());
293         if (channel.getIntValue(F_OPEN) != READ_ONLY) {
294             channel.setValue(F_OPEN, READ_ONLY);
295             if (channel.commit()) {
296                 log.debug("close(): channel "+channelnr+"("+channel.getValue("name")+") made read only.");
297                 return true;
298             }
299             log.error("close(): Can't make channel "+channelnr+" read only");
300             return false;
301         }
302         return true;
303     }
304
305     /**
306      * Returns if a channel is open, closed or readonly.
307      *
308      * @param channel The channel of which the open state is asked.
309      * @return the state of the channel, described as a <code>String</code>
310      */

311     public String JavaDoc isOpen(MMObjectNode channel) {
312         switch (channel.getIntValue(F_OPEN)) {
313             case OPEN: return STR_OPEN;
314             case READ_ONLY: return STR_READ_ONLY;
315             case CLOSED: return STR_CLOSED;
316             default:
317                 log.warn("isOpen: the following channel has an invalid open value: " +
318                           channel + " - assuming the channel is closed.");
319                 return STR_CLOSED;
320         }
321     }
322
323     /**
324      * @param channel The channel that has to give out a new sequence number.
325      * @return A new sequence number for a message that's want to get postend in this channel.
326      */

327     public int getNewSequence(MMObjectNode channel) {
328         int newHighseq;
329         Integer JavaDoc channelnr=new Integer JavaDoc(channel.getNumber());
330         Integer JavaDoc highseqObj = (Integer JavaDoc)openChannels.get(channelnr);
331         if (highseqObj != null) {
332             // The highest sequence is kept track of in the openChannels table.
333
newHighseq = highseqObj.intValue() + 1;
334             openChannels.put(channelnr, new Integer JavaDoc(newHighseq));
335             return newHighseq;
336         }
337         // The highest sequence is kept track of in the node's highseq field.
338
newHighseq = channel.getIntValue("highseq") + 1;
339         channel.setValue("highseq", newHighseq);
340         if (channel.commit()) {
341             return newHighseq;
342         }
343         log.error("getnewSequence(): couldn't return a new sequence number, because couldn't commit channel node.");
344         return -1;
345     }
346
347
348     /**
349      * Removes all messages posted in the channel.
350      * XXX: does not work for chats.
351      *
352      * @param channel The channel whose messages have to be removed.
353      */

354     public void removeAllMessages(MMObjectNode channel) {
355         /* This function will call the removeNode of the message builder with
356          * the recursive parameter set to true for all messages in the channel.
357          */

358         if (messageBuilder != null) {
359             for (Enumeration messages = mmb.getInsRel().getRelated(channel.getNumber(), messageBuilder.getNumber());
360                  messages.hasMoreElements();) {
361                 messageBuilder.removeNode((MMObjectNode)messages.nextElement(), true);
362             }
363         }
364     }
365
366     /**
367      * Returns the recorder for this channel.
368      * The recorder is currently only used in chats.
369      * @param channel the channel to record
370      * @return a <code>Writer</code> with which to record data, or <code>null</code> if
371      * the recorder doesn't exist.
372      */

373     public Writer getRecorder(int channel) {
374         return (Writer)recorders.get(new Integer JavaDoc(channel));
375     }
376
377     /**
378      * Start the recording session of a channel.
379      * @param channel the channel to record
380      * @param recorder a Writer to record to.
381      * @return the Writer that will record the messages
382      */

383     public Writer startRecorder(int channel, Writer recorder) {
384         Writer writer=getRecorder(channel);
385         if (writer!=null) {
386             return writer;
387         }
388         recorders.put(new Integer JavaDoc(channel), recorder);
389         Date now= new Date();
390         try {
391             recorder.write("Start recorder for channel "+channel+" at "+now+"\n");
392             recorder.flush();
393         } catch (IOException e) {
394             log.error(""+e);
395         }
396         return recorder;
397     }
398
399     /**
400      * Start the recording session of a channel, using a file to stream the output to.
401      * @param channel the channel to record
402      * @param filepath the file to record to.
403      * @return the Writer that will record the messages
404      */

405     public Writer startRecorder(int channel, String JavaDoc filepath) {
406         try {
407             return startRecorder(channel,new FileWriter(filepath,true));
408         } catch(IOException e) {
409             log.error(""+e);
410             return null;
411         }
412     }
413
414     /**
415      * Stop the recording session of a channel.
416      * @param channel the channel being recorded
417      */

418     public void stopRecorder(int channel) {
419         Writer recorder=(Writer)recorders.get(new Integer JavaDoc(channel));
420         if (recorder!=null) {
421             try {
422                 recorder.flush();
423                 recorder.close();
424             } catch(IOException e) {
425                 log.error(""+e);
426             }
427             recorders.remove(new Integer JavaDoc(channel));
428         }
429     }
430
431     /**
432      * Returns a key for the combination of nodes (a channel and a user).
433      * The key need not be unique.
434      * @param channel the channel object
435      * @param suer the user object
436      * @return a key for sue with teh temporary node manager
437      */

438     private String JavaDoc getTemporaryNodeKey(MMObjectNode channel, MMObjectNode user) {
439         return "C"+channel.getNumber()+"U"+user.getNumber();
440     }
441
442     /**
443      * Connects a user to a channel by making a temporary relation between them.
444      * If the user is already connected to another channel of the same
445      * community the old connection is closed.
446      *
447      * @param user the node representing the user that tries to join the channel.
448      * @param channel the channel to join
449      * @return <code>CHANNEL_FULL</code> if there's no room in the channel,
450      * <code>CONNECTED</code> if the connection is made, and <code>FAILED</code>
451      * if an error occurred. If the user is already connected to the channel
452      * <code>ALREADY_CONNECTED</code> is returned.
453      */

454     public synchronized int join(MMObjectNode user, MMObjectNode channel) {
455         // Test if the user is not already connected to this channel
456
if (messageBuilder == null) {
457             log.error("No message builder : join failed");
458             return FAILED;
459         }
460         String JavaDoc key=getTemporaryNodeKey(channel,user);
461
462         if (getTmpNode(key)!=null) {
463                 return ALREADY_CONNECTED;
464         }
465
466         // Test if there is room in the channel. When the channel is full return "full" and abort.
467
int maxusers = channel.getIntValue("maxusers");
468         if (maxusers != -1) {
469             int usersCount = messageBuilder.getTemporaryRelated(channel, "users").size();
470             if (usersCount >= maxusers)
471                 return CHANNEL_FULL;
472         }
473
474         /* Make the relation between the user and the joined channel.
475          * Also give the relation ID to the NodeBreaker so the relation is
476          * automatically broken after a specified period of inactivety.
477          */

478         try {
479             String JavaDoc tmp = tmpNodeManager.createTmpRelationNode("related", tOwner,key, "realchannel", "realusers");
480             MMObjectNode node = tmpNodeManager.getNode(tOwner, tmp);
481             tmpNodeManager.setObjectField(tOwner, tmp, "snumber", channel.getValue("number"));
482             tmpNodeManager.setObjectField(tOwner, tmp, "dnumber", user.getValue("number"));
483             // XXX: the way owner/key are combined to a key and/or
484
// stored in a node could be application-specific.
485
// Should be fixed by adding some methods to the
486
// TemporaryNodeManagerInterface
487
chatboxConnections.add(tOwner+"_"+key, (new Long JavaDoc(System.currentTimeMillis() + expireTime)).longValue());
488         }catch(Exception JavaDoc e) {
489             log.error("join(): Could not create temporary relations between between channel and user.\n" + e);
490             return FAILED;
491         }
492
493         // XXX:code below should be optional ?
494

495         /* If there is another channel to which the user is connected
496          * in the same community logout of the eldest connection,
497          * so the user is only in one channel of a community at the same time.
498          */

499         MMObjectNode relatedChannel;
500         MMObjectNode community = communityParent(channel);
501         MMObjectNode relatedCommunity;
502
503         Enumeration relatedChannels = messageBuilder.getTemporaryRelated(user, "channel").elements();
504         while (relatedChannels.hasMoreElements()) {
505             relatedChannel = (MMObjectNode)relatedChannels.nextElement();
506             relatedCommunity = communityParent(relatedChannel);
507             if (relatedCommunity.getNumber() == community.getNumber())
508                 if (relatedChannel.getNumber() != channel.getNumber())
509                     logout(user, relatedChannel);
510         }
511         return CONNECTED;
512     }
513
514     /**
515      * Disconnects a user from a channel.
516      * Removes the temporary relations between the user and the channel.
517      * @param user The user to disconnect.
518      * @param channel The channel to disconnect from.
519      * @return <code>DISCONNECTED</code> if the user was successfully disconnected,
520      * <code>FAILED</code> if an error occurred.
521      * @deprecated use {@link #leave} instead
522      */

523     public synchronized int logout(MMObjectNode user, MMObjectNode channel) {
524         return leave(user,channel);
525     }
526
527     /**
528      * Disconnects a user from a channel.
529      * Removes the temporary relations between the user and the channel.
530      * @param user The user to disconnect.
531      * @param channel The channel to disconnect from.
532      * @return <code>DISCONNECTED</code> if the user was successfully disconnected,
533      * <code>FAILED</code> if an error occurred.
534      */

535     public synchronized int leave(MMObjectNode user, MMObjectNode channel) {
536         chatboxConnections.remove(tOwner+"_"+getTemporaryNodeKey(channel,user));
537         return DISCONNECTED;
538     }
539
540     /**
541      * Registers a user as still being active.
542      * Tells the associated NodeBreaker to extend the relation between the
543      * user and the channel for another period of time.
544      * @param user the node representing the user
545      * @param channel the channel to which the user is connected
546      */

547     public synchronized void userStillActive(MMObjectNode user, MMObjectNode channel) {
548         chatboxConnections.update(tOwner+"_"+getTemporaryNodeKey(channel,user),
549                                   System.currentTimeMillis() + expireTime);
550     }
551
552     /**
553      * Retrieve a list of users connected to a channel.
554      * The params parameter contains the parameters for the list.
555      * <ul>
556      * <li>CHANNEL should containt the number or alias of the channel node</li>
557      * <li>TYPE may conmtain the user's object type. The default is the
558      * value specified in the builder properties</li>
559      * <li>FIELDS should contain the names of the fields whose values to return</li>
560      * <li>SORTFIELDS may contain the names of the fields to sort on</li>
561      * <li>SORTDIRS may contain the sort-order for the fields in SORTFIELDS</li>
562      * </ul>
563      * @param params contains the parameters for this list command.
564      * @return a vector with the (string) values of the requested fields, per user.
565      */

566     public Vector getListUsers(StringTagger params) {
567         Vector relatedUsers = getNodeListUsers(params);
568         // Get the fieldnames out of the FIELDS attribute.
569
Vector fields = params.Values("FIELDS");
570         // Put in params the number of fields that will get returned.
571
params.setValue("ITEMS","" + fields.size());
572
573         Vector result=new Vector();
574         MMObjectNode relatedUser;
575         String JavaDoc field;
576         for (Iterator i=relatedUsers.iterator(); i.hasNext();) {
577             relatedUser = (MMObjectNode)i.next();
578             for (Iterator j=fields.iterator(); j.hasNext(); ) {
579                 field=(String JavaDoc)j.next();
580                 result.add(relatedUser.getStringValue(field));
581             }
582         }
583         return result;
584     }
585
586     /**
587      * Retrieve a list of users connected to a channel.
588      * The params parameter contains the parameters for the list.
589      * <ul>
590      * <li>CHANNEL should containt the number or alias of the channel node</li>
591      * <li>TYPE may conmtain the user's object type. The default is the
592      * value specified in the builder properties</li>
593      * <li>FIELDS should contain the names of the fields whose values to return</li>
594      * <li>SORTFIELDS may contain the names of the fields to sort on</li>
595      * <li>SORTDIRS may contain the sort-order for the fields in SORTFIELDS</li>
596      * </ul>
597      * @param params contains the parameters for this list command.
598      * @return a vector with the user nodes.
599      */

600     public Vector getNodeListUsers(Map params) {
601         Vector result = new Vector();
602         String JavaDoc id = (String JavaDoc)params.get("CHANNEL");
603         MMObjectNode node = getNode(id);
604         if ((node == null) || !(node.getBuilder() instanceof Channel)) {
605             log.debug("getListUsers(): no or incorrect channel specified");
606             return result;
607         }
608         String JavaDoc usertype = (String JavaDoc)params.get("TYPE");
609         if (usertype==null) usertype= defaultUserType;
610
611         int offset=0;
612         String JavaDoc tmp = (String JavaDoc)params.get("FROMCOUNT");
613         if (tmp!=null) offset=Integer.parseInt(tmp);
614         int max=Integer.MAX_VALUE;
615         tmp = (String JavaDoc)params.get("MAXCOUNT");
616         if (tmp!=null) max=Integer.parseInt(tmp);
617
618         /* Create a Comparator, provided SORTFIELDS is specified.
619          * Sortdirections can be specified in SORTDIRS.
620          *
621          * Note: this is not very nice, but we prefer to use
622          * the more generic Map over StringTagger.
623          * However, the get() method of StringTagger always returns a string.
624          * We need to fix StringTagger so that get() always returns the
625          * _original_ value.
626          */

627         Vector sortFields;
628         Vector sortDirs;
629         if (params instanceof StringTagger) {
630             sortFields = ((StringTagger)params).Values("SORTFIELDS");
631             sortDirs = ((StringTagger)params).Values("SORTDIRS");
632         } else {
633             sortFields = (Vector)params.get("SORTFIELDS");
634             sortDirs = (Vector)params.get("SORTDIRS");
635         }
636
637         NodeComparator compareUsers=null;
638         if (sortFields!=null) {
639             if (sortDirs == null) {
640                 compareUsers = new NodeComparator(sortFields);
641             } else {
642                 compareUsers = new NodeComparator(sortFields, sortDirs);
643             }
644         }
645         return getListUsers(node, usertype, compareUsers,offset, max);
646     }
647
648     // javadoc overriden
649
public void setDefaults(MMObjectNode node) {
650         node.setValue(F_OPEN, OPEN);
651         node.setValue(F_STATE, 0);
652     }
653
654
655
656     /**
657      * Retrieve a sorted list of users connected to a channel.
658      * @param channel the channel
659      * @param usertype the type of the userobjects to retrieve
660      * @param compareUsers a Comparator used for sorting. <code>null</code>
661      * means the result is not no sorted
662      * @return a vector with the nodes of the users.
663      */

664     public Vector getListUsers(MMObjectNode channel, String JavaDoc usertype,
665                                Comparator compareUsers, int offset, int max) {
666         Vector relatedUsers = messageBuilder.getTemporaryRelated(channel,usertype,offset,max);
667         if (compareUsers!=null) {
668             Collections.sort(relatedUsers,compareUsers);
669         }
670         return relatedUsers;
671     }
672
673     /**
674      * Returns to which community a channel belongs.
675      *
676      * @param channel The channel node
677      * @return The community of the channel, or <code>null</code> if the
678      * channel is not associated with a community.
679      */

680     public MMObjectNode communityParent(MMObjectNode channel) {
681         // During call to Channel.init(), communityBuilder.getNumber() can still be 0
682
// So need to check it before using it
683
// When openChannels is removed from init this can be removed
684
if (communityBuilder != null) {
685             int oType = communityBuilder.getNumber();
686             if (oType == 0) oType = mmb.getTypeDef().getIntValue("community");
687             Enumeration relatedCommunity = mmb.getInsRel().getRelated(channel.getNumber(), oType);
688             if (relatedCommunity.hasMoreElements()) {
689                 return (MMObjectNode)relatedCommunity.nextElement();
690             }
691         }
692         return null;
693     }
694
695     /**
696      * Provides additional functionality when obtaining field values.
697      * This method is called whenever a Node of the builder's type fails at
698      * evaluating a getValue() request (generally when a fieldname is supplied
699      * that doesn't exist).
700      * <br />
701      * The following virtual fields are calculated:
702      * <br />
703      * readlogin - returns true when a user has to log on before he can read messages,
704      * false otherwise.
705      * <br />
706      * writelogin - returns true when a user has to log on before he can post a message,
707      * false otherwise.
708      * <br />
709      * hasmoods - returns "yes" when there are moods related to the channel,
710      * "no" otherwise.
711      * <br />
712      * It is also possible to specify hasmoods as a function.
713      * hasmoods() performs the same test, but returns a Boolean object (which
714      * evaluates to "true" and "false" when used referenced as a string)
715      * instead of "yes"/"no". This method is preferred
716      * for use with the MMCI.
717      *
718      * @param node the node whose fields are queried
719      * @param field the fieldname that is requested
720      * @return the result of the call, <code>null</code> if no valid functions or virtual fields could be determined.
721      */

722     public Object JavaDoc getValue(MMObjectNode node, String JavaDoc field) {
723         // PRE: The name of a field in the database or the name of a virtual field.
724
// POST: The value of the field.
725
if (field.equals(Message.F_REPLY_COUNT)) {
726             return new Integer JavaDoc(messageBuilder.getNrMsgAndHighSeq(node).messageCount);
727         }
728
729         if (field.equals(F_READLOGIN)) {
730             return Boolean.valueOf((node.getIntValue(F_STATE) & STATE_READ_LOGIN)>0);
731         }
732         if (field.equals(F_WRITELOGIN)) {
733             return Boolean.valueOf((node.getIntValue(F_STATE) & STATE_WRITE_LOGIN)>0);
734         }
735         if (field.equals(F_HASMOODS)) {
736             if (node.getRelationCount("mood")>0) return "yes"; else return "no";
737         }
738         return super.getValue(node, field);
739     }
740
741     /**
742      * Executes a function on the field of a node, and returns the result.
743      * This method is called by the builder's {@link #getValue} method.
744      * Functions implemented are readlogin(), writelogin() and hasmoods().
745      * @param node the node whose fields are queries
746      * @param field the fieldname that is requested
747      * @return the result of the 'function', or null if no valid functions could be determined.
748      */

749     protected Object JavaDoc executeFunction(MMObjectNode node,String JavaDoc function,String JavaDoc field) {
750         if (function.equals(F_HASMOODS)) {
751             return Boolean.valueOf(node.getRelationCount("mood")>0);
752         } else {
753             return super.executeFunction(node,function,field);
754         }
755     }
756
757     /**
758      * Handles the $MOD-MMBASE-BUILDER-channel- commands.
759      * The commands to use are: <br />
760      * <ul>
761      * <li>channelnumber-OPEN - opens the channel </li>
762      * <li>channelnumber-CLOSE - closes the channel </li>
763      * <li>channelnumber-ISOPEN - returns if a channel is "open", "closed" or "readonly"; </li>
764      * <li>channelnumber-DELALLMESSAGES - deletes all posted messages in the channel </li>
765      * <li>channelnumber-NEWSEQ - returns a new sequence number for in the channel </li>
766      * <li>channelnumber-JOIN-usernumber - connects the user to the channel. </li>
767      * <li>channelnumber-LEAVE-usernumber - disconnects a user from the channel. </li>
768      * <li>channelnumber-STILLACTIVE-usernumber - resets the time-out before the user is
769      * automatically disconnected from the channel. </li>
770      * </ul>
771      * @param sp the current PageInfo context
772      * @param tok the tokenized command
773      * @return the result of the command as a String
774      */

775     public String JavaDoc replace(PageInfo sp, StringTokenizer tok) {
776         // The first thing we expect is a channel number.
777
if (!tok.hasMoreElements()) {
778             log.error("replace(): channel number expected after $MOD-BUILDER-channel-.");
779             return "";
780         }
781
782         MMObjectNode channel = getNode(tok.nextToken());
783
784         if (tok.hasMoreElements()) {
785             String JavaDoc cmd = tok.nextToken();
786             // i.e.:
787
// channel-RECORD-FILE-channels.txt
788
// channel-RECORD-STOP
789
if (cmd.equals("RECORD")) {
790                 if (tok.hasMoreElements()) {
791                     cmd = tok.nextToken();
792                     if (cmd.equals("STOP")) {
793                         stopRecorder(channel.getNumber());
794                     } else if (cmd.equals("FILE")) {
795                         if (tok.hasMoreElements()) {
796                             cmd=tok.nextToken();
797                         } else {
798                             cmd=defaultrecordfile;
799                         }
800                         if (baseRecordPath==null) {
801                             log.error("Base recording filepath not specified - cannot record this channel.");
802                             return "";
803                         }
804                         String JavaDoc filename = baseRecordPath+cmd;
805                         startRecorder(channel.getNumber(),filename);
806                    }
807                     return "";
808                 }
809             }
810
811             if (cmd.equals("OPEN")) open(channel);
812             if (cmd.equals("READONLY")) readonly(channel);
813             if (cmd.equals("CLOSE")) close(channel);
814             if (cmd.equals("ISOPEN")) return isOpen(channel);
815             if (cmd.equals("DELALLMESSAGES")) removeAllMessages(channel);
816             if (cmd.equals("NEWSEQ")) return "" + getNewSequence(channel);
817             if (cmd.equals("JOIN"))
818                 join(getNode(tok.nextToken()), channel);
819             if (cmd.equals("LEAVE") || cmd.equals("QUIT"))
820                 leave(getNode(tok.nextToken()), channel);
821             if (cmd.equals("STILLACTIVE")) {
822                 userStillActive(getNode(tok.nextToken()), channel);
823                 log.debug("replace(): user still active") ;
824             }
825         }
826         return "";
827     }
828
829     /**
830      * Ask URL from related community and append the channel number to the URL.
831      */

832     public String JavaDoc getDefaultUrl(int src) {
833         if (communityBuilder != null) {
834             Enumeration e = mmb.getInsRel().getRelated(src, communityBuilder.getNumber());
835             if (!e.hasMoreElements()) {
836                 log.warn("GetDefaultURL Could not find related community for channel node " + src);
837                 return null;
838             }
839             MMObjectNode commNode = (MMObjectNode)e.nextElement();
840             String JavaDoc url = commNode.getBuilder().getDefaultUrl(commNode.getNumber());
841             if (url != null) url += "+" + src;
842             return url;
843         } else {
844             return null;
845         }
846     }
847
848     /**
849      * What should a GUI display for this node/field combo.
850      * Fields converted are ""session" (returns yes/no) and
851      * ""state", which returns a description of the login condition.
852      * @param node The node to display
853      * @param field the name field of the field to display
854      * @return the display of the node's field as a <code>String</code>, null if not specified
855      */

856     public String JavaDoc getGUIIndicator(String JavaDoc field, MMObjectNode node) {
857         // for recording sessions.
858
// If true, guess a 'session' object would be linked
859
// to a channel. Messages related to teh chanenl should then also be
860
// related to the session object.
861
// This doesn't work right now (sounds like overkill anyway)
862

863         if (field.equals(F_SESSION)) {
864             int value = node.getIntValue(field);
865             if (value == 1) return "yes"; else return "no";
866         }
867         if (field.equals(F_STATE)) { // alias login
868
int value = node.getIntValue(field);
869             if ((value & STATE_READ_LOGIN) >0)
870                 return "login before read";
871             else if ((value & STATE_WRITE_LOGIN) > 0)
872                 return "login before post";
873             else return "no login";
874         }
875         if (field.equals(F_OPEN)) {
876             return isOpen(node);
877         }
878         if (field.equals(F_MAXUSERS)) {
879             if (node.getIntValue(field)==-1) return "unlimited";
880         }
881         return null;
882     }
883
884 }
885
Popular Tags