/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.admingui.common.handlers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.prefs.Preferences;
import org.glassfish.admingui.common.util.GuiUtil;
/**
* <p> This class provides API support for managing {@link Tag}s.</p>
*/
public class TagSupport implements Serializable{
/**
* <p> This method adds a tag for the given <code>tagViewId</code> and
* <code>user</code>. If the tagViewId is new, it will store the
* given <code>displayName</code>.</p>
*/
public static void addTag(String tagName, String tagViewId, String displayName, String user) {
// Normalize tagViewId, but not Tag name (only normalize the Tag-name
// when getting/storing it as a key to a Map)
if (tagName == null) {
throw new IllegalArgumentException("You cannot add a tag with a null name!");
}
if (tagViewId == null) {
throw new IllegalArgumentException("You cannot tag a page which does not have an ID!");
}
tagViewId = normalizeTagViewId(tagViewId);
// Give user a reasonable value if it is null
if (user == null) {
// user = getExternalContext().getUserPrincipal().getName();
user = "anonymous";
}
// See if we already have a Tag for this...
Tag theTag = null;
List<Tag> tags = queryTags(tagName, tagViewId, (String) null);
// Check to see if this is already tagged...
if ((tags != null) && (tags.size() > 0)) {
// There should only be 1 for a unique tagName/tagViewId...
theTag = tags.get(0);
if (theTag.containsUser(user)) {
// Already tagged by this user... nothing to do
return;
}
// Add a new user to the existing tag...
theTag.addUser(user);
} else {
// Create a new one...
theTag = new Tag(tagName, tagViewId, displayName, user);
}
// Store it...
setTag(theTag);
}
/**
* <p> This method stores a single {@link Tag}.</p>
*/
private static void setTag(Tag tag) {
// First get the 2 Tags Maps...
Map<String, List<Tag>>[] maps = getTagMaps();
if ((tag.getUsers() != null) && (tag.getUsers().size() > 0)) {
// Set in the by-tag-name Map...
setTagInMap(maps[TAG_NAME_MAP_IDX],
normalizeTagName(tag.getTagName()), tag);
// Set in the by-page Map...
setTagInMap(maps[PAGE_MAP_IDX], tag.getTagViewId(), tag);
} else {
// Delete mode...
// Tags by name map...
List<Tag> tags = maps[TAG_NAME_MAP_IDX].get(
normalizeTagName(tag.getTagName()));
tags.remove(tag);
// Tags by viewId
tags = maps[PAGE_MAP_IDX].get(tag.getTagViewId());
tags.remove(tag);
}
// Save the data...
setTagMaps(maps);
}
/**
* <p> This method sets a <code>Tag</code> in the given
* <code>Map</code> by the given <code>key</code>. The key is
* expected to be normalized already.</p>
*/
private static void setTagInMap(Map<String, List<Tag>> map, String key, Tag tag) {
List<Tag> tagList = map.get(key);
if (tagList != null) {
// We already have this Tag, see if we have a hit on the page too
// equals() compares tagName / page
int tagIdx = tagList.indexOf(tag);
if (tagIdx != -1) {
// Need to remove this tag so its doesn't exist 2x
// (it is important to update due to possible user changes)
tagList.remove(tagIdx);
}
} else {
// We need to create a List and add it to the Map
tagList = new ArrayList<Tag>(1);
map.put(key, tagList);
}
// Now just add tag to the List
tagList.add(tag);
}
/**
* <p> This method stores the given array of maps via the
* <code>Preferences API</code>.</p>
*/
private static void setTagMaps(Map<String, List<Tag>>[] maps) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
try {
// Prepare the data...
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(maps);
// Store it via the Preferences API...
Preferences prefs = Preferences.userRoot().node(BASE_NODE);
prefs.putByteArray(TAG_DATA_KEY, buf.toByteArray());
} catch (Exception ex) {
throw new RuntimeException("Unable to store preference!", ex);
}
// FIXME: I need to be able to store larger amounts of DATA!
// FIXME: I should make the data I'm storing smaller...
}
/**
* <p> This method accesses the Tag Map.</p>
*/
@SuppressWarnings("unchecked")
private static Map<String, List<Tag>>[] getTagMaps() {
Map<String, List<Tag>>[] result = null;
Preferences prefs = Preferences.userRoot().node(BASE_NODE);
byte tagData[] = prefs.getByteArray(TAG_DATA_KEY, null);
if (tagData == null) {
// Initialize it...
result = (Map<String, List<Tag>>[]) new Map[] {
new HashMap<String, List<Tag>>(), // By Tag Name
new HashMap<String, List<Tag>>() // By Page ID
};
} else {
try {
ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(tagData));
result = (Map<String, List<Tag>>[]) stream.readObject();
} catch (java.io.InvalidClassException ex) {
throw new IllegalStateException(
"Perhaps you have an old Tag storage format?", ex);
} catch (Exception ex) {
throw new IllegalStateException(
"Unable to read Tag information!", ex);
}
}
return result;
}
/**
* <p> This method searches the tags based on the given criteria. Any of
* the criteria may be null, meaning not to filter by that
* criterion.</p>
*
* @param tagName Name of the tag to find, not required. May be null
* if tagViewId, or user is supplied -- or to return
* all tags.
* @param tagViewId Unique id for the page to search for tags. May be
* null (for all pages).
* @param user User id. From getUserPrincipal() in some cases?
* Allow seeing tags created by specific users. May be
* null (for all users).
*
* @return Returns the search results, or <code>null</code> if nothing
* is found.
*/
public static List<Tag> queryTags(String tagName, String tagViewId, String user) {
Map<String, List<Tag>>[] maps = getTagMaps();
List<Tag> results = null;
Tag testTag = null;
// Make sure this is normalized...
tagViewId = normalizeTagViewId(tagViewId);
// Check out we should search...
if (tagName != null) {
// We'll search first by TagName
results = maps[TAG_NAME_MAP_IDX].get(normalizeTagName(tagName));
if (results == null) {
return null;
}
if (tagViewId != null) {
// Now filter by tagViewId...
Iterator<Tag> it = results.iterator();
while (it.hasNext()) {
testTag = it.next();
if (!testTag.getTagViewId().equals(tagViewId)) {
// Is not for the page, remove it from the result set
it.remove();
}
}
}
} else if (tagViewId != null) {
// Search by tagViewId and maybe user (if !null)
results = maps[PAGE_MAP_IDX].get(tagViewId);
} else {
// Include everything...
results = new ArrayList<Tag>();
Map<String, List<Tag>> map = maps[TAG_NAME_MAP_IDX];
for(Map.Entry<String,List<Tag>> e : map.entrySet()){
results.addAll(e.getValue());
}
}
// Finally filter out unwanted users, if applicable...
if ((user != null) && (results != null)) {
Iterator<Tag> it = results.iterator();
while (it.hasNext()) {
testTag = it.next();
if (!testTag.containsUser(user)) {
// Does not contain the user, remove it from the result set
it.remove();
}
}
}
// Make sure we have something to return...
if ((results != null) && (results.size() == 0)) {
// We don't have anything to return!
results = null;
}
// Return the results of the search...
return results;
}
/**
* <p> This method removes a Tag.</p>
*/
public static void removeTag(String tagName, String tagViewId, String user) {
if ((tagName == null) || (tagViewId == null) || (user == null)) {
throw new IllegalArgumentException("To remove a Tag, you "
+ "must specify the tagName, tagViewId, and tag owner!");
}
tagName = normalizeTagName(tagName);
tagViewId = normalizeTagViewId(tagViewId);
// Find it...
List<Tag> results = TagSupport.queryTags(tagName, tagViewId, user);
// Should be at most 1 match, however, there may be multiple users.
if (results.size() > 0) {
// Remove the 1st (and only) Tag...
Tag targetTag = results.get(0);
targetTag.removeUser(user);
// Save tag (if no more users, it will be deleted)
setTag(targetTag);
}
}
/**
* <p> This method ensure that tags are compared w/o taking into account
* case or whitespace.</p>
*/
private static String normalizeTagName(String tagName) {
if (tagName == null) {
throw new IllegalArgumentException("Tag name cannot be null!");
}
return tagName.replaceAll("\\s", "").toLowerCase(GuiUtil.guiLocale);
}
/**
* <p> tagViewId's are expected to be context relative paths. These
* tagViewId's may include QUERY_STRING parameters if they are used to
* determine the content of the page. This means, we must take extra
* special care to normalize the order of important QUERY_STRING
* properties. We also need to ensure leading (or intermediate) '/'
* characters are normalized, and that the extension is normalized
* (this method will ensure all tagViewId's end in .jsf).</p>
*
* <p> Case will be preserved.</p>
*/
public static String normalizeTagViewId(String tagViewId) {
if (tagViewId == null) {
return null;
}
// Split off the base/QS...
tagViewId = tagViewId.trim();
int idx = tagViewId.indexOf('?');
String baseName = (idx == -1) ? tagViewId : tagViewId.substring(0, idx);
String queryString = (idx == -1) ? "" : tagViewId.substring(idx+1);
// Get rid of leading and extra '/' characters...
StringTokenizer tokenizer = new StringTokenizer(baseName, "/");
StringBuilder builder = new StringBuilder(tokenizer.nextToken());
while (tokenizer.hasMoreTokens()) {
builder.append('/');
builder.append(tokenizer.nextToken());
}
baseName = builder.toString();
// Normalize Extension...
if (!baseName.endsWith(".jsf")) {
idx = baseName.lastIndexOf('.');
if (idx != -1) {
// Replace existing extension with .jsf...
baseName = baseName.substring(0, idx) + ".jsf";
}
}
// Split & sort the NVPs...
if (queryString.length() > 0) {
tokenizer = new StringTokenizer(queryString, "&");
List<String> nvps = new ArrayList<String>();
while (tokenizer.hasMoreTokens()) {
nvps.add(tokenizer.nextToken());
}
// Sort them...
Collections.sort(nvps);
// Rebuild the QS, now ordered...
builder = new StringBuilder(nvps.remove(0));
for (String nvp : nvps) {
// Add the rest...
builder.append('&');
builder.append(nvp);
}
queryString = builder.toString();
}
// Reassemble the String...
tagViewId = baseName
+ ((queryString.length() > 0) ? ("?" + queryString) : "");
return tagViewId;
}
/**
*
*/
public static void main(String args[]) {
/*
// Write
ByteArrayOutputStream buf = new ByteArrayOutputStream();
byte[] data = null;
try {
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject("This is a test!");
data = buf.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
System.exit(-1);
}
// Read
//BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
try {
ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
System.out.println("Line == " + stream.readObject());
} catch (Exception ex) {
ex.printStackTrace();
}
*/
TagSupport.addTag("bar", "deploy.jsf?a=z&c=e", "Display Name", "jane");
TagSupport.addTag("bar", "/index.jsf?a=z&c=f", "Display Name", "jane");
TagSupport.addTag("bar", "/index.jsf?a=z&c=e", "Display Name", "jane");
TagSupport.addTag("bar", "deploy.jsf?a=z&c=g", "Display Name", "jane");
TagSupport.addTag("bar", "indexdeployjsf?a=z&c=h", "Display Name", "jack");
TagSupport.addTag("bar", "/index.jsf?deploy=z&c=i", "Display Name", "jack");
TagSupport.addTag("foo", "/index.jsf?a=z&c=j", "Display Name", "jack");
TagSupport.addTag("bar", "/index.jsf?a=z&c=e", "Display Name", "jane");
TagSupport.addTag("bar", "/index.jsf?a=z&c=f", "Display Name", "jane");
TagSupport.addTag("bar", "deploy.jsf?a=z&c=e", "Display Name", "jane");
TagSupport.addTag("bar", "/index.jsf?a=z&c=g", "Display Name", "jane");
TagSupport.addTag("bar", "deploy.jsf?a=z&c=h", "Display Name", "jack");
TagSupport.addTag("bar", "/index.jsf?a=z&c=i", "Display Name", "jack");
TagSupport.addTag("foo", "deploy.jsf?a=z&c=j", "Display Name", "jack");
TagSupport.addTag("foo", "deploy.jsf?a=z&c=k", "Display Name", "jack");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=l", "Display Name", "jack");
TagSupport.addTag("foo", "/index.jsf?a=z&c=m", "Display Name", "jack");
TagSupport.addTag("foo", "deploy.jsf?a=z&c=k", "Display Name", "jack");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=l", "Display Name", "jack");
TagSupport.addTag("foo", "deploy.jsf?a=z&c=m", "Display Name", "jack");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=n", "Display Name", "jack");
TagSupport.addTag("bat", "/index.jsf?a=z&c=o", "Display Name", "bill");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=p", "Display Name", "bill");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=q", "Display Name", "bill");
TagSupport.addTag("foo", "deploy.jsf?a=z&c=s", "Display Name", "bill");
TagSupport.addTag("foo", "deploy.jsf?a=z&c=t", "Display Name", "jane");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=u", "Display Name", "jane");
TagSupport.addTag("bat", "deploy.jsf?a=z&c=v", "Display Name", "jane");
List<Tag> results = TagSupport.queryTags(null, null, null);
// List<Tag> results = TagSupport.queryTags(null, null, "anonymous");
// List<Tag> results = TagSupport.queryTags(null, null, "admin");
// List<Tag> results = TagSupport.queryTags("bar", null, null);
// List<Tag> results = TagSupport.queryTags("foo", null, null);
// List<Tag> results = TagSupport.queryTags("foo", null, "anonymous");
// List<Tag> results = TagSupport.queryTags("foo", null, "admin");
// List<Tag> results = TagSupport.queryTags("foo", "/index.jsf?a=b&c=d", "admin");
// List<Tag> results = TagSupport.queryTags("foo", "/index.jsf?a=b&c=e", "anonymous");
// List<Tag> results = TagSupport.queryTags("bar", "/index.jsf?a=b&c=e", "admin");
// List<Tag> results = TagSupport.queryTags(null, "/index.jsf?a=b&c=e", "admin");
// List<Tag> results = TagSupport.queryTags(null, "/index.jsf?a=b&c=e", "anonymous");
// List<Tag> results = TagSupport.queryTags("foo", "/index.jsf?a=b&c=d", null);
// List<Tag> results = TagSupport.queryTags("bar", "/index.jsf?a=b&c=e", null);
// List<Tag> results = TagSupport.queryTags(null, "/index.jsf?a=b&c=d", null);
if (results != null) {
for (Tag tag : results) {
System.out.println("Found==> " + tag);
}
}
}
/**
* The array index for the Map of Tags by tag name.
*/
private static final int TAG_NAME_MAP_IDX = 0;
/**
* The array index for the Map of Tags by page.
*/
private static final int PAGE_MAP_IDX = 1;
/**
* <p> This is the base <em>Preferences</em> node for tags.</p>
*/
public static final String BASE_NODE = "/glassfish/tags";
/**
* <p> This is the key used to access the tag data under the
* {@link #BASE_NODE} <code>Java Preferences API</code> node.</p>
*/
public static final String TAG_DATA_KEY = "tagData";
}