PHOTARK-28 - Applying patch from Phillipe Ramalho to enable tag search

git-svn-id: https://svn.apache.org/repos/asf/incubator/photark/trunk@985534 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/photark-search/src/main/java/org/apache/photark/search/services/ImageTags.java b/photark-search/src/main/java/org/apache/photark/search/services/ImageTags.java
new file mode 100644
index 0000000..904a77b
--- /dev/null
+++ b/photark-search/src/main/java/org/apache/photark/search/services/ImageTags.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+
+package org.apache.photark.search.services;
+
+import org.oasisopen.sca.annotation.Remotable;
+
+@Remotable
+public interface ImageTags {
+
+    String getImageID();
+    
+    String[] getTags();
+    
+}
diff --git a/photark-search/src/main/java/org/apache/photark/search/services/SearchService.java b/photark-search/src/main/java/org/apache/photark/search/services/SearchService.java
index 9ada930..d41acb9 100644
--- a/photark-search/src/main/java/org/apache/photark/search/services/SearchService.java
+++ b/photark-search/src/main/java/org/apache/photark/search/services/SearchService.java
@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.apache.photark.search.services;
 
 import org.apache.photark.services.gallery.GalleryListener;
@@ -9,5 +28,11 @@
     void clear();
 
     String[] search(String queryString);
+    
+    ImageTags getTags(String album, String imageName);
+    
+    void addTag(String album, String imageName, String tag);
+    
+    void removeTag(String album, String imageName, String tag);
 
 }
diff --git a/photark-search/src/main/java/org/apache/photark/search/services/impl/ImageTagsImpl.java b/photark-search/src/main/java/org/apache/photark/search/services/impl/ImageTagsImpl.java
new file mode 100644
index 0000000..3d68a37
--- /dev/null
+++ b/photark-search/src/main/java/org/apache/photark/search/services/impl/ImageTagsImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+
+package org.apache.photark.search.services.impl;
+
+import java.io.Serializable;
+
+import org.apache.photark.search.services.ImageTags;
+
+public class ImageTagsImpl implements ImageTags, Serializable, Cloneable {
+    
+    private static final long serialVersionUID = 541904835779808460L;
+
+    final private String id;
+    
+    final private String[] tags;
+    
+    public ImageTagsImpl(String id, String[] tags) {
+        this.id = id;
+        this.tags = tags;
+        
+    }
+
+    public String getImageID() {
+        return this.id;
+    }
+
+    public String[] getTags() {
+        return this.tags;
+    }
+
+}
diff --git a/photark-search/src/main/java/org/apache/photark/search/services/impl/SearchServiceImpl.java b/photark-search/src/main/java/org/apache/photark/search/services/impl/SearchServiceImpl.java
index ddb61c5..41d901a 100644
--- a/photark-search/src/main/java/org/apache/photark/search/services/impl/SearchServiceImpl.java
+++ b/photark-search/src/main/java/org/apache/photark/search/services/impl/SearchServiceImpl.java
@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.apache.photark.search.services.impl;
 
 import java.io.File;
@@ -5,28 +24,35 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.document.DateTools;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Fieldable;
 import org.apache.lucene.document.DateTools.Resolution;
 import org.apache.lucene.document.Field.Index;
 import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.index.CorruptIndexException;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermEnum;
 import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
 import org.apache.lucene.queryParser.ParseException;
 import org.apache.lucene.queryParser.QueryParser;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
 import org.apache.photark.Image;
-import org.apache.photark.ImageMetadata;
+import org.apache.photark.search.services.ImageTags;
 import org.apache.photark.search.services.SearchService;
 import org.oasisopen.sca.annotation.AllowsPassByReference;
 import org.oasisopen.sca.annotation.Destroy;
@@ -35,17 +61,17 @@
 import org.oasisopen.sca.annotation.Scope;
 
 // TODO: review actions when index is corrupted
+@Scope("COMPOSITE")
 public class SearchServiceImpl implements SearchService {
 
-    // TODO: a more dynamic way to specify field configuration should be
-    // implemented
-    // later
     final static public String IMAGE_NAME_FIELD = "name";
-    
+
     final static public String IMAGE_ALBUM_FIELD = "album";
 
     final static public String IMAGE_DATE_POSTED_FIELD = "date_posted";
 
+    final static public String TAG_FIELD = "tag";
+
     final static private Set<String> INDEXED_METADATA = new HashSet<String>();
 
     static {
@@ -64,9 +90,9 @@
 
     private IndexWriter indexWriter;
 
-    private IndexSearcher indexSearcher;
-
-    private QueryParser queryParser = new QueryParser("tag", this.defaultAnalyzer);
+    private MultiFieldQueryParser queryParser =
+        new MultiFieldQueryParser(new String[] {TAG_FIELD, IMAGE_NAME_FIELD, IMAGE_ALBUM_FIELD}, this.defaultAnalyzer);
+    
     {
         this.queryParser.setAllowLeadingWildcard(true);
     }
@@ -74,11 +100,21 @@
     @Property
     public String indexDirectoryPath;
 
+    private IndexSearcher indexSearcher;
+
+    private Directory dir;
+
     @Init
     public void init() throws IOException {
-        FSDirectory dir = FSDirectory.getDirectory(new File(this.indexDirectoryPath));
+        this.dir = FSDirectory.getDirectory(new File(this.indexDirectoryPath));
         this.indexWriter = new IndexWriter(dir, this.defaultAnalyzer, MaxFieldLength.UNLIMITED);
-        this.indexSearcher = new IndexSearcher(dir);
+        this.indexSearcher = new IndexSearcher(this.dir);
+
+        TermEnum terms = this.indexSearcher.getIndexReader().terms();
+        System.out.println("termmmmmmmmmmmmmmmmmsssssss");
+        while (terms.next()) {
+            System.out.println(terms.term());
+        }
 
     }
 
@@ -98,7 +134,7 @@
 
         try {
             Query query = this.queryParser.parse(queryString);
-            TopDocs topDocs = this.indexSearcher.search(query, 100);
+            TopDocs topDocs = this.indexSearcher.search(query, 10000);
 
             if (topDocs.scoreDocs.length > 0) {
                 ArrayList<String> pictureNames = new ArrayList<String>(topDocs.scoreDocs.length);
@@ -107,7 +143,7 @@
                     Document doc = this.indexSearcher.doc(scoreDoc.doc);
                     pictureNames.add(doc.get(IMAGE_ALBUM_FIELD) + '/' + doc.get(IMAGE_NAME_FIELD));
                 }
-                
+
                 return pictureNames.toArray(new String[0]);
 
             }
@@ -129,10 +165,12 @@
         try {
             Term idTerm = getImageIDTerm(albumName, image);
             Document doc = createDocument(idTerm, albumName, image);
-            this.indexWriter.updateDocument(idTerm, doc);
-            
+            this.indexWriter.deleteDocuments(idTerm);
+            this.indexWriter.addDocument(doc);
+
             this.indexWriter.commit();
-            
+            reopenIndexSearcher();
+
         } catch (Exception e) {
             // create a new exception type for this
             throw new RuntimeException(e);
@@ -140,6 +178,17 @@
 
     }
 
+    private void reopenIndexSearcher() throws CorruptIndexException, IOException {
+
+        try {
+            this.indexSearcher.close();
+        } catch (IOException e) {
+        }
+
+        this.indexSearcher = new IndexSearcher(this.dir);
+
+    }
+
     private Document createDocument(Term id, String albumName, Image image) {
         Document doc = new Document();
 
@@ -150,18 +199,19 @@
                                                                           Resolution.MINUTE), Store.YES,
                           Index.NOT_ANALYZED));
 
-//        for (ImageMetadata metadata : image.getImageMetadata()) {
-//
-//            if (INDEXED_METADATA.contains(metadata.getKey())) {
-//
-//                // TODO: the field should be extracted from a configuration map
-//                // instead of using the original metadata key
-//                doc.add(new Field(metadata.getKey().replace(" ", "_"), metadata.getValue(), Store.YES,
-//                                  Index.NOT_ANALYZED));
-//
-//            }
-//
-//        }
+        // for (ImageMetadata metadata : image.getImageMetadata()) {
+        //
+        // if (INDEXED_METADATA.contains(metadata.getKey())) {
+        //
+        // // TODO: the field should be extracted from a configuration map
+        // // instead of using the original metadata key
+        // doc.add(new Field(metadata.getKey().replace(" ", "_"),
+        // metadata.getValue(), Store.YES,
+        // Index.NOT_ANALYZED));
+        //
+        // }
+        //
+        // }
 
         return doc;
 
@@ -197,4 +247,99 @@
         return new Term("id", albumName + '/' + image.getName());
     }
 
+    private static Term getImageIDTerm(String albumName, String imageName) {
+        return new Term("id", albumName + '/' + imageName);
+    }
+
+    public void addTag(String album, String imageName, String tag) {
+        Term pictureID = getImageIDTerm(album, imageName);
+
+        try {
+            TopDocs docs = this.indexSearcher.search(new TermQuery(pictureID), 1);
+
+            if (docs.scoreDocs.length < 1) {
+                throw new IllegalArgumentException("picture not found: " + pictureID.text());
+            }
+
+            Document doc = this.indexSearcher.doc(docs.scoreDocs[0].doc);
+            Field[] tagFields = doc.getFields(TAG_FIELD);
+
+            for (Field tagField : tagFields) {
+                
+                if (tagField.stringValue().equals(tag)) {
+                    return;
+                }
+                
+            }
+            
+            doc.add(new Field(TAG_FIELD, tag, Store.YES, Index.ANALYZED));
+
+            this.indexWriter.deleteDocuments(pictureID);
+            this.indexWriter.addDocument(doc);
+            this.indexWriter.commit();
+
+            reopenIndexSearcher();
+
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    public void removeTag(String album, String imageName, String tag) {
+        Term pictureID = getImageIDTerm(album, imageName);
+
+        try {
+            TopDocs docs = this.indexSearcher.search(new TermQuery(pictureID), 1);
+
+            if (docs.scoreDocs.length < 1) {
+                throw new IllegalArgumentException("picture not found: " + pictureID.text());
+            }
+
+            Document doc = this.indexSearcher.doc(docs.scoreDocs[0].doc);
+            List<?> fieldList = doc.getFields();
+            Document newDoc = new Document();
+
+            for (Object obj : fieldList) {
+                Fieldable field = (Fieldable)obj;
+
+                if (!field.name().equals(TAG_FIELD) || !field.stringValue().equals(tag)) {
+                    newDoc.add(field);
+                }
+
+            }
+
+            newDoc.setBoost(doc.getBoost());
+
+            this.indexWriter.deleteDocuments(pictureID);
+            this.indexWriter.addDocument(newDoc);
+            this.indexWriter.commit();
+
+            reopenIndexSearcher();
+
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    public ImageTags getTags(String album, String imageName) {
+        Term pictureID = getImageIDTerm(album, imageName);
+
+        try {
+            TopDocs topDocs = this.indexSearcher.search(new TermQuery(pictureID), 1);
+
+            if (topDocs.scoreDocs.length > 0) {
+                Document doc = this.indexSearcher.doc(topDocs.scoreDocs[0].doc);
+                return new ImageTagsImpl(pictureID.text(), doc.getValues(TAG_FIELD));
+
+            }
+
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        return new ImageTagsImpl(pictureID.text(), new String[0]);
+
+    }
 }
diff --git a/photark-ui/src/main/webapp/gallery.html b/photark-ui/src/main/webapp/gallery.html
index 5f7e881..71a4ab6 100644
--- a/photark-ui/src/main/webapp/gallery.html
+++ b/photark-ui/src/main/webapp/gallery.html
@@ -97,10 +97,23 @@
 
       <table style="margin-left:auto; margin-right:auto;width:720px;" border="0" cellspacing="0" cellpadding="0">
         <tr>
+        	<td align="left"> 
+        		<table  style="margin-left:auto; margin-right:auto;width:150px;" border="0"
+			                cellspacing="0" cellpadding="1">
+			         </table>
+        	</td>
             <td align="center"> <!-- Image without original -->
                 <img id="albumImage" src="images/space.gif" class="slideImage" width="720" height="540" ondragstart="return false"
                      onselectstart="return false" oncontextmenu="return false" galleryimg="no" usemap="#imagemap" alt=""/>
             </td>
+             <td align="left"> 
+                <div id="tags">
+    	 			<input id="addtag-input" style="width:20;" type="text" style="display:inline;" value="" size="20" alt="add tag..."/><input type="button" onclick="addTag();" style="display:inline;" value="Add Tag"/>
+			         <table id='tableTags' style="margin-left:auto; margin-right:auto;width:150;" border="0"
+			                cellspacing="0" cellpadding="1">
+			         </table>
+			    </div>
+            </td>
         </tr>
       </table>
     </div>
diff --git a/photark-ui/src/main/webapp/js/gallery.js b/photark-ui/src/main/webapp/js/gallery.js
index a257c34..90d8557 100644
--- a/photark-ui/src/main/webapp/js/gallery.js
+++ b/photark-ui/src/main/webapp/js/gallery.js
@@ -34,6 +34,7 @@
 var searchService;
 var galleryName;
 var galleryAlbums;
+var albumTags;
 var albumCovers = new Array();
 var albumName;
 var albumItems;
@@ -44,6 +45,7 @@
 var userId;
 var SECURITY_TOKEN;
 var permissions = new Array();
+var albumImageToBeLoaded = null;
 
 dojo.addOnLoad(function() {
     dojo.require("dojo._base.xhr");
@@ -83,7 +85,6 @@
 
 function initGallery() {
     try {
-     //   gallery.getAlbums().addCallback(gallery_getAlbumsResponse); getAlbumsToUser
         gallery.getAlbumsToUser(SECURITY_TOKEN).addCallback(gallery_getAlbumsResponse);
     } catch(exception) {
         alert(exception);
@@ -157,22 +158,37 @@
 
 	var table=document.getElementById('tableSearch');
 	deleteTableRows(table);
-    var lastRow = 0;//table.rows.length;
-    for (i = 0; i < items.length; i++) {
-        var row = table.insertRow(lastRow++);
-        var column = row.insertCell(0);
+    
+    for (i = 0; i < items.length / 5; i++) {
+ 	   var row = table.insertRow(i*2);
+	    for (j = 0; j < 5 && i*5 + j < items.length ; j++) {
+	        
+	        var column = row.insertCell(j);
+	        var aux = items[i*5 + j].split('/', 2);
+	        var albumName = aux[0];
+	        var imageName = aux[1];
+	
+	        var img = document.createElement("img");
+	        img.src = "";
+	        img['class'] = "slideImage";
+	        img.width=10;
+	        img.height=10;
+	        img.ondragstart = function () { return false; };
+	        img.onselectstart = function () { return false; };
+	        img.onconstextmenu = function () { return false; };
+	        img.alt = items[i*5 + j];
+	        var a = document.createElement("a");
+	        a.href = "javascript:initializeAlbum('" + albumName + "', '" + imageName + "')";
+	        a.appendChild(img);
+	        column.appendChild(a);
 
-        var img = document.createElement("img");
-        img.src = "";
-        //img.class = "slideImage";
-        img.alt = items[i];
-        var a = document.createElement("a");
-        a.href = window.location.href + "gallery/" + items[i];
-        a.appendChild(img);
-        column.appendChild(a);
-        row = table.insertRow(lastRow++);
-        column = row.insertCell(0)
-        column.innerHTML = "<img src=\"images/space.gif\" class=\"slideImage\" width=\"10\" height=\"10\" ondragstart=\"return false\" onselectstart=\"return false\" oncontextmenu=\"return false\" galleryimg=\"no\" usemap=\"#imagemap\" alt=\"\">";
+        
+  		 }
+  	 
+  	 	 row = table.insertRow(i*2+1);
+	     column = row.insertCell(0)
+	     column.innerHTML = "<img src=\"images/space.gif\" class=\"slideImage\" width=\"10\" height=\"10\" ondragstart=\"return false\" onselectstart=\"return false\" oncontextmenu=\"return false\" galleryimg=\"no\" usemap=\"#imagemap\" alt=\"\">";
+   
    }
 
    displaySearchResults();
@@ -185,6 +201,24 @@
 	}
 }
 
+function addTag() {
+	var tag = document.getElementById("addtag-input").value;
+	searchService.addTag(albumName, albumItems[albumPos], tag);
+	
+	var imageID = albumName + '/' + albumItems[albumPos];
+	var imageTags = albumTags[imageID];
+	
+	if (imageTags == null) {
+		imageTags = new Array();
+		albumTags[imageID] = imageTags;
+		
+	}
+	
+	imageTags.push(tag);
+	showTags(imageTags);
+	 
+}
+
 function initializeGallery() {
     var table=document.getElementById('tableGallery');
     var lastRow = table.rows.length;
@@ -198,7 +232,7 @@
             var img = document.createElement("img");
             img.src = window.location.href + "gallery/"+ albumName +"/" + albumCovers[i];
             var img_html = "<img src=" + img.src + " class=\"slideImage\" width=180px ondragstart=\"return false\" onselectstart=\"return false\" oncontextmenu=\"return false\" galleryimg=\"no\" usemap=\"#imagemap\" alt=\"\"/>";
-            var html = "<a href=\"javascript:initializeAlbum('" + albumName + "')\">" + img_html + "</a>";
+            var html = "<a href=\"javascript:initializeAlbum('" + albumName + "', null)\">" + img_html + "</a>";
             column.innerHTML = html;
 
             column = row.insertCell(1);
@@ -227,16 +261,106 @@
     setVisibility('album',false);
 }
 
-function initializeAlbum(albumName) {
+function initializeAlbum(albumName,imageName) {
     try {
         this.albumName = albumName;
-       // gallery.getAlbumPictures(albumName).addCallback(gallery_getAlbumPicturesResponse);
-          gallery.getAlbumPicturesToUser(albumName,SECURITY_TOKEN).addCallback(gallery_getAlbumPicturesResponse);
+   	   	albumImageToBeLoaded = imageName;
+   	   	albumTags = new Array();
+   	   	gallery.getAlbumPicturesToUser(albumName,SECURITY_TOKEN).addCallback(gallery_getAlbumPicturesResponse);
+    	  
     } catch(exception) {
         alert(e);
     }
 }
 
+function loadTags(albumPos) {
+	var imageTags = albumTags[albumName + '/' + albumItems[albumPos]];
+	
+	if (imageTags == null) {
+		searchService.getTags(albumName, albumItems[albumPos]).addCallback(getTagsResponse);
+		
+	} else {
+		showTags(imageTags);
+	}
+	
+}
+
+function getTagsResponse(items, exception) {
+    if(exception) {
+        alert(exception.msg);
+        return;
+    }
+    
+    albumTags[items.imageID] = items.tags;
+    
+    if (albumName + '/'  + albumItems[albumPos] == items.imageID) {
+    	showTags(items.tags);
+    }
+    
+}
+
+function showTags(tags) {
+	var table=document.getElementById('tableTags');
+	var textField = document.getElementById('addtag-input');
+	textField.value = "";
+	deleteTableRows(table);
+
+	var lastRow = 0;
+	for (i = 0; i < tags.length; i++) {
+    	var row = table.insertRow(lastRow++);
+        var column = row.insertCell(0);
+
+		var divElement = document.createElement("div");
+        var tagElement = document.createElement("a");
+        tagElement.href = "javascript:executeSearch('tag:" + tags[i] + "')"; 
+        tagElement.innerHTML = tags[i];
+        divElement.id = tags[i];
+        divElement.style.display = 'inline';
+        divElement.onmouseover = function (evt) {
+        	this.removeButton.style.display = 'inline';
+            this.removeButton.style.visibility = 'visible';
+        };
+        divElement.onmouseout = function (evt) {
+        	this.removeButton.style.display = 'none';
+            this.removeButton.style.visibility = 'hidden';
+        };
+        
+		divElement.appendChild(tagElement);
+		
+        var removeElement = document.createElement("a");
+        removeElement['class'] = "removeTag";
+        removeElement.href = "javascript:removeTag('" + tags[i] + "')";
+        removeElement.id = "removeTag_" + tags[i];
+        removeElement.innerHTML = 'remove';
+        removeElement.style.display = 'none';
+        removeElement.style.visibility = 'hidden';
+        divElement.removeButton = removeElement;
+        divElement.appendChild(removeElement);
+        
+        column.appendChild(divElement);
+        
+     }
+     
+     setVisibility('tableTags',true);
+    
+}
+
+function removeTag(tag) {
+	searchService.removeTag(albumName, albumItems[albumPos], tag);
+	var table=document.getElementById('tableTags');
+	
+	for (i = 0 ; i < table.rows.length ; i++) {
+	
+		if (table.rows[i].cells[0].firstChild.id == tag) {
+			table.deleteRow(i);
+			break;
+		}
+		
+	}
+	
+	
+}
+
 function gallery_getAlbumPicturesResponse(items, exception) {
     if(exception) {
        // alert(exception.msg);
@@ -246,6 +370,21 @@
     }
     albumItems = items;
     albumPos = 0;
+    
+    if (albumImageToBeLoaded != null) {
+    
+    	for (i = 0 ; i < items.length ; i++) {
+    	
+    		if (items[i] == albumImageToBeLoaded) {
+    			albumPos = i;
+    			albumImageToBeLoaded = null;
+    			
+    		}	
+    	
+    	}
+    	
+    }
+    
     showAlbum();
 }
 
@@ -270,6 +409,7 @@
         document.getElementById("albumImage").height=this.height;
     }
     img.src = window.location.href + "gallery/"+ this.albumName +"/" + albumItems[albumPos];
+    loadTags(albumPos);
     return false;
 }
 
@@ -288,7 +428,7 @@
 }
 
 function setVisibility(divId, visible) {
-    //valid values { visible, hidden }
+ 	 //valid values { visible, hidden }
     if (document.getElementById) {
         var element = document.getElementById(divId)
         if(visible) {
@@ -339,8 +479,12 @@
 }
 
 function search(){
-		var query = document.getElementById("search-input").value;
-		searchService.search(query).addCallback(searchResponse);
+	var query = document.getElementById("search-input").value;
+	executeSearch(query);
+}
+
+function executeSearch(query) {
+	searchService.search(query).addCallback(searchResponse);
 }
 
 function onSlideShow(){
diff --git a/photark/src/main/java/org/apache/photark/services/album/Album.java b/photark/src/main/java/org/apache/photark/services/album/Album.java
index 387ea2e..93a824f 100644
--- a/photark/src/main/java/org/apache/photark/services/album/Album.java
+++ b/photark/src/main/java/org/apache/photark/services/album/Album.java
@@ -48,4 +48,5 @@
     void addOwner(String owner);
 
     String[] getOwners();
+
 }
\ No newline at end of file
diff --git a/photark/src/main/java/org/apache/photark/services/album/AlbumAgregator.java b/photark/src/main/java/org/apache/photark/services/album/AlbumAgregator.java
index f6a3178..e8fcdaa 100644
--- a/photark/src/main/java/org/apache/photark/services/album/AlbumAgregator.java
+++ b/photark/src/main/java/org/apache/photark/services/album/AlbumAgregator.java
@@ -135,4 +135,5 @@
     public void deletePicture(String picture){
 
     }
+    
 }
diff --git a/photark/src/test/java/org/apache/photark/services/gallery/filesystem/ImageMetadataScannerTestCase.java b/photark/src/test/java/org/apache/photark/services/gallery/filesystem/ImageMetadataScannerTestCase.java
index 7a43b6f..7fed20e 100644
--- a/photark/src/test/java/org/apache/photark/services/gallery/filesystem/ImageMetadataScannerTestCase.java
+++ b/photark/src/test/java/org/apache/photark/services/gallery/filesystem/ImageMetadataScannerTestCase.java
@@ -22,6 +22,7 @@
 import java.io.File;
 import java.net.URISyntaxException;
 import java.util.Date;
+import java.util.List;
 
 import org.apache.photark.Image;
 import org.junit.Assert;
@@ -40,5 +41,7 @@
             System.out.println(">>" + metadata.getKey() + "\t" + metadata.getValue());
         }
         */
+        
     }
+    
 }