KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > applications > media > builders > MediaFragments


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 package org.mmbase.applications.media.builders;
11
12 import org.mmbase.applications.media.filters.MainFilter;
13 import org.mmbase.applications.media.urlcomposers.URLComposer;
14 import org.mmbase.applications.media.cache.URLCache;
15
16 import java.util.*;
17
18 import org.mmbase.module.core.*;
19 import org.mmbase.module.corebuilders.InsRel;
20 import org.mmbase.util.*;
21 import org.mmbase.util.functions.*;
22 import org.mmbase.util.logging.Logger;
23 import org.mmbase.util.logging.Logging;
24
25
26 /**
27  * The MediaFragment builder describes a piece of media. This can be audio, or video.
28  * A media fragment contains a title, description, and more information about the media fragment.
29  * A media fragment will have relations with mediasources which are the actual
30  * files in different formats (mp3, real, etc.)
31  *
32  * The classification, and replace methods are added for backwards compatibility.
33  *
34  * @author Rob Vermeulen (VPRO)
35  * @author Michiel Meeuwissen
36  * @version $Id: MediaFragments.java,v 1.44 2005/12/05 18:44:41 johannes Exp $
37  * @since MMBase-1.7
38  */

39
40 public class MediaFragments extends MMObjectBuilder {
41
42     private static Logger log = Logging.getLoggerInstance(MediaFragments.class);
43
44     // let the compiler check for typo's:
45
public static final String JavaDoc FUNCTION_URLS = "urls";
46     public static final String JavaDoc FUNCTION_FILTEREDURLS = "filteredurls";
47     public static final String JavaDoc FUNCTION_URL = "url";
48     public static final String JavaDoc FUNCTION_NUDEURL = "nudeurl";
49     public static final String JavaDoc FUNCTION_PARENTS = "parents";
50     public static final String JavaDoc FUNCTION_ROOT = "root";
51     public static final String JavaDoc FUNCTION_SUBFRAGMENT = "issubfragment";
52     public static final String JavaDoc FUNCTION_SUBFRAGMENTS = "subfragments";
53     public static final String JavaDoc FUNCTION_AVAILABLE = "available";
54     public static final String JavaDoc FUNCTION_FORMAT = "format";
55     public static final String JavaDoc FUNCTION_DURATION = "duration";
56
57     // parameter definitions (making use of reflection utitility for functions)
58
public final static Parameter[] URLS_PARAMETERS = { new Parameter("format", List.class), new Parameter("bitrate", String JavaDoc.class), Parameter.REQUEST };
59     public final static Parameter[] FILTEREDURLS_PARAMETERS = URLS_PARAMETERS;
60     public final static Parameter[] URL_PARAMETERS = URLS_PARAMETERS;
61     public final static Parameter[] NUDEURL_PARAMETERS = URLS_PARAMETERS;
62     public final static Parameter[] PARENTS_PARAMETERS = {};
63     public final static Parameter[] ROOT_PARAMETERS = {};
64     public final static Parameter[] ISSUBFRAGMENT_PARAMETERS = {};
65     public final static Parameter[] SUBFRAGMENT_SPARAMETERS = {};
66     public final static Parameter[] AVAILABLE_PARAMETERS = URLS_PARAMETERS;
67     public final static Parameter[] FORMAT_PARAMETERS = URLS_PARAMETERS;
68     public final static Parameter[] DURATION_PARAMETERS = {};
69
70
71     // This filter is able to find the best mediasource by a mediafragment.
72
// private static MainFilter mediaSourceFilter = null;
73

74     // Is the mediafragment builder already initialised?
75
// this class is used for several builders (mediafragments and descendants)
76
private static boolean initDone = false;
77
78     private URLCache cache = URLCache.getCache();
79
80     public boolean init() {
81         if(initDone) {
82         return super.init();
83     }
84         log.service("Init of Media Fragments builder");
85         initDone = true; // because of inheritance we do init-protections
86

87         boolean result = super.init();
88         // deprecated:
89
retrieveClassificationInfo();
90
91         return result;
92     }
93
94     /**
95      * Would something like this be feasible to translate a List to a Map?
96      *
97      */

98     static protected Map translateURLArguments(List arguments, Map info) {
99         if (info == null) info = new HashMap();
100         if (arguments != null) {
101             if (arguments instanceof Parameters) {
102                 info.putAll(((Parameters) arguments).toMap());
103             } else {
104                 info.put("format", arguments);
105             }
106         }
107         return info;
108     }
109
110     /**
111      * {@inheritDoc}
112      */

113     protected Object JavaDoc executeFunction(MMObjectNode node, String JavaDoc function, List args) {
114         if (log.isDebugEnabled()) {
115             log.debug("executeFunction " + function + "(" + args + ") on " + node);
116         }
117         if (function.equals("info")) {
118             List empty = new Vector();
119             java.util.Map JavaDoc info = (java.util.Map JavaDoc) super.executeFunction(node, function, empty);
120             info.put(FUNCTION_URL, "(<format>) Returns the 'best' url for this fragment. Hashtable can be filled with speed/channel/ or other info to evalute the url.");
121             info.put(FUNCTION_URLS, "(info) A list of all possible URLs to this fragment (Really URLComposer.URLComposer's)");
122             info.put(FUNCTION_ROOT, "() Returns the 'parent' MMObjectNode's number of the parent or null");
123             info.put(FUNCTION_SUBFRAGMENT, "() Wether this fragment is a subfragment (returns a Boolean)");
124             info.put(FUNCTION_SUBFRAGMENTS, "() Returns a stack of parents (Stack of MMObjectNode)");
125             info.put(FUNCTION_AVAILABLE, "() Wether this fragment is 'available'. A fragment can be unaivable when there is a related publishtimes which defines it 'unpublished'");
126             // info.put("urlresult", "(<??>) ");
127
info.put("gui", "(state|channels|codec|format|..) Gui representation of this object.");
128
129             if (args == null || args.size() == 0) {
130                 return info;
131             } else {
132                 return info.get(args.get(0));
133             }
134         } else if (FUNCTION_URLS.equals(function)) {
135             return getURLs(node, translateURLArguments(args, null), null,null);
136         } else if (FUNCTION_FILTEREDURLS.equals(function)) {
137             return getFilteredURLs(node, translateURLArguments(args, null),null);
138         } else if (FUNCTION_SUBFRAGMENT.equals(function)) {
139             return Boolean.valueOf(isSubFragment(node));
140         } else if (FUNCTION_ROOT.equals(function)) {
141             MMObjectNode parent = getRootFragment(node);
142             return parent;
143         } else if (FUNCTION_PARENTS.equals(function)) {
144             return getParentFragments(node);
145         } else if (FUNCTION_ROOT.equals(function)) {
146             return "" + getRootFragment(node).getNumber();
147         } else if (FUNCTION_AVAILABLE.equals(function)) {
148             List pt = node.getRelatedNodes("publishtimes");
149             if (pt.size() == 0) {
150                 return Boolean.TRUE;
151             } else {
152                 MMObjectNode publishtime = (MMObjectNode) pt.get(0);
153                 int now = (int) (System.currentTimeMillis() / 1000);
154                 int begin = publishtime.getIntValue("begin");
155                 int end = publishtime.getIntValue("end");
156                 Boolean JavaDoc available = Boolean.TRUE;
157                 if (begin > 0 && now < begin) available = Boolean.FALSE;
158                 if (end > 0 && now > end) available = Boolean.FALSE;
159                 return available;
160             }
161         } else if (FUNCTION_URL.equals(function)) {
162             return getURL(node, translateURLArguments(args, null));
163         } else if (FUNCTION_NUDEURL.equals(function)) {
164             Map info = translateURLArguments(args, null);
165             info.put("nude", "true");
166             return getURL(getRootFragment(node), info);
167         } else if (FUNCTION_FORMAT.equals(function)) {
168             return getFormat(node, translateURLArguments(args, null));
169         } else if (FUNCTION_DURATION.equals(function)) {
170             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
171             org.mmbase.applications.media.urlcomposers.RealURLComposer.appendTime(calculateLength(node), buf);
172             return buf.toString();
173         }
174         log.debug("Function not matched in mediafragments");
175         return super.executeFunction(node, function, args);
176     }
177
178
179     /**
180      * Calculate the length of a mediafragment
181      * @param node the mediafragment
182      * @return length in milliseconds
183      */

184     protected long calculateLength(MMObjectNode node) {
185         long start = node.getLongValue("start");
186         long stop = node.getLongValue("stop");
187         long length = node.getLongValue("length");
188
189         if(stop != 0) {
190             return stop - start;
191         } else if (length != 0) {
192             return length - start;
193         }
194         log.debug("length cannot be evaluated, no stoptime and no length");
195         return 0;
196     }
197
198     /**
199      * Will show the title (clickable if possible)
200      * @param node the mediafragment node
201      * @return the title of the mediafragment
202      */

203     public String JavaDoc getGUIIndicator(MMObjectNode node) {
204         String JavaDoc url = node.getFunctionValue(FUNCTION_URL, null).toString();
205         String JavaDoc title = node.getStringValue("title");
206         if ("".equals(title)) title = "***";
207         if (! "".equals(url)) {
208             if (url.startsWith("/")) {
209                 url = MMBaseContext.getHtmlRootUrlPath() + url.substring(1);
210             }
211             return "<a HREF=\"" + url + "\" alt=\"\" >" + title + "</a>";
212         } else {
213             return "[" + title + "]";
214         }
215     }
216
217
218     public String JavaDoc getGUIIndicator(String JavaDoc field, MMObjectNode node) {
219         if (getField(field).getGUIType().equals("relativetime")) { // must be delegated to a field-type implementation
220
StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
221             org.mmbase.applications.media.urlcomposers.RealURLComposer.appendTime(node.getIntValue(field), buf);
222             return buf.toString();
223         }
224         return super.getGUIIndicator(field, node);
225     }
226
227
228     /**
229      * Returns a List of all possible (unfiltered) URLComposer's for this Fragment.
230      * A list of arguments can be supplied, which is currently unused (but should not be null).
231      * It could contain a Map with preferences, or other information about the client.
232      *
233      */

234     protected List getURLs(MMObjectNode fragment, Map info, List urls, Set cacheExpireObjects) {
235         if (urls == null) urls = new ArrayList();
236
237         Iterator i = getSources(fragment).iterator();
238         while (i.hasNext()) {
239             MMObjectNode source = (MMObjectNode) i.next();
240             MediaSources bul = (MediaSources) source.getBuilder(); // cast everytime, because it can be extended
241
bul.getURLs(source, fragment, info, urls, cacheExpireObjects);
242         }
243         return urls;
244     }
245
246     protected List getFilteredURLs(MMObjectNode fragment, Map info, Set cacheExpireObjects) {
247         log.debug("getfilteredurls");
248         List urls = getURLs(fragment, info, null,cacheExpireObjects);
249         return MainFilter.getInstance().filter(urls);
250     }
251
252
253     /**
254      * Retrieves the url of the mediasource that matches best.
255      * (e.g. pnm://www.mmbase.org/music/test.ra)
256      *
257      * @param fragment the media fragment
258      * @param info extra information (i.e. request, wanted bitrate, preferred format)
259      * @return the url of the audio file
260      */

261     protected String JavaDoc getURL(MMObjectNode fragment, Map info) {
262         log.debug("Getting url of a fragment.");
263         String JavaDoc key = URLCache.toKey(fragment, info);
264         if(cache.containsKey(key)) {
265             String JavaDoc url = (String JavaDoc) cache.get(key);
266             if (log.isDebugEnabled()) {
267                 log.debug("Cache hit, key = " + key);
268                 log.debug("Resolved url = " + url);
269             }
270             return url;
271         } else {
272             log.debug("No cache hit, key = " + key);
273         }
274
275         Set cacheExpireObjects = new HashSet();
276         List urls = getFilteredURLs(fragment, info, cacheExpireObjects);
277         String JavaDoc result = "";
278         if (urls.size() > 0) {
279             result = ((URLComposer) urls.get(0)).getURL();
280         }
281         if (log.isDebugEnabled()) {
282             log.debug("Add to cache, key = " + key);
283             log.debug("Resolved url = " + result);
284         }
285         // put result in cache
286
cache.put(key, result, cacheExpireObjects);
287         return result;
288     }
289
290     protected String JavaDoc getFormat(MMObjectNode fragment, Map info) {
291         log.debug("Getting format of a fragment.");
292         // XXX also cache this ?
293
// XXX can be done in the same cache if we extend the key...
294
List urls = getFilteredURLs(fragment, info, null);
295         if (urls.size() > 0) {
296             return ((URLComposer) urls.get(0)).getFormat().toString();
297         } else {
298             return ""; //no sources
299
}
300     }
301
302     /**
303      * If a mediafragment is coupled to another mediafragment instead of being directly
304      * coupled to mediasources, the mediafragment is a subfragment.
305      * @return true if the mediafragment is coupled to another fragment, false otherwise.
306      */

307     public boolean isSubFragment(MMObjectNode mediafragment) {
308         int mediacount = mediafragment.getRelationCount("mediasources");
309         return (mediacount == 0 && mediafragment.getRelationCount("mediafragments") > 0);
310     }
311
312     /**
313      * Adds a parent fragment to the Stack and returns true, or returns false.
314      */

315     protected boolean addParentFragment(Stack fragments) {
316         MMObjectNode fragment = (MMObjectNode) fragments.peek();
317         int role = mmb.getRelDef().getNumberByName("posrel");
318         InsRel insrel = mmb.getRelDef().getBuilder(role);
319         Enumeration e = insrel.getRelations(fragment.getNumber(), mmb.getBuilder("mediafragments").getObjectType(), role);
320         while (e.hasMoreElements()) {
321             MMObjectNode relation = (MMObjectNode) e.nextElement();
322             if (relation.getIntValue("dnumber") == fragment.getNumber()) { // yes, found a parent node
323
if (log.isDebugEnabled()) {
324                     log.debug("Yes, found parent of " + fragment.getNumber() + " " + relation.getIntValue("snumber"));
325                 }
326                 MMObjectNode parent = getNode(relation.getIntValue("snumber"));
327                 if (fragments.contains(parent)) {
328                     log.warn("Circular fragment nesting detected " + fragments + " breaking infinite loop");
329                     return false;
330                 }
331                 fragments.push(parent);
332                 return true;
333             }
334         }
335         return false;
336     }
337
338     /**
339      * Returns a Stack with all parent fragments. Starts stacking from
340      * this, so on top is the mediafragment with the sources, and on
341      * the bottom is the fragment itself.
342      */

343     public Stack getParentFragments(MMObjectNode fragment) {
344         Stack result = new Stack();
345         result.push(fragment);
346         if (log.isDebugEnabled()) {
347             log.debug("Finding parents of node " + fragment.getNumber());
348         }
349         while (addParentFragment(result));
350         return result;
351     }
352
353
354     /**
355      * Find the mediafragment of which the given mediafragment is a
356      * part. This fragment is not a subfragment itself, and should be
357      * linked to the actual sources.
358      *
359      * @param fragment sub media fragment
360      * @return The parent media fragment or null if it has not.
361      */

362     public MMObjectNode getRootFragment(MMObjectNode fragment) {
363         Stack s = getParentFragments(fragment);
364         return (MMObjectNode) s.peek();
365     }
366
367     /**
368      * Get all mediasources belonging to this mediafragment
369      * (scope should be protected)
370      * @param fragment the mediafragment
371      * @return All mediasources related to given mediafragment
372      */

373     public List getSources(MMObjectNode fragment) {
374         if (log.isDebugEnabled()) log.debug("Get mediasources mediafragment " + fragment.getNumber());
375         MMObjectNode root = getRootFragment(fragment);
376         List mediasources = root.getRelatedNodes("mediasources");
377         if (mediasources == null) {
378             log.warn("Could not get related nodes of type mediasources");
379         }
380         if (log.isDebugEnabled()) log.debug("Mediafragment contains "+mediasources.size()+" mediasources");
381
382         return mediasources;
383     }
384
385
386     /**
387      * Removes related media sources. This can be used by automatic recording VWMS's.
388      *
389      * @param fragment The MMObjectNode
390      */

391     public void removeSources(MMObjectNode fragment) {
392         List ms = getSources(fragment);
393         for (Iterator mediaSources = ms.iterator() ;mediaSources.hasNext();) {
394             MMObjectNode source = (MMObjectNode) mediaSources.next();
395             MMObjectBuilder parent = source.getBuilder();
396             parent.removeRelations(source);
397             parent.removeNode(source);
398         }
399     }
400
401     // --------------------------------------------------------------------------------
402
// These methods are added to be backwards compatible.
403

404     private Map classification = null;
405
406      /**
407       * For backwards compatibility reasons, the first version of the mediafragment builder
408       * will contain the classification field. This field will contain numbers that are
409       * resolved using the lookup builder. This construction, using classification in
410       * mediafragment, was used for speeding up listings.
411       * @deprecated
412       */

413      private void retrieveClassificationInfo() {
414
415          MMObjectBuilder lookup = mmb.getMMObject("lookup");
416          if(lookup == null) {
417              log.debug("Downwards compatible classification code not used.");
418              return;
419          }
420          log.debug("Using downwards compatible classification code.");
421          classification = new Hashtable();
422          MMObjectNode fn = getNode(mmb.getTypeDef().getIntValue("mediafragments"));
423          Vector nodes = fn.getRelatedNodes("lookup");
424          for (Enumeration e = nodes.elements();e.hasMoreElements();) {
425              MMObjectNode node = (MMObjectNode)e.nextElement();
426              String JavaDoc index = node.getStringValue("index");
427              String JavaDoc value = node.getStringValue("value");
428              log.debug("classification uses: " + index + " -> " + value);
429              classification.put(index,value);
430          }
431          return;
432      }
433
434     /**
435      * Replace all for frontend code
436      * Replace commands available are GETURL (gets mediafile url for an objectnumber),
437      * @param sp the PageInfo
438      * @param command the stringtokenizer reference with the replace command.
439      * @return the result value of the replace command or null.
440      */

441     public String JavaDoc replace(PageInfo sp,StringTokenizer command) {
442         if (command.hasMoreTokens()) {
443             String JavaDoc token=command.nextToken();
444             
445             log.debug("scan - "+token);
446             if (token.equals("GETURL")) {
447                 Integer JavaDoc number=null, userSpeed=null, userChannels=null;
448                 if (command.hasMoreTokens()) number=new Integer JavaDoc(command.nextToken());
449                 if (command.hasMoreTokens()) userSpeed=new Integer JavaDoc(command.nextToken());
450                 if (command.hasMoreTokens()) userChannels=new Integer JavaDoc(command.nextToken());
451                 if (number!=null) {
452             MMObjectNode media = getNode(number.intValue());
453             if(!media.getBuilder().isExtensionOf(mmb.getBuilder("mediafragments"))) {
454                 log.error("Number "+number+" is not a media/audio/video fragment "+media);
455                 return "Number "+number+" is not a media/audio/video fragment "+media;
456             }
457             Map info = new HashMap();
458             if(userSpeed!=null) {
459                 info.put("speed",""+userSpeed);
460             }
461             if(userChannels!=null) {
462                 info.put("channels",""+userChannels);
463             }
464                     return getURL(media, info);
465                 } else {
466             log.error("No mediafragment specified");
467                     return null;
468                 }
469             }
470             log.error("only command GETURL is supported");
471             return "only command GETURL is supported";
472         }
473         log.error("No commands defined.");
474         return "No commands defined.";
475     }
476
477     public Object JavaDoc getObjectValue(MMObjectNode node, String JavaDoc field) {
478         if (field.equals("lengthsec")) {
479             long val=node.getLongValue("length");
480             return ""+val/1000;
481         }
482         return super.getObjectValue(node,field);
483     }
484
485     public boolean setValue(MMObjectNode node,String JavaDoc fieldname) {
486         if (fieldname.equals("lengthsec")) {
487             long val=node.getLongValue("lengthsec");
488             log.info("store value in seconds: "+val);
489             node.setValue("length",new Long JavaDoc(val*1000));
490             node.storeValue("lengthsec",null);
491             return false;
492         }
493         return super.setValue(node,fieldname);
494     }
495
496     /**
497      * {@inheritDoc}
498      *
499      * Stack.contains is used, so make sure equal node are equal.
500      */

501
502     public boolean equals(MMObjectNode o1, MMObjectNode o2) {
503         int n1 = o1.getNumber();
504         int n2 = o2.getNumber();
505         if (n1 > 0 && n2 > 0) { // both 'real' nodes.
506
return n1 == n2;
507         } else {
508             String JavaDoc t1 = o1.getStringValue("_number");
509             String JavaDoc t2 = o2.getStringValue("_number");
510             if (t1 == null) {
511                 return n1 == n2 && t2 == null;
512             } else {
513                 return n1 == n2 && t1.equals(t2);
514             }
515         }
516
517     }
518
519 }
520
Popular Tags