Source code: com/fm/rss/rssChannelCategory.java
1 /****************************************************************************
2 * Copyright (c) 2003 Andrew Duka | aduka@users.sourceforge.net
3 * All right reserved.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 ****************************************************************************/
11 package com.fm.rss;
12
13 import java.util.*;
14
15
16 import org.w3c.dom.*;
17 import javax.xml.parsers.DocumentBuilder;
18 import javax.xml.parsers.DocumentBuilderFactory;
19 import javax.xml.parsers.ParserConfigurationException;
20
21 import com.fm.update.updateException;
22 import com.fm.update.Updateable;
23 import com.fm.rss.filter.RssItemFilter;
24
25 /**
26 * Container for RSS channel entities. Channel categories are intended for the
27 * user defined grouping of RSS channels (e.g. Developer's news,XML news)
28 *
29 * @author Andrew Duka (Andrew.Duka@oktet.ru)
30 */
31 public class rssChannelCategory extends rssAbstractEntry implements ItemContainer, Updateable
32 {
33
34 private HashMap subCategories; /* has table for sub categories */
35 private HashMap channels; /* hash table for channels */
36
37 private String version;
38
39
40 /**
41 * Default constructor
42 */
43 public rssChannelCategory ()
44 {
45 subCategories = new HashMap();
46 channels = new HashMap();
47
48 setID(0);
49 setTitle("");
50 setDescription("");
51 setDateCreated(new Date());
52 version = "1.0";
53 }
54
55 /**
56 * Constructs category using given title and description and ID
57 *
58 * @param t String with category title
59 * @param d String with category description
60 */
61 public rssChannelCategory(String t, String d)
62 {
63 subCategories = new HashMap();
64 channels = new HashMap();
65
66 setTitle(t);
67 setDescription(d);
68 setDateCreated(new Date());
69 }
70
71 /**
72 * Adds new category to the sub categories list
73 *
74 * @param cat rssChannelCategory object representing category to add
75 * @return true on success, false if failed or category already exists
76 */
77 public boolean addSubCategory(rssChannelCategory cat)
78 {
79 synchronized (subCategories)
80 {
81 if (subCategories.containsKey(new Integer(cat.getID())) == true)
82 return false;
83 else
84 subCategories.put(new Integer(cat.getID()), cat);
85 return true;
86 }
87 }
88
89 /**
90 * Removes sub category specified by ID from sub categories
91 *
92 * @param category category to remove
93 * @return true on success, false if failed or category doesn't exist
94 */
95 public synchronized boolean removeSubCategory(rssChannelCategory category)
96 {
97 synchronized (subCategories)
98 {
99 if (subCategories.containsKey(new Integer(category.getID())) == false)
100 return false;
101 else
102 subCategories.remove(new Integer(category.getID()));
103 return true;
104 }
105 }
106
107 /**
108 * Replaces sub category specified by ID with given one
109 *
110 * @param catID ID of category to replace
111 * @param cat new category
112 * @return true on success, false if failed or category doesn't exist
113 */
114 public boolean replaceSubCategory(int catID, rssChannelCategory cat)
115 {
116 synchronized (subCategories)
117 {
118 if (subCategories.containsKey(new Integer(catID)) == false)
119 return false;
120 else
121 subCategories.put(new Integer(catID), cat);
122 return true;
123 }
124 }
125
126 /**
127 * Adds new channel to the channel list
128 *
129 * @param ch rssChannel object representing category to add
130 * @return true on success, false if failed or category already exists
131 */
132 public boolean addChannel(rssChannel ch)
133 {
134 synchronized (channels)
135 {
136 if (channels.containsKey(new Integer(ch.getID())) == true)
137 {
138 return false; // already have channel with same ID
139 }
140 else
141 channels.put(new Integer(ch.getID()), ch);
142 return true;
143 }
144 }
145
146 /**
147 * Removes channel specified by ID from channels
148 *
149 * @param channel channel to remove
150 * @return true on success, false if failed or category doesn't exist
151 */
152 public synchronized boolean removeChannel(rssChannel channel)
153 {
154 synchronized (channels)
155 {
156 if (channels.containsKey(new Integer(channel.getID())) == false)
157 return false;
158 else
159 channels.remove(new Integer(channel.getID()));
160 return true;
161 }
162 }
163
164 /**
165 * Replaces channel specified by ID with given one
166 *
167 * @param channelID ID of channel to replace
168 * @param ch new channel
169 * @return true on success, false if failed or category doesn't exist
170 */
171 public boolean replaceChannel(int channelID, rssChannel ch)
172 {
173 synchronized (channels)
174 {
175 if (channels.containsKey(new Integer(channelID)) == false)
176 return false;
177 else
178 channels.put(new Integer(channelID), ch);
179 return true;
180 }
181 }
182
183 /**
184 * Returns sub categories as a list
185 *
186 * @return ArrayList containing sub categories
187 */
188 public Map getSubCategories()
189 {
190 return subCategories;
191 }
192
193 /**
194 * Returns channels as a list
195 *
196 * @return ArrayList containing category channels
197 */
198 public Map getChannels()
199 {
200 return this.channels;
201 }
202
203 /**
204 * Parse Element object and initialize category properties and channel list.
205 *
206 * <p>The <code>parse()</code> method for the channel categories works only
207 * with proprietary JNR format</p>
208 *
209 * @param el
210 * @throws com.fm.rss.rssParseException
211 */
212 public synchronized void parse(Element el) throws rssParseException
213 {
214 // checking for version
215 //System.out.println(el.toString());
216 String ver = (el.getAttributeNode("version")).getValue();
217 if ((ver == null) || this.version.compareTo(ver) != 0)
218 {
219 throw new rssParseException("Category parse error: Incompatible rss-channel-category version");
220 }
221
222 Attr id_attr = el.getAttributeNode("id");
223 String idstr;
224 if ((id_attr == null) || ((idstr = id_attr.getValue()) == null))
225 {
226 throw new rssParseException("Category parse error: category ID isn't specified");
227 }
228 // setting ID
229 int id;
230 try
231 {
232 id = Integer.parseInt(idstr);
233 this.setID(id);
234 }
235 catch (NumberFormatException nfe)
236 {
237 throw new rssParseException("Can't set ID: invalid format or can't convert string into int value");
238 }
239
240
241
242 Node curr_node;
243 Node temp;
244 NodeList nl = el.getChildNodes();
245 rssChannel new_ch;
246 rssChannelCategory new_cat;
247 int child_num = nl.getLength();
248 String node_name;
249
250 for (int i=0; i < child_num; i++)
251 {
252 curr_node = nl.item(i);
253
254 if (curr_node.getNodeType() == Node.ELEMENT_NODE)
255 {
256 node_name = curr_node.getNodeName();
257 if (node_name.equalsIgnoreCase("dc:title"))
258 {
259 if ((temp = curr_node.getFirstChild()) != null)
260 this.setTitle(temp.getNodeValue());
261 else
262 {
263 throw new rssParseException("JNR format parse error: dc:title element is empty");
264 }
265 }
266 else if (node_name.equalsIgnoreCase("dc:description"))
267 {
268 if ((temp = curr_node.getFirstChild()) != null)
269 this.setDescription(temp.getNodeValue());
270 else
271 {
272 this.setDescription("");
273 }
274 }
275 else if (node_name.equalsIgnoreCase("dateCreated"))
276 {
277 if ((temp = curr_node.getFirstChild()) != null)
278 setDateCreated(rssDateHandler.getDateInstanceFromRSS(temp.getNodeValue()));
279 else
280 {
281 throw new rssParseException("JNR format parse error: dc:dateCreated element is empty");
282 }
283 }
284 else if (node_name.equalsIgnoreCase("dcterms:hasPart"))
285 {
286 // getting only first child
287 Node f_ch = curr_node.getFirstChild();
288 String ch_t = f_ch.getNodeName();
289
290 // category case
291 if (ch_t.equalsIgnoreCase("rss-channel-category"))
292 {
293 new_cat = new rssChannelCategory();
294 try
295 {
296 new_cat.parse((Element)f_ch);
297 }
298 catch (rssParseException pe)
299 {
300 throw new rssParseException("Sub category parse exception: " + pe.toString());
301 }
302 this.addSubCategory(new_cat);
303 } // channel case
304 else if (ch_t.equalsIgnoreCase("rss-channel"))
305 {
306 new_ch = new rssChannel();
307 try
308 {
309 new_ch.parse((Element)f_ch);
310 }
311 catch (rssParseException pe)
312 {
313 throw new rssParseException("Channel parse exception: " + pe.toString());
314 }
315 this.addChannel(new_ch);
316 }
317 }//end of hasPart
318 }//end of ELEMENT_NODE
319 }//end of for
320 }
321
322
323 /**
324 * Return item container as element of XML document
325 *
326 * @return DOM Element object with representation of the item container
327 */
328 public Element toDomElement()
329 {
330 Element root_elem;
331 if (this.docAdapter == null)
332 {
333 try
334 {
335 docAdapter = documentAdapter.newInstance();
336 }
337 catch (ParserConfigurationException pce)
338 {
339 //catch exception here
340 return null;
341 }
342 }
343
344 root_elem = this.docAdapter.createElement("rss-channel-category");
345
346 try
347 {
348 // ID & Version
349 root_elem.setAttribute("id", Integer.toString(this.getID()));
350 root_elem.setAttribute("version", "1.0");
351
352 // Title, Description, dateCreated
353 Element c_t = this.docAdapter.createElement("title",
354 "dc",
355 documentAdapter.DC_NAMESPACE_URI);
356
357 c_t.appendChild(this.docAdapter.createTextNode(this.getTitle()));
358
359 Element c_d = this.docAdapter.createElement("description",
360 "dc",
361 documentAdapter.DC_NAMESPACE_URI);
362 c_d.appendChild(this.docAdapter.createTextNode(this.getDescription()));
363
364 Element c_dc = this.docAdapter.createElement("dateCreated",
365 "dcterms",
366 documentAdapter.DCTERMS_NAMESPACE_URI);
367 c_dc.appendChild(this.docAdapter.
368 createTextNode(rssDateHandler.
369 dateToString(getDateCreated(),
370 rssDateHandler.RSS_OUTPUT_PATTERN)));
371
372 root_elem.appendChild(c_t);
373 root_elem.appendChild(c_d);
374 root_elem.appendChild(c_dc);
375
376 // channels & categories
377 Element sub_entry;
378 Element has_part;
379
380 // dumping sub categories
381 synchronized (subCategories)
382 {
383 for (Iterator it = subCategories.keySet().iterator(); it.hasNext();)
384 {
385 sub_entry = (((rssAbstractEntry)(subCategories.get(it.next()))).toDomElement());
386 if (sub_entry != null)
387 {
388 has_part = this.docAdapter.createElement("hasPart",
389 "dcterms",
390 documentAdapter.DCTERMS_NAMESPACE_URI);
391 has_part.appendChild(sub_entry);
392 root_elem.appendChild(has_part);
393 }
394 else
395 {
396 //possibly report error here
397 }
398 }
399 }
400 // same for channels
401 synchronized (channels)
402 {
403 for (Iterator it = channels.keySet().iterator(); it.hasNext();)
404 {
405 sub_entry = (((rssAbstractEntry)(channels.get(it.next()))).toDomElement());
406 if (sub_entry != null)
407 {
408 has_part = this.docAdapter.createElement("hasPart",
409 "dcterms",
410 documentAdapter.DC_NAMESPACE_URI);
411 has_part.appendChild(sub_entry);
412 root_elem.appendChild(has_part);
413 }
414 else
415 {
416 //possibly report error here
417 }
418 }
419 }
420 }
421 catch (DOMException e)
422 {
423 return null;
424 }
425
426 return root_elem;
427 }
428
429 /**
430 * Update category channels. This method will subsequently update
431 * all channels and sub categories associated with the category.
432 *
433 * @throws com.fm.update.updateException if error occured during update
434 */
435 public void update() throws updateException
436 {
437 // updating sub categories
438 synchronized (subCategories)
439 {
440 for (Iterator it = subCategories.values().iterator(); it.hasNext();)
441 {
442 try
443 {
444 ((rssChannelCategory)it.next()).update();
445 }
446 catch (updateException upe)
447 {
448 throw upe;
449 }
450 }
451 }
452 // same for channels
453 synchronized (channels)
454 {
455 for (Iterator it = channels.values().iterator(); it.hasNext();)
456 {
457 try
458 {
459 ((rssChannel)it.next()).update();
460 }
461 catch (updateException upe)
462 {
463 throw upe;
464 }
465
466 }
467 }
468 }
469
470 /**
471 * Returns list of the container items
472 *
473 * @return list of the channel items
474 */
475 public Map getItems()
476 {
477 HashMap result = new HashMap();
478 Map temp_mp;
479
480 // updating sub categories
481 synchronized (subCategories)
482 {
483
484 for (Iterator it = subCategories.keySet().iterator(); it.hasNext();)
485 {
486 // do update in the sub category, if new items are retreived -
487 // put them into result list
488 if ((temp_mp = ((ItemContainer)subCategories.get(it.next())).getItems()) !=
489 null)
490 {
491 result.putAll(temp_mp);
492 }
493
494 }
495 }
496 // same for channels
497 synchronized (channels)
498 {
499 for (Iterator it = channels.keySet().iterator(); it.hasNext();)
500 {
501 if ((temp_mp = ((ItemContainer)channels.get(it.next())).getItems()) !=
502 null)
503 {
504 result.putAll(temp_mp);
505 }
506 }
507 }
508 return (result.size() > 0) ? result : null;
509 }
510
511 /**
512 * Filter items using specified filter
513 *
514 * @param f
515 */
516 public void filterItems(RssItemFilter f)
517 {
518 if (f == null)
519 return;
520
521 synchronized (subCategories) {
522 for (Iterator i = subCategories.values().iterator(); i.hasNext();)
523 ((ItemContainer) i.next()).filterItems(f);
524 }
525
526 synchronized (channels) {
527 for (Iterator i = channels.values().iterator(); i.hasNext();)
528 ((ItemContainer) i.next()).filterItems(f);
529 }
530
531 }
532
533
534 }