View Javadoc

1   package org.musicontroller.dao;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.Calendar;
6   import java.util.Collection;
7   import java.util.Date;
8   import java.util.GregorianCalendar;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.LinkedList;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.TreeMap;
16  import java.util.Vector;
17  import java.util.Map.Entry;
18  import java.util.concurrent.CopyOnWriteArraySet;
19  
20  import org.apache.log4j.Logger;
21  import org.hibernate.HibernateException;
22  import org.hibernate.Query;
23  import org.hibernate.Session;
24  import org.hibernate.SessionFactory;
25  import org.musicontroller.SongChangeListener;
26  import org.musicontroller.core.AIBag;
27  import org.musicontroller.core.Artist;
28  import org.musicontroller.core.Band;
29  import org.musicontroller.core.Contract_PS;
30  import org.musicontroller.core.Event;
31  import org.musicontroller.core.Instrument;
32  import org.musicontroller.core.Keyword;
33  import org.musicontroller.core.Keywordbag;
34  import org.musicontroller.core.Link;
35  import org.musicontroller.core.LinkableAbs;
36  import org.musicontroller.core.Playlist;
37  import org.musicontroller.core.Song;
38  import org.musicontroller.repair.ConsistencyChecker;
39  import org.musicontroller.security.Authority;
40  import org.musicontroller.security.Encrypter;
41  import org.musicontroller.security.IUser;
42  import org.musicontroller.security.Role;
43  import org.musicontroller.security.User;
44  import org.springframework.orm.hibernate3.HibernateTemplate;
45  import org.springframework.orm.hibernate3.SessionFactoryUtils;
46  import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
47  import org.varienaja.util.DateTools;
48  import org.varienaja.util.DenseSet;
49  import org.varienaja.util.FileOperations;
50  
51  public class HibernateDao extends HibernateDaoSupport implements Dao {
52  	private static final Logger LOG = Logger.getLogger(HibernateDao.class);
53  	
54  	private Set<SongChangeListener> _songChangeListeners;
55  	
56  	public HibernateDao() {
57  		_songChangeListeners = new CopyOnWriteArraySet<SongChangeListener>();
58  	}
59  
60  	@SuppressWarnings("unchecked")
61  	public List<Long> getSongIds() {
62  		return getHibernateTemplate().find(
63  				"select s.id from Song s order by s.id");
64  	}
65  
66  	public Song getSongById(long id) {
67  		return (Song) getHibernateTemplate().get(Song.class,id);
68  	}
69  	
70  	@SuppressWarnings("unchecked")
71  	public List<Song> getSongsById(Collection<Long> ids) {
72  		String sql = "from Song s where s.id in (:ids)";
73  		List<Song> songlist = getHibernateTemplate().findByNamedParam(sql, 
74  				new String[]{"ids"}, 
75  				new Object[] {ids});
76  
77  		return songlist;
78  	}
79  	
80  	public Band getBandById(long id) {
81  		return (Band) getHibernateTemplate().get(Band.class,id);
82  	}
83  
84  	/*
85  	 * (non-Javadoc)
86  	 * @see org.musicontroller.dao.Dao#getBandByName(java.lang.String)
87  	 */
88  	@SuppressWarnings("unchecked")
89  	public Band getBandByName(String name) {
90  		String sql = "from Band b where lower(b.name)=:name order by b.inserted";
91  		List<Band> bandlist = getHibernateTemplate().findByNamedParam(sql,
92  					new String[] {"name"},
93  					new Object[] {name.toLowerCase()});
94  		return bandlist.size()==0 ? null : bandlist.get(0);
95  	}
96  
97  	public Keyword getKeywordById(long id) {
98  		return (Keyword) getHibernateTemplate().get(Keyword.class,id);
99  	}
100 	
101 	public Keywordbag getKeywordBagById(long id) {
102 		return (Keywordbag) getHibernateTemplate().get(Keywordbag.class,id);
103 	}
104 	
105 	public Artist getArtistById(long id) {
106 		return (Artist) getHibernateTemplate().get(Artist.class,id);
107 	}
108 
109 	public AIBag getAIBagById(long id) {
110 		return (AIBag) getHibernateTemplate().get(AIBag.class,id);
111 	}
112 	
113 	@SuppressWarnings("unchecked")
114 	public Artist searchArtist(String artistfirstname, String artistlastname) {
115 		if(artistlastname==null) {
116 			return null;
117 		}
118 		StringBuilder sb = new StringBuilder();
119 		sb.append("from Artist a where lower(a.lastname)=:lastname");
120 		List<Artist> artistlist = null;
121 		if(artistfirstname==null) {
122 			sb.append(" and a.firstname is null");
123 			artistlist = getHibernateTemplate().findByNamedParam( sb.toString(),
124 					new String[] { "lastname" },
125 					new Object[] { artistlastname.toLowerCase()});
126 		} else {
127 			sb.append(" and lower(a.firstname)=:firstname");
128 			artistlist = getHibernateTemplate().findByNamedParam( sb.toString(),
129 					new String[] { "lastname","firstname" },
130 					new Object[] { artistlastname.toLowerCase(),artistfirstname.toLowerCase()});
131 		}
132 		Artist result = null;
133 		if (artistlist.size() > 0) {
134 			result = artistlist.get(0);
135 		}
136 		return result;		
137 	}
138 	
139 	@SuppressWarnings("unchecked")
140 	public AIBag getArtistAppearances(long artistid) {
141 		String hql = "from AIBag bag " +
142 				"left join bag.relations rel" +
143 				" where rel.artist_id=:artistid"; 
144 		Map<String,Object> params = new HashMap<String,Object>();
145 		params.put("artistid", artistid);
146 		List<AIBag> bags = search(hql, params, 0);
147 		return bags.isEmpty()? null : bags.get(0);
148 	}
149 	
150 	public Instrument getInstrumentById(long id) {
151 		return (Instrument) getHibernateTemplate().get(Instrument.class,id);
152 	}
153 
154 	@SuppressWarnings("unchecked")
155 	public Song getSong(Band band, String songname) {
156 		Song result = null;
157 		List<Song> bandlist = getHibernateTemplate().findByNamedParam("from Song s where s.name=:name and s.band=:band",
158 				new String[]{"name","band"},new Object[]{LinkableAbs.normalizeName(songname),band});
159 		if(bandlist.size()>0) {
160 			result = bandlist.get(0);
161 		}
162 		return result;
163 	}
164 	
165 	@SuppressWarnings("unchecked")
166 	public Playlist songsByBand(long bandid) {
167 		Playlist result = new Playlist();
168 		
169 		Map<String,Object> params = new HashMap<String,Object>();
170 		params.put("bandid",bandid);
171 		List<Song> l = search("from Song s where s.band.id=:bandid order by s.name",params,0);
172 		
173 		result.setName("");
174 		int counter = 1;
175 		for (Song s : l) {
176 			result.addSong(s,counter++);
177 		}
178 	
179 		return result;
180 	}
181 
182 	/*
183 	 * (non-Javadoc)
184 	 * @see org.musicontroller.dao.Dao#search(java.lang.String, java.util.Map, int)
185 	 */
186 	@SuppressWarnings("unchecked")
187 	public List search(String hql, Map<String,Object> params, int maxResults) {
188 		return search(hql, params, maxResults, 0);
189 	}
190 
191 	/*
192 	 * (non-Javadoc)
193 	 * @see org.musicontroller.dao.Dao#search(java.lang.String, java.util.Map, int, int)
194 	 */
195 	@SuppressWarnings("unchecked")
196 	public List search(String hql, Map<String,Object> params, int maxResults, int offset) {
197 		List result = new ArrayList();
198 		try {
199 			Query q = searchInternal(hql, params, maxResults, offset);
200 			if(q!=null) {
201 				result = q.list();
202 			}
203 		} catch (Exception e) {
204 			LOG.error("Query failed: "+e.toString());
205 			e.printStackTrace();
206 		}
207 		return result;
208 	}
209 
210 	/**
211 	 * Searches in the Database, and returns the objects that were selected.
212 	 * @param hql The HQL-query.
213 	 * @param params Query parameters as name,value pairs
214 	 * @param maxResults The maximum amount of objects to return
215 	 * @param offset The first <i>offset</i> objects will not be returned
216 	 * @return The objects that fit to the search-query.
217 	 * @throws Exception When the query failed.
218 	 */
219 	private Query searchInternal(String hql, Map<String,Object> params,
220 			int maxResults, int offset) throws Exception {
221 		Session session = SessionFactoryUtils.getSession(getSessionFactory(),
222 				true);
223 		
224 		try {
225 			LOG.debug("Query: "+hql);
226 			Query query = session.createQuery(hql);
227 			if (maxResults > 0)
228 				query.setMaxResults(maxResults);
229 			if (offset > 0)
230 				query.setFirstResult(offset);
231 
232 			if (params != null) {
233 				LOG.debug("Setting query parameters");
234 				for (Entry<String,Object> entry : params.entrySet()) {
235 					String s = entry.getKey();
236 					LOG.debug("Parameter: "+s);
237 					Object value = entry.getValue();
238 					LOG.debug("Value: "+value);
239 					if (value instanceof Collection) {
240 						query.setParameterList(s,(Collection<?>) value);
241 					} else {
242 						query.setParameter(s,value);
243 					}
244 				}
245 				LOG.debug("All parameters set");
246 			}
247 
248 			return query;
249 		} catch (HibernateException e) {
250 			LOG.error("Query failed: "+e.toString());
251 			throw new Exception("Query failed. " + e.toString());
252 		}
253 	}
254 
255 	@SuppressWarnings("unchecked")
256 	public Band searchBand(String bandname) {
257 		Band result = null;
258 		List<Band> results = getHibernateTemplate().findByNamedParam(
259 				"from Band b where b.name=:name", "name", LinkableAbs.normalizeName(bandname));
260 		if (results.size() > 0) {
261 			result = results.get(0);
262 		}
263 		return result;
264 	}
265 
266 	@SuppressWarnings("unchecked")
267 	public Keyword searchKeyword(String keyworddesc) {
268 		if(keyworddesc==null) {
269 			return null;
270 		}
271 		Keyword result = null;
272 		List<Keyword> results = getHibernateTemplate().findByNamedParam(
273 				"from Keyword k where lower(k.name)=:name", "name", LinkableAbs.normalizeName(keyworddesc).toLowerCase());
274 		if (results.size() > 0) {
275 			result = results.get(0);
276 		}
277 		return result;
278 	}
279 
280 	@SuppressWarnings("unchecked")
281 	public List<Playlist> searchPlaylist(String playlistname) {
282 		List<Playlist> results = getHibernateTemplate().findByNamedParam(
283 				"from Playlist p where p.name=:name", "name", LinkableAbs.normalizeName(playlistname));
284 		return results;
285 	}
286 	
287 	public void save(Band band) {
288 		getHibernateTemplate().save(band);
289 	}
290 
291 	public void save(Keyword keyword) {
292 		getHibernateTemplate().save(keyword);
293 	}
294 
295 	public void save(Keywordbag keywordbag) {
296 		getHibernateTemplate().save(keywordbag);
297 	}
298 	
299 	public void save(Playlist playlist) {
300 		getHibernateTemplate().save(playlist);
301 	}
302 
303 	public void save(Link link) {
304 		getHibernateTemplate().save(link);
305 	}
306 
307 	/*
308 	 * (non-Javadoc)
309 	 * @see org.musicontroller.dao.Dao#save(org.musicontroller.core.Song)
310 	 */
311 	public void save(Song song) {
312 		boolean newSong = song.getId()==-1L; //New Songs have an 'uninitialized' id.
313 		getHibernateTemplate().save(song);
314 		if (newSong) {
315 			fireSongAdded(song);
316 		} else {
317 			fireSongChanged(song);
318 		}
319 	}
320 
321 	public void save(Artist artist) {
322 		getHibernateTemplate().save(artist);
323 	}
324 	
325 	public void save(Instrument instr) {
326 		getHibernateTemplate().save(instr);
327 	}
328 
329 	public void save(AIBag aibag) {
330 		getHibernateTemplate().save(aibag);
331 	}
332 
333 	@SuppressWarnings("unchecked")
334 	public IUser findUserByName(String username) {
335 		IUser result = null;
336 		List<IUser> results = getHibernateTemplate().findByNamedParam(
337 				"from User u where u.nickname=:name", "name", username);
338 		if (results.size() > 0) {
339 			result = results.get(0);
340 		}
341 		return result;
342 	}
343 	
344 	private void appendSongIDsToPlaylist(Playlist playlist, List<Long> songids) {
345 		if(songids==null) {
346 			return;
347 		}
348 		int counter=playlist.getSongs().size()+1;
349 		for (Long songid : songids) {
350 			playlist.addSong(getSongById(songid),counter++);
351 		}
352 	}
353 
354 	/**
355 	 * Returns a Playlist with a maximum of amount songs.
356 	 * @param Specifies a title for the playlist
357 	 * @param amount The maximum amount of songs
358 	 * @param user The User, to select the Toplist for. When
359 	 * this parameter is null anonymous statistics are returned
360 	 * @param eventkind The eventkind to base the top-list on
361 	 * @param daybegin Specifies the Date from which Events are considered
362 	 * Set this value to null to consider all Events.
363 	 * @param dayend Specifies the last date to be consideren.
364 	 * Set this value to null to consider all events after daybegin
365 	 * @param bags Specifies a List of Keywordbags. If this is a non-null
366 	 * parameter, all songs in the resulting Playlist will have a Keywordbag that
367 	 * is any of the Keywordbags specified.
368 	 * @return A Playlist containing the specified criteria, sorted by popularity
369 	 */
370 	@SuppressWarnings("unchecked")
371 	private Playlist getTopSongsInternal(String title, int amount, final IUser user, final int eventkind, final Date daybegin, Date dayend, List<Keywordbag> bags) {
372 		Playlist result = new Playlist();
373 		Map<String,Object> params = new HashMap<String,Object>();
374 		StringBuilder sb = new StringBuilder();
375 
376 		sb.append("select ev.song.id from Event ev where ev.eventkind=:kind ");
377 		params.put("kind",eventkind);
378 		
379 		if (user!=null) {
380 			sb.append("and ev.user=:user ");
381 			params.put("user",user);
382 		}
383 		
384 		if (daybegin!=null) {
385 			sb.append("and ev.moment>=:daybegin ");
386 			params.put("daybegin",daybegin);
387 			result.setFilterBegin(daybegin);
388 			if (dayend!=null) {
389 				sb.append("and ev.moment<:dayend ");
390 				params.put("dayend",dayend);
391 				result.setFilterEnd(dayend);
392 			}
393 		}
394 		
395 		if (bags!=null && bags.size()>0) {
396 			sb.append("and ev.song.keywordbag.id in (");
397 			Iterator<Keywordbag> it = bags.iterator();
398 			while (it.hasNext()) {
399 				sb.append(it.next().getId());
400 				if (it.hasNext()) {
401 					sb.append(",");
402 				}
403 			}
404 			sb.append(") ");
405 		}
406 		
407 		sb.append("group by ev.song.id order by count(ev.id) desc, max(ev.moment) desc)"); 
408 		
409 		List<Long> l = search(sb.toString(),params,amount);
410 		this.appendSongIDsToPlaylist(result,l);
411 		result.setName(title);
412 		return result;
413 	}
414 	
415 	/**
416 	 * Queries the database for the list of songs inserted between two dates.
417 	 * @param title The title to use for the Playlist
418 	 * @param daybegin The timestamp from which to start counting (inclusive).
419 	 * @param dayend The timestamp to stop counting at (exclusive).
420 	 * @return A Playlist, containing the requested songs.
421 	 */
422 	@SuppressWarnings("unchecked")
423 	private Playlist getNewSongs(String title, Date daybegin, Date dayend) {
424 		String hql = "from Song s where s.inserted>=:daybegin and s.inserted<:dayend " +
425 			"order by s.inserted desc";
426 
427 		Map<String,Object> params = new HashMap<String,Object>();
428 		params.put("daybegin",daybegin);
429 		params.put("dayend",dayend);
430 
431 		List<Song> l = search(hql,params,0);
432 		
433 		Playlist result = new Playlist();
434 		result.setName(title);
435 		int counter = 1;
436 		for (Song s : l) {
437 			result.addSong(s,counter++);
438 		}
439 		return result;
440 	}
441 	
442 	/**
443 	 * Works together with the ConsistencyChecker to scan for songs with
444 	 * broken filesystem links. This Playlist is intended to use to make
445 	 * repairs in the database.
446 	 * @param title The title to use for the Playlist
447 	 * @return A Playlist of songs with broken filesystem links.
448 	 */
449 	private Playlist getBrokenSongs(String title) {
450 		final int maxSize = 250;
451 		DenseSet errors = ConsistencyChecker.getBrokenSongIDs();
452 		int size = errors.size();
453 		
454 		Playlist result = new Playlist();
455 		if (size>maxSize) {
456 			title = title + " (first "+maxSize+" shown)";
457 		}
458 		result.setName(title);
459 		
460 		int counter = 0;
461 		while (counter<maxSize && counter<size) {
462 			long songid = errors.get(counter);
463 			Song s = getSongById(songid);
464 			result.addSong(s,counter++);
465 		}
466 		
467 		return result;
468 	}
469 	
470 	private Playlist getTopSongs(String title, int amount, IUser user, int eventkind, Date daybegin, Date dayend) {
471 		Playlist result = getTopSongsInternal(title,amount,user,eventkind,daybegin,dayend,null);
472 		
473 		if (daybegin!=null && dayend!=null) {
474 			Date olddaybegin = DateTools.previousMonthEnd(daybegin);
475 			Date olddayend = DateTools.previousMonthEnd(dayend);
476 			//We create a toplist of the period before the specified one to make it possible
477 			//to show the difference of position of songs in these two periods
478 			result.setPrevious( getTopSongsInternal(title,amount,user,eventkind,olddaybegin,olddayend,null) );
479 		}
480 		
481 		return result;
482 	}
483 	
484 	@SuppressWarnings("unchecked")
485 	private Playlist getSongsPlayedAt(String title, IUser user, Date daybegin, Date dayend) {
486 		Playlist result = new Playlist();
487 		
488 		StringBuilder sb = new StringBuilder();
489 		Map<String,Object> params = new HashMap<String,Object>();
490 		
491 		sb.append("select e.song from Event e where e.moment>=:daybegin and e.moment<:dayend and e.eventkind=:kind ");
492 		params.put("daybegin",daybegin);
493 		params.put("dayend",dayend);
494 		params.put("kind",Event.played);
495 		
496 		if (user!=null) {
497 			sb.append("and e.user=:user ");
498 			params.put("user",user);
499 		}
500 		sb.append("order by e.moment desc");
501 		
502 		List<Song> l = search(sb.toString(),params,0);
503 		
504 		result.setName(title);
505 		int counter = 1;
506 		for (Song s : l) {
507 			result.addSong(s,counter++);
508 		}
509 		
510 		return result;
511 	}
512 
513 	
514 	/*
515 	 * (non-Javadoc)
516 	 * @see org.musicontroller.dao.Dao#getBandByName(java.lang.String)
517 	 */
518 	@SuppressWarnings("unchecked")
519 	public Playlist getPlaylistByName(String name) {
520 		String sql = "from Playlist p where lower(p.name)=:name order by p.inserted";
521 		List<Playlist> playlistlist = getHibernateTemplate().findByNamedParam(sql,
522 					new String[] {"name"},
523 					new Object[] {LinkableAbs.normalizeName(name).toLowerCase()});
524 		return playlistlist.size()==0 ? null : playlistlist.get(0);
525 	}
526 
527 	public Playlist getPlaylistById(long id, IUser user) {
528 		PlaylistKey key = new PlaylistKey(id);
529 		if (key.isSpecial()) {
530 			return getSpecialPlaylist(key.getKind(),user,key.getFilter());
531 		} else {
532 			return (Playlist) getHibernateTemplate().get(Playlist.class,id);
533 		}
534 	}
535 
536 	private Playlist getSpecialPlaylist(int kind, IUser user, Date filter) {
537 		if (user==null && kind>-1000) kind-=1000; //Get anonymous stats when user==null
538 		
539 		Calendar begin = new GregorianCalendar();
540 		begin.set(Calendar.HOUR_OF_DAY,0);
541 		begin.set(Calendar.MINUTE,0);
542 		begin.set(Calendar.SECOND,0);
543 		
544 		Calendar end = new GregorianCalendar();
545 		end.set(Calendar.HOUR_OF_DAY,23);
546 		end.set(Calendar.MINUTE,59);
547 		end.set(Calendar.SECOND,59);
548 		
549 		Date prevmonth = DateTools.previousMonthEnd(filter);
550 		Date endofmonth = DateTools.endOfMonth(filter);
551 		String dateDesc = DateTools.formatDate(filter,"MMMMM yyyy");
552 		
553 		//TODO Use the enum-type for Dynamic Playlists 
554 		switch (kind) {
555 		case -1 : return getTopSongs(user+"'s top 100",100,user,Event.played,null,null);
556 		case -2 : return getSongsPlayedAt("Songs played today by "+user,user,begin.getTime(),end.getTime()); 
557 		case -3 : return getTopSongs(user+"'s top 100 most requested songs",100,user,Event.requested,null,null);
558 		case -4 : return getTopSongs(user+"'s top 25 of "+dateDesc,25,user,Event.played,prevmonth,endofmonth);
559 		case -5 : return getTopSongs(user+"'s top 25 requested of "+dateDesc,25,user,Event.requested,prevmonth,endofmonth);
560 		case -6 : return getTopSongs(user+"'s horrible 100",100,user,Event.skipped,null,null);
561 		case -7 : return getForgottenSongs(user);
562 		case -8 : {
563 			begin.setTime(filter);
564 			begin.set(Calendar.HOUR_OF_DAY,0);
565 			begin.set(Calendar.MINUTE,0);
566 			begin.set(Calendar.SECOND,0);
567 			begin.set(Calendar.MONTH,0);
568 			begin.set(Calendar.DAY_OF_MONTH, 1);
569 			
570 			end.setTime(filter);
571 			end.set(Calendar.HOUR_OF_DAY,23);
572 			end.set(Calendar.MINUTE,59);
573 			end.set(Calendar.SECOND,59);
574 			end.set(Calendar.MONTH,11);
575 			end.set(Calendar.DAY_OF_MONTH,31);
576 			return getTopSongs(user+"'s top 100 in "+DateTools.formatDate(begin.getTime(), "yyyy"),100,user,Event.played,begin.getTime(),end.getTime());
577 		}
578 		
579 		//------------------ Anonymous stats
580 		case -1001 : return getTopSongs("Top 100",100,null,Event.played,null,null);
581 		case -1002 : return getSongsPlayedAt("Songs played today",null,begin.getTime(),end.getTime()); 
582 		case -1003 : return getTopSongs("Top 100 most requested songs",100,null,Event.requested,null,null);
583 		case -1004 : return getTopSongs("Top 25 of "+dateDesc,25,null,Event.played,prevmonth,endofmonth);
584 		case -1005 : return getTopSongs("Top 25 requested of "+dateDesc,25,null,Event.requested,prevmonth,endofmonth);
585 		case -1006 : return getTopSongs("Horrible 100",100,null,Event.skipped,null,null);
586 		case -1007 : return getForgottenSongs(null);
587 		
588 		case -2001 : return getNewSongs("Songs inserted in "+dateDesc,prevmonth,endofmonth);
589 		case -2002 : return getBrokenSongs("Songs that don't have a correct filesystem-link");
590 		default : return null;
591 		}
592 	}
593 
594 	/**
595 	 * Lists a maximum amount of 25 Songs, with the following properties:
596 	 * <ul>
597 	 * <li>The Songs have not been played, skipped, downloaded or requested at 
598 	 * all during the last two years.</li>
599 	 * <li>The Songs have been played at least 10 times.</li>
600 	 * </ul>
601 	 * The list is ordered to the plays/year ratio. 
602 	 * 
603 	 * @param user The User for who to create this list for (may be null).
604 	 * @return A Playlist, containing at most 25 Songs.
605 	 */
606 	@SuppressWarnings("unchecked")
607 	protected Playlist getForgottenSongs(IUser user) {
608 		Playlist result = new Playlist();
609 		result.setName(user + "'s forgotten songs");
610 		
611 		StringBuilder hql = new StringBuilder();
612 		hql.append("select s.id from Song s, Event e "); 
613 		hql.append("where s=e.song ");
614 		hql.append("and s.id in ( ");
615 		hql.append("select plays.song.id from Event plays where plays.user=:user and plays.eventkind=:eventkind group by plays.song.id having max(plays.moment)<:twoyearsago and count(plays.id)>=10");
616 		hql.append(")");
617 		if (user!=null) hql.append("and e.user=:user "); 
618 		hql.append("group by s.id, s.inserted "); 
619 		hql.append("having max(e.moment)<:twoyearsago ");
620 		hql.append("order by (:twoyearsago-s.inserted)/count(e.id)"); 
621 		//FIXME Does not work in HSQL
622 		//Timestamp - Timestamp is Interval in Postgres, which you can divide by something
623 		//Timestamp - Timestamp remains Timestamp in HSQL, which you cannot divide.
624 
625 		Map params = new HashMap<String, Object>();
626 		params.put("user", user);
627 		params.put("eventkind", Event.played);
628 		Date now = new Date();
629 		Date twoyearsago = new Date(now.getTime()- (2L*365*24*60*60*1000) );
630 		params.put("twoyearsago", twoyearsago);
631 		List<Long> songIDs = search(hql.toString(), params, 25); 
632 			
633 		int counter = 0;
634 		while (counter<songIDs.size()) {
635 			long songid = songIDs.get(counter);
636 			Song s = getSongById(songid);
637 			result.addSong(s,++counter);
638 		}
639 		
640 		return result;
641 	}
642 
643 	public IUser getUserById(long userId) {
644 		return (IUser) getHibernateTemplate().get(User.class, userId);
645 	}
646 
647 	@SuppressWarnings("unchecked")
648 	public IUser findUserByNamePassword(String username, String password) {
649 		if(username==null) {
650 			return null;
651 		}
652         IUser gebruiker = null;
653         String encryptedPassword = Encrypter.encrypt(password);
654         List<IUser> gebrList = getHibernateTemplate().findByNamedParam(
655                 "from User p where p.loginname=:inlognaam and p.password=:password",
656                 new String[] { "inlognaam", "password"},
657                 new Object[] { username, encryptedPassword});
658         if (gebrList != null && !gebrList.isEmpty()) {
659         	gebruiker = (IUser) gebrList.get(0);
660         }
661         return gebruiker;
662 	}
663 
664 	@SuppressWarnings("unchecked")
665 	public List<IUser> listUsers() {
666 		return (List<IUser>) getHibernateTemplate().find("from User u order by u.loginname");
667 	}
668 	
669 	@SuppressWarnings("unchecked")
670 	public List<IUser> findUserByLoginname(String loginname) {
671 		if(loginname==null) {
672 			return new Vector<IUser>();
673 		}
674 		return getHibernateTemplate().findByNamedParam(
675 					"from User p where p.loginname=:inlognaam",
676 					new String[]{"inlognaam"},
677 					new String[]{loginname});
678 	}
679 
680 	@SuppressWarnings("unchecked")
681 	public IUser getUserByLoginname(String inlognaam) {
682 		List<IUser> l = getHibernateTemplate().findByNamedParam("from User p where p.loginname=:inlognaam",
683 			new String[]{"inlognaam"},
684 			new Object[]{inlognaam});
685 		return (l.size()>0) ? l.get(0) : null;
686 	}
687 	
688 	public void save(IUser user) {
689 		getHibernateTemplate().saveOrUpdate(user);
690 	}
691 	
692 	public void delete(IUser user) {
693 		getHibernateTemplate().delete(user);
694 	}
695 	
696 	public Role getRoleById(int roleId) {
697 		return (Role) getHibernateTemplate().get(Role.class, roleId);
698 	}
699 	
700 	@SuppressWarnings("unchecked")
701 	public List<Role> listRoles() {
702 		return (List<Role>) getHibernateTemplate().find("from Role r order by r.name");
703 	}
704 	
705 	public void save(Role role) {
706 		getHibernateTemplate().saveOrUpdate(role);
707 	}
708 	
709 	public Authority getAuthorityById(int authorityId) {
710 		return (Authority) getHibernateTemplate().get(Authority.class, authorityId);
711 	}
712 	
713 	@SuppressWarnings("unchecked")
714 	public List<Authority> listAuthorities() {
715 		return (List<Authority>) getHibernateTemplate().find("from Authority a order by a.name");
716 	}
717 	
718 	public void save(Authority authority) {
719 		getHibernateTemplate().saveOrUpdate(authority);
720 	}
721 
722 	@SuppressWarnings("unchecked")
723 	public List<Playlist> listPlaylists(Band band) {
724 		return getHibernateTemplate().findByNamedParam(
725 				"from Playlist p where p in (" +
726 				"select distinct (ps.playlist) from Contract_PS ps, Song s "+
727 				"where ps.song=s and s.band=:band) order by p.releasedate, p.name",
728 				new String[]{"band"},
729 				new Object[]{band});
730 	}
731 
732 	@SuppressWarnings("unchecked")
733 	public List<Playlist> listPlaylists(Song song) {
734 		return getHibernateTemplate().findByNamedParam(
735 				"from Playlist p where p in (" +
736 				"select distinct (ps.playlist) from Contract_PS ps "+
737 				"where ps.song=:song) order by p.releasedate, p.name",
738 				new String[]{"song"},
739 				new Object[]{song});
740 	}
741 	
742 	@SuppressWarnings("unchecked")
743 	public List<Playlist> listPlaylists() {
744 		return getHibernateTemplate().find("from Playlist p");
745 	}
746 	
747 	@SuppressWarnings("unchecked")
748 	public List<Playlist> listPodcasts() {
749 		return getHibernateTemplate().find("from Playlist p where p.link is not null");
750 	}
751 	
752 	public void evictSong(long songId) {
753 		Song song = getSongById(songId);
754 		getHibernateTemplate().evict(song);
755 	}
756 
757 	public void evict(Object o) {
758 		getHibernateTemplate().evict(o);
759 	}
760 	
761 	@SuppressWarnings("unchecked")
762 	public List<Object[]> listBands(IUser user) {
763 		Map<String,Object> params = new HashMap<String,Object>();
764 		StringBuffer sb = new StringBuffer();
765 		sb.append("select b.id, b.name, sum(case when ev.eventkind="+Event.played+" then 1 else 0 end), ");
766 		sb.append("sum(case when ev.eventkind="+Event.skipped+" then 1 else 0 end)");
767 		sb.append(" from Song s join s.band b left outer join s.events ev");
768 		sb.append(" with ev.moment>:startdate ");
769 
770 		if(user!=null) {
771 			sb.append("and ev.user.id=:userid");
772 			params.put("userid",user.getId());
773 		}
774 		sb.append(" group by b.id, b.name, b.sortname");
775 		sb.append(" order by b.sortname");
776 		
777 		Calendar lastYear = new GregorianCalendar();
778 		lastYear.add(Calendar.YEAR,-1);
779 
780 		params.put("startdate",lastYear.getTime());
781 
782 		return search(sb.toString(),params,0);
783 	}
784 
785 	/*
786 	 * (non-Javadoc)
787 	 * @see org.musicontroller.dao.Dao#listKeywords()
788 	 */
789 	@SuppressWarnings("unchecked")
790 	public List<Keyword> listKeywords() {
791 		return getHibernateTemplate().find("from Keyword k");
792 	}
793 
794 	
795 	@SuppressWarnings("unchecked")
796 	public List<Object[]> listKeywords(IUser user, List<Keywordbag> bags) {
797 		/*
798 		 * select k.id, k.name, count(ev.id) as co from keyword k, keywordbag kwb, song s left join event ev on ev.song_id=s.id and ev.moment>'2006.01.01' and (ev.eventkind=2 or ev.eventkind=3) and ev.user_id=2
799 where s.kbrel_id=kwb.id and k.id=kwb.keyword_id
800 group by k.id, k.name
801 order by co desc
802 		 */
803 		/* Ha, dat kan nog veel ingewikkelder!
804 	     * select keyword2_.id as col_0_0_, keyword2_.name as col_1_0_, count(events5_.id) as col_2_0_ 
805 	     * from s_kb_relation keywordbag0_ 
806 	     * left outer join keywordbag keywords1_ on keywordbag0_.id=keywords1_.bag_id 
807 	     * left outer join keyword keyword2_ on keywords1_.keyword_id=keyword2_.id, song song3_ 
808 		 * inner join s_kb_relation keywordbag4_ on song3_.kbrel_id=keywordbag4_.id 
809 		 * left outer join event events5_ on song3_.id=events5_.song_id and (events5_.moment>'2006.01.01'  and events5_.eventkind=2 and events5_.user_id=100) 
810 		 * where keywordbag4_.id=keywordbag0_.id 
811 		 * group by keyword2_.id , keyword2_.name 
812 		 * order by keyword2_.name		 */
813 		StringBuilder sb = new StringBuilder();
814 		
815 		sb.append("select k.id, k.name, sum(case when ev.eventkind="+Event.played+" then 1 else 0 end), ");
816 		sb.append("sum(case when ev.eventkind="+Event.skipped+" then 1 else 0 end)");
817 		sb.append(" from Keywordbag kwb left join kwb.keywords k,");
818 		sb.append("  Song s join s.keywordbag skwb left join s.events as ev ");
819 		sb.append(" with ev.moment>:startdate and ev.user.id=:userid ");
820 		sb.append(" where skwb=kwb and k.id is not null");
821 
822 		if (bags!=null) {
823 			// If the bags is non-null and there are no keywords in the bag,
824 			// then the result should be empty as well.
825 			if (bags.size()==0) {
826 				return null;
827 			}
828 			sb.append(" and kwb.id in(");
829 			Iterator<Keywordbag> it = bags.iterator();
830 			while (it.hasNext()) {
831 				sb.append(it.next().getId());
832 				if (it.hasNext()) {
833 					sb.append(",");
834 				}
835 			}
836 			sb.append(")");
837 		}
838 		sb.append(" group by k.id, k.name ");
839 		sb.append(" order by k.name");
840 
841 		Calendar lastYear = new GregorianCalendar();
842 		lastYear.add(Calendar.YEAR,-1);
843 
844 		Map<String,Object> params = new HashMap<String,Object>();
845 		params.put("userid",user.getId());
846 		params.put("startdate",lastYear.getTime());
847 
848 		return search(sb.toString(),params,0);
849 	}
850 	
851 	@SuppressWarnings("unchecked")
852 	private List<Keywordbag> listKeywordbagsInternal(Collection<Keyword> keywords, boolean exactmatch) {
853 		if (keywords==null) return null;
854 		if (keywords.size()==0) return null;
855 
856 		Map<String,Object> params = new HashMap<String,Object>();
857 
858 		StringBuilder sb = new StringBuilder();
859 		sb.append("select kwb.id from Keywordbag kwb");
860 		if(exactmatch) {
861 			sb.append(" where kwb.keywords.size=:size ");
862 		} else {
863 			sb.append(" where kwb.keywords.size>=:size ");
864 		}
865 		
866 		Iterator<Keyword> it = keywords.iterator();
867 		int keywordcount = 0;
868 		while (it.hasNext()) {
869 			keywordcount++;
870 			Keyword kw = it.next();
871 			String paramname = "k"+String.valueOf(keywordcount);
872 			sb.append(" and :");
873 			sb.append(paramname);
874 			sb.append(" in elements(kwb.keywords)");
875 			params.put(paramname, kw);
876 		}
877 		params.put("size",keywordcount);
878 		
879 		List<Long> kwbIds = search(sb.toString(),params,0);
880 		
881 		if (kwbIds==null) {
882 			return null;
883 		} else {
884 			List<Keywordbag> bags = new LinkedList<Keywordbag>();
885 			for (Long kwbid : kwbIds) {
886 				bags.add(getKeywordBagById(kwbid));
887 			}
888 			return bags;
889 		}
890 	}
891 	
892 	public List<Keywordbag> listKeywordsBags(List<Keyword> keywords) {
893 		/* 	select bag_id, count(id) from keywordbag where keyword_id in (1986,1987)
894 		 * 	group by bag_id having count(bag_id)>=2
895 		 */
896 		return listKeywordbagsInternal(keywords,false);
897 	}
898 	
899 	public Keywordbag getKeywordsBag(Collection<Keyword> keywords) {
900 		/* 	select bag_id, count(id) from keywordbag where keyword_id in (1986,1987)
901 		 * 	group by bag_id having count(bag_id)==2
902 		 */
903 		List<Keywordbag> bags = listKeywordbagsInternal(keywords,true);
904 		return bags==null ? null : bags.size()==0 ? null : bags.get(0);
905 	}
906 
907 	public Playlist songsByKeywordbags(List<Keywordbag> bags, IUser user, int maxResults) {
908 		String title = "All songs with keywordbags...";
909 		Date daybegin = DateTools.currentDate();
910 		Calendar lastYear = new GregorianCalendar();
911 		lastYear.add(Calendar.YEAR,-1);
912 		return getTopSongsInternal(title,maxResults,user,Event.played,lastYear.getTime(),daybegin,bags);
913 	}
914 	
915 	@SuppressWarnings("unchecked")
916 	private Playlist songsByKeywordsInternal(List<Keyword> keywords, IUser user) {
917 		//TODO If user is set, sort by playcount
918 		/* select s.*, kwb.* from song s, keywordbag kwb, keyword k where k.id=1
919 		 * and kwb.keyword_id=k.id and s.kbrel_id=kwb.id
920 		 */
921 		//TODO Currently maxed at 250 items, should be configurable
922 		Playlist result = new Playlist();
923 		if (keywords.size()==0) {
924 			result.setName("");
925 		} else {
926 			StringBuilder sb = new StringBuilder();
927 			//Find the bag(s) that containt the keywords mentioned return the
928 			//songs in those bag(s)
929 			sb.append("select s from Keywordbag kwb left join kwb.keywords as k, Song s where k in (:keywords) ");
930 			sb.append("and s.keywordbag=kwb.id order by s.name");
931 			
932 			Map<String, Object> params = new HashMap<String,Object>();
933 			params.put("keywords", keywords);
934 			List<Song> l = search(sb.toString(),params,250);
935 			
936 			result.setName("All songs with keyword '"+keywords+"'");
937 			int counter = 1;
938 			for (Song s : l) {
939 				result.addSong(s,counter++);
940 			}
941 		}
942 	
943 		return result;
944 	}
945 	
946 	public Playlist songsByKeyword(long keywordid) {
947 		Keyword keyword = getKeywordById(keywordid);
948 		List<Keyword> keywords = new LinkedList<Keyword>();
949 		keywords.add(keyword);
950 		return songsByKeywordsInternal(keywords,null);
951 	}
952 	
953 	public Playlist songsByKeywords(List<Keyword> keywords, IUser user) {
954 		return songsByKeywordsInternal(keywords,user);
955 	}
956 
957 	public Playlist songsByKeywordIds(List<Long> keywordIds, IUser user) {
958 		List<Keyword> keywords = new LinkedList<Keyword>();
959 		if (keywordIds!=null) {
960 			for (Long keywordid : keywordIds) {
961 				Keyword keyword = getKeywordById(keywordid);
962 				keywords.add(keyword);
963 			}
964 		}
965 		return songsByKeywordsInternal(keywords,null);
966 	}
967 
968 	@SuppressWarnings("unchecked")
969 	public List<Keywordbag> listKeywordbags() {
970 		return (List<Keywordbag>) getHibernateTemplate().find("from Keywordbag kbg");
971 	}
972 	
973 	/*
974 	 * (non-Javadoc)
975 	 * @see org.musicontroller.dao.Dao#getNeighbours(long, org.musicontroller.security.IUser)
976 	 */
977 	@SuppressWarnings("unchecked")
978 	public Playlist getNeighbours(long songid, IUser user) {
979 		/* 	select n.song_id, count(n.moment) from event ev, event n
980 		 *	where ev.song_id=223 and ev.user_id=2 and ev.eventkind in (1,2)
981   		 *	and n.user_id=2 and n.eventkind in (1,2)
982   		 *	and n.song_id<>ev.song_id and (n.moment between ev.moment - time '0:10' and ev.moment + time '0:10')
983   		 *	group by n.song_id
984   		 *	having count(n.moment)>=5
985   		 *	order by count(n.moment) desc 
986 		 */
987 		
988 		Playlist result = new Playlist();
989 		Map<String,Object> params = new HashMap<String,Object>();
990 		StringBuilder sb = new StringBuilder();
991 
992 		sb.append("select n.song.id from Event ev, Event n ");
993 		sb.append("where ev.song.id=:songid and ev.eventkind in (:kinds1) ");
994 		if (user!=null) {
995 			sb.append("and ev.user=:user ");
996 			params.put("user",user);
997 		}
998 		sb.append("and n.user=ev.user and n.eventkind in (:kinds2) ");
999 		sb.append("and n.song<>ev.song and n.moment between ev.moment+'-0:10' and ev.moment+'0:10' ");
1000 		sb.append("group by n.song.id ");
1001 		sb.append("having count(n.moment)>=5 ");
1002 		sb.append("order by count(n.moment) desc");
1003 		
1004 		Collection<Integer> kinds = new LinkedList<Integer>();
1005 		kinds.add(Event.played);
1006 		params.put("kinds1",kinds);
1007 		params.put("kinds2",kinds);
1008 		params.put("songid",songid);
1009 		
1010 		List<Long> l = search(sb.toString(),params,0);
1011 		if(l!=null) {
1012 			appendSongIDsToPlaylist(result,l);
1013 		}
1014 		result.setName("Neighbouring songs of song: "+songid);			
1015 		return result;
1016 	}
1017 	
1018 	@SuppressWarnings("unchecked")
1019 	public List<Long[]> getDoubleSongs() {
1020 		/* 	select s1.id, s2.id, s1.name, s2.name, s1.length, s2.length
1021 		 * from song s1, song s2
1022 		 * where s1.id<s2.id and s1.name ilike s2.name
1023 		 * and s1.band_id=s2.band_id
1024 		 * and s1.length-s2.length between -10000 and 10000
1025 		 * order by s1.name 
1026 		 */
1027 		
1028 		StringBuilder sb = new StringBuilder();
1029 		sb.append("select s1.id, s2.id from Song s1, Song s2 ");
1030 		sb.append("where s1.id<s2.id and lower(s1.name) like lower(s2.name) ");
1031 		sb.append("and s1.band=s2.band and abs(s1.length-s2.length)<10000 ");
1032 		sb.append("order by s1.name");
1033 		List<Object[]> l = search(sb.toString(),new HashMap<String,Object>(),0);
1034 
1035 		//Transform the list to a list of Long-pairs
1036 		//TODO Won't a cast just work?
1037 		List<Long[]> thePairs = new LinkedList<Long[]>();
1038 		for (Object[] ll : l) {
1039 			Long[] pair = new Long[2];
1040 			pair[0] = (Long) ll[0];
1041 			pair[1] = (Long) ll[1];
1042 			thePairs.add(pair);
1043 		}
1044 		
1045 		//TODO Some similarities are not detected:
1046 		//TODO Billy vs Billie
1047 		//TODO Cant vs Can't
1048 		//TODO U vs You
1049 		
1050 		//TODO Same function with similar bands??? Mc Hammer vs MC Hammer...
1051 		
1052 		
1053 		return thePairs;
1054 	}
1055 
1056 	public void mergeSongs(Song keep, Song remove) {
1057 		//Oldest inserted-date stays.
1058 		try {
1059 			keep.setInserted(new Date(Math.min(keep.getInserted().getTime(), remove.getInserted().getTime())));
1060 		} catch (Exception e) {
1061 			LOG.error("Not-fatal error setting date to oldest version when merging song: "+e);
1062 		}
1063 		
1064 		//Copy events from 'remove' to 'keep', event-cache is updated.
1065 		for (Event ev : remove.getEvents()) {
1066 			keep.addEvent(ev.getMoment(), ev.getUser(), ev.getEventkind());
1067 		}
1068 		save(keep); //Implicitly informs SongChangeListeners.
1069 		
1070 		//Move 'remove's' occurences in playlists to 'keep'
1071 		for (Playlist pl : listPlaylists(remove)) {
1072 			for (Contract_PS ps : pl.getSongs()) {
1073 				if (ps.getSong().equals(remove)) {
1074 					ps.setSong(keep);
1075 				}
1076 			}
1077 			save(pl);
1078 		}
1079 		
1080 		//If the link-object of 'remove' points to an other file than the link
1081 		//of 'keep', then delete the file that 'remove' points to
1082 		//TODO Keep biggest (highest quality) file?
1083 		File file = null;
1084 		if (remove.getLink()!=null) {
1085 			if (keep.getLink()!=null) {
1086 				if (!remove.getLink().getFile().equals(keep.getLink().getFile())) {
1087 					file = remove.getLink().getFile();
1088 				}
1089 			}
1090 		}
1091 		getHibernateTemplate().delete(remove);
1092 		getHibernateTemplate().flush(); 
1093 		//Flush here, if it fails, an exception is thrown, rollback is invoked, file will not be deleted 
1094 
1095 		fireSongDeleted(remove); //Inform SongChangeListeners
1096 		if (file!=null) {
1097 			if (!file.delete()) {
1098 				LOG.error("Could not delete duplicate file: " + file.getAbsolutePath());
1099 			}
1100 		}
1101 		
1102 		LOG.info("Song "+remove.getLink().getUrl()+" merged with song "+keep.getLink().getUrl()+".");
1103 	}
1104 	
1105 	@SuppressWarnings("unchecked")
1106 	public void mergeBands(Band keep, Band remove) {
1107 		// Build a list of songs to move
1108 		StringBuilder sb = new StringBuilder();
1109 		sb.append("from Song s where s.band=:remove");
1110 		Map<String, Object> params = new HashMap<String, Object>();
1111 		params.put("remove", remove);
1112 		List<Song> songs = search(sb.toString(), params,0);
1113 		// Move the songs in the list from "remove" to "keep".
1114 		for(Song s: songs) {
1115 			s.setBand(keep);
1116 			save(s);
1117 		}
1118 		// Eliminate the song to remove.
1119 		getHibernateTemplate().delete(remove);
1120 		LOG.info("All songs from band "+remove.getName()+" moved to band "+keep.getName()+
1121 				", band "+remove.getName()+" deleted.");
1122 	}
1123 
1124 	@SuppressWarnings("unchecked")
1125 	public Instrument searchInstrument(String instrname) {
1126 		Instrument result = null;
1127 		List<Instrument> instrlist = getHibernateTemplate().findByNamedParam(
1128 				"from Instrument i where lower(i.name)=:name",
1129 				new String[] { "name" },
1130 				new Object[] { LinkableAbs.normalizeName(instrname).toLowerCase() });
1131 		if (instrlist.size() > 0) {
1132 			result = instrlist.get(0);
1133 		}
1134 		return result;		
1135 	}
1136 
1137 	public int count(String hql, Map<String,Object> params) throws Exception {
1138 		Query query = searchInternal(hql,params,0,0);
1139 		int result = ((Long)query.iterate().next()).intValue(); 
1140 		return result;
1141 	}
1142 
1143 	@SuppressWarnings("unchecked")
1144 	public List<Object[]> listMonthlySongCounts() {
1145 		String hql =	"select month(s.inserted), year(s.inserted), count(s.id) "+
1146 						"from Song s "+
1147 						"group by month(s.inserted),year(s.inserted) "+
1148 						"order by year(s.inserted),month(s.inserted)";
1149 		
1150 		return search(hql,null,0);
1151 	}
1152 
1153 	/*
1154 	 * (non-Javadoc)
1155 	 * @see org.musicontroller.dao.Dao#listMonthlyStatistics(org.musicontroller.security.IUser)
1156 	 */
1157 	@SuppressWarnings("unchecked")
1158 	public Map<String, Integer> listMonthlyStatistics(IUser user) {
1159 		String hql = "select year(e.moment), month(e.moment), e.eventkind, count(e.id) from Event e " +
1160 				"where e.user=:user and e.eventkind>0 and e.moment is not null " +
1161 				"group by year(e.moment), month(e.moment), e.eventkind";
1162 		Map<String,Object> params = new HashMap<String,Object>();
1163 		params.put("user", user);
1164 		List<Object[]> iresult = search(hql, params, 0);
1165 		
1166 		// We now process the intermediate result, and spit out a result which
1167 		// conforms to the javadoc: Year, Month, Map<Eventkind, Integer>
1168 		Map<String, Integer> result = new TreeMap<String, Integer>(); //TreeMap guarantees sorted keys
1169 		for (Object[] obj : iresult) {
1170 			String key = obj[0].toString() + "," + obj[1].toString() + "," + obj[2].toString();
1171 			Integer value = 0;
1172 			if (obj[3] instanceof Integer) {
1173 				value = (Integer) obj[3];
1174 			} else if (obj[3] instanceof Long) {
1175 				value = ((Long) obj[3]).intValue();
1176 			}
1177 			result.put(key, value);	
1178 		}
1179 		return result;
1180 	}
1181 	
1182 	@SuppressWarnings("unchecked")
1183 	public List<Object[]> listNamableItems() {
1184 		List<Object[]> result = search("select s.name, s.id, 's' from Song s",null,0);
1185 		result.addAll(search("select i.name, i.id, 'i' from Instrument i",null,0));
1186 		result.addAll(search("select k.name, k.id, 'k' from Keyword k",null,0));
1187 		result.addAll(search("select a.lastname, a.id, 'a' from Artist a",null,0));
1188 		result.addAll(search("select b.name, b.id, 'b' from Band b",null,0));
1189 		result.addAll(search("select p.name, p.id, 'p' from Playlist p",null,0));
1190 		
1191 		return result;
1192 	}
1193 
1194 	
1195 	@SuppressWarnings("unchecked")
1196 	public AIBag getMostUsedAIBag(Band band) {
1197 		String hql = "select s.aibag.id from Song s where s.band=:band and s.aibag is not null group by s.aibag.id order by count(s.id) desc";
1198 		Map<String,Object> params = new HashMap<String,Object>();
1199 		params.put("band",band);
1200 		
1201 		List<Long> bags = search(hql,params,1);
1202 		if (bags.size()>0) {
1203 			return getAIBagById(bags.get(0));
1204 		} else {
1205 			return null;
1206 		}
1207 	}
1208 
1209 	@SuppressWarnings("unchecked")
1210 	public List<AIBag> listAIBags() {
1211 		return (List<AIBag>) getHibernateTemplate().find("from AIBag ai");		
1212 	}
1213 
1214 	public void mergePlaylists(Playlist keep, Playlist delete) {
1215 		if(keep==null || delete==null) {
1216 			return;
1217 		}
1218 		for (Contract_PS songs : delete.getSongs()) {
1219 			Song song = songs.getSong();
1220 			keep.addSong(song, songs.getRowno());
1221 		}
1222 		save(keep);
1223 		delete.getSongs().clear();
1224 		save(delete);
1225 		getHibernateTemplate().delete(delete);
1226 	}
1227 
1228 	public void deleteArtist(Artist delete) {
1229 		if(delete==null) {
1230 			return;
1231 		}
1232 		getHibernateTemplate().delete(delete);
1233 	}
1234 
1235 	public void deleteAiBag(AIBag delete) {
1236 		if(delete==null) {
1237 			return;
1238 		}
1239 		getHibernateTemplate().delete(delete);
1240 	}
1241 
1242 	/*
1243 	 * (non-Javadoc)
1244 	 * @see org.musicontroller.dao.Dao#openSession()
1245 	 */
1246 	public SessionFactory getSessionFactory2() {
1247 		return getSessionFactory();
1248 	}
1249 
1250 	/*
1251 	 * (non-Javadoc)
1252 	 * @see org.musicontroller.dao.Dao#registerSongChangeListener(org.musicontroller.SongChangeListener)
1253 	 */
1254 	public void registerSongChangeListener(SongChangeListener listener) {
1255 		_songChangeListeners.add(listener);
1256 	}
1257 	
1258 	/**
1259 	 * Informs listeners of a new Song.
1260 	 * @param song The new Song.
1261 	 */
1262 	private void fireSongAdded(Song song) {
1263 		LOG.debug("Informing " + _songChangeListeners.size()+" listeners about new Song with id: " + song.getId());
1264 		for (SongChangeListener listener : _songChangeListeners) {
1265 			listener.onSongAdded(song);
1266 		}
1267 	}
1268 
1269 	/**
1270 	 * Informs listeners of a deleted Song.
1271 	 * @param song The deleted Song.
1272 	 */
1273 	private void fireSongDeleted(Song song) {
1274 		LOG.debug("Informing " + _songChangeListeners.size()+" listeners about deleted Song with id: " + song.getId());
1275 		for (SongChangeListener listener : _songChangeListeners) {
1276 			listener.onSongDeleted(song);
1277 		}
1278 	}
1279 	
1280 	/**
1281 	 * Informs listeners of a changed Song.
1282 	 * @param song The changed Song.
1283 	 */
1284 	private void fireSongChanged(Song song) {
1285 		LOG.debug("Informing " + _songChangeListeners.size()+" listeners about changed Song with id: " + song.getId());
1286 		for (SongChangeListener listener : _songChangeListeners) {
1287 			listener.onSongChanged(song);
1288 		}
1289 	}
1290 	
1291 	public HibernateTemplate getSupport() {
1292 		return getHibernateTemplate();
1293 	}
1294 
1295 }