From c863b9a5769cb5eda9a3cdf38255512ed9d642ff Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Mon, 22 Jul 2013 15:28:50 -0400
Subject: [PATCH] Added skeleton for token services.

A skeleton for token service and the clients has been added. Currently
it's storing the database in memory. The actual implementation using
LDAP database will be added after the TPS configuration code is ready.

Ticket #652
---
 .../com/netscape/certsrv/token/TokenClient.java    |  67 ++++++
 .../netscape/certsrv/token/TokenCollection.java    |  65 ++++++
 .../src/com/netscape/certsrv/token/TokenData.java  | 219 ++++++++++++++++++
 .../netscape/certsrv/token/TokenModifyRequest.java | 152 +++++++++++++
 .../com/netscape/certsrv/token/TokenResource.java  |  80 +++++++
 .../src/com/netscape/certsrv/tps/TPSClient.java    |  42 ++++
 .../src/com/netscape/cmstools/cli/CLI.java         |  15 ++
 .../src/com/netscape/cmstools/cli/MainCLI.java     |   4 +-
 .../src/com/netscape/cmstools/cli/TPSCLI.java      | 109 +++++++++
 .../com/netscape/cmstools/token/TokenAddCLI.java   |  81 +++++++
 .../src/com/netscape/cmstools/token/TokenCLI.java  | 136 ++++++++++++
 .../com/netscape/cmstools/token/TokenFindCLI.java  |  94 ++++++++
 .../netscape/cmstools/token/TokenModifyCLI.java    |  81 +++++++
 .../netscape/cmstools/token/TokenRemoveCLI.java    |  53 +++++
 .../com/netscape/cmstools/token/TokenShowCLI.java  |  56 +++++
 base/tps/java/CMakeLists.txt                       |   9 +-
 .../org/dogtagpki/tps/server/TPSApplication.java   |   5 +
 .../{TPSAuthority.java => TPSSubsystem.java}       |  15 +-
 .../org/dogtagpki/tps/token/TokenDatabase.java     |  76 +++++++
 .../java/org/dogtagpki/tps/token/TokenRecord.java  | 188 ++++++++++++++++
 .../java/org/dogtagpki/tps/token/TokenService.java | 245 +++++++++++++++++++++
 base/tps/shared/conf/CS.cfg.in                     |   2 +-
 22 files changed, 1790 insertions(+), 4 deletions(-)
 create mode 100644 base/common/src/com/netscape/certsrv/token/TokenClient.java
 create mode 100644 base/common/src/com/netscape/certsrv/token/TokenCollection.java
 create mode 100644 base/common/src/com/netscape/certsrv/token/TokenData.java
 create mode 100644 base/common/src/com/netscape/certsrv/token/TokenModifyRequest.java
 create mode 100644 base/common/src/com/netscape/certsrv/token/TokenResource.java
 create mode 100644 base/common/src/com/netscape/certsrv/tps/TPSClient.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/cli/TPSCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/token/TokenAddCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/token/TokenCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/token/TokenFindCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/token/TokenModifyCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/token/TokenRemoveCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/token/TokenShowCLI.java
 rename base/tps/java/org/dogtagpki/tps/server/{TPSAuthority.java => TPSSubsystem.java} (86%)
 create mode 100644 base/tps/java/org/dogtagpki/tps/token/TokenDatabase.java
 create mode 100644 base/tps/java/org/dogtagpki/tps/token/TokenRecord.java
 create mode 100644 base/tps/java/org/dogtagpki/tps/token/TokenService.java

diff --git a/base/common/src/com/netscape/certsrv/token/TokenClient.java b/base/common/src/com/netscape/certsrv/token/TokenClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..d14fb9e2e4cad86c194b1c406ed892fba0e4d843
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/token/TokenClient.java
@@ -0,0 +1,67 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//GNU General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2013 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+package com.netscape.certsrv.token;
+
+import java.net.URISyntaxException;
+
+import org.jboss.resteasy.client.ClientResponse;
+
+import com.netscape.certsrv.client.ClientConfig;
+import com.netscape.certsrv.client.PKIClient;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenClient {
+
+    public PKIClient client;
+    public TokenResource resource;
+
+    public TokenClient(ClientConfig config) throws URISyntaxException {
+        this(new PKIClient(config));
+    }
+
+    public TokenClient(PKIClient client) throws URISyntaxException {
+        this.client = client;
+        resource = client.createProxy(TokenResource.class);
+    }
+
+    public TokenCollection findTokens(Integer start, Integer size) {
+        return resource.findTokens(start, size);
+    }
+
+    public TokenData getToken(String tokenID) {
+        return resource.getToken(tokenID);
+    }
+
+    public TokenData addToken(TokenData tokenData) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<TokenData> response = (ClientResponse<TokenData>)resource.addToken(tokenData);
+        return client.getEntity(response);
+    }
+
+    public TokenData updateToken(String tokenID, TokenData tokenData) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<TokenData> response = (ClientResponse<TokenData>)resource.updateToken(tokenID, tokenData);
+        return client.getEntity(response);
+    }
+
+    public void removeToken(String tokenID) {
+        resource.removeToken(tokenID);
+    }
+}
diff --git a/base/common/src/com/netscape/certsrv/token/TokenCollection.java b/base/common/src/com/netscape/certsrv/token/TokenCollection.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5be9977ffdae53a6f19862c02662134aad8d8cc
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/token/TokenCollection.java
@@ -0,0 +1,65 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.certsrv.token;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+
+/**
+ * @author Endi S. Dewata
+ */
+@XmlRootElement(name="Tokens")
+public class TokenCollection {
+
+    Collection<TokenData> tokens = new ArrayList<TokenData>();
+    Collection<Link> links = new ArrayList<Link>();
+
+    @XmlElementRef
+    public Collection<TokenData> getTokens() {
+        return tokens;
+    }
+
+    public void setTokens(Collection<TokenData> tokens) {
+        this.tokens = tokens;
+    }
+
+    public void addToken(TokenData tokenData) {
+        tokens.add(tokenData);
+    }
+
+    @XmlElement(name="Link")
+    public Collection<Link> getLinks() {
+        return links;
+    }
+
+    public void setLink(Collection<Link> links) {
+        this.links = links;
+    }
+
+    public void addLink(Link link) {
+        links.add(link);
+    }
+}
diff --git a/base/common/src/com/netscape/certsrv/token/TokenData.java b/base/common/src/com/netscape/certsrv/token/TokenData.java
new file mode 100644
index 0000000000000000000000000000000000000000..9dc8a9cec03d0c2ff83c18487ef99b244be4d826
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/token/TokenData.java
@@ -0,0 +1,219 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.certsrv.token;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Date;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+/**
+ * @author Endi S. Dewata
+ */
+@XmlRootElement(name="Token")
+public class TokenData {
+
+    public static Marshaller marshaller;
+    public static Unmarshaller unmarshaller;
+
+    static {
+        try {
+            marshaller = JAXBContext.newInstance(TokenData.class).createMarshaller();
+            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            unmarshaller = JAXBContext.newInstance(TokenData.class).createUnmarshaller();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    String id;
+    String userID;
+    String status;
+    String reason;
+    String appletID;
+    String keyInfo;
+    Date createTimestamp;
+    Date modifyTimestamp;
+
+    Link link;
+
+    @XmlAttribute(name="id")
+    public String getID() {
+        return id;
+    }
+
+    public void setID(String id) {
+        this.id = id;
+    }
+
+    @XmlElement(name="UserID")
+    public String getUserID() {
+        return userID;
+    }
+
+    public void setUserID(String userID) {
+        this.userID = userID;
+    }
+
+    @XmlElement(name="Status")
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    @XmlElement(name="Reason")
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    @XmlElement(name="AppletID")
+    public String getAppletID() {
+        return appletID;
+    }
+
+    public void setAppletID(String appletID) {
+        this.appletID = appletID;
+    }
+
+    @XmlElement(name="KeyInfo")
+    public String getKeyInfo() {
+        return keyInfo;
+    }
+
+    public void setKeyInfo(String keyInfo) {
+        this.keyInfo = keyInfo;
+    }
+
+    @XmlElement(name="CreateTimestamp")
+    public Date getCreateTimestamp() {
+        return createTimestamp;
+    }
+
+    public void setCreateTimestamp(Date createTimestamp) {
+        this.createTimestamp = createTimestamp;
+    }
+
+    @XmlElement(name="ModifyTimestamp")
+    public Date getModifyTimestamp() {
+        return modifyTimestamp;
+    }
+
+    public void setModifyTimestamp(Date modifyTimestamp) {
+        this.modifyTimestamp = modifyTimestamp;
+    }
+
+    @XmlElement(name="Link")
+    public Link getLink() {
+        return link;
+    }
+
+    public void setLink(Link link) {
+        this.link = link;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((link == null) ? 0 : link.hashCode());
+        result = prime * result + ((userID == null) ? 0 : userID.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TokenData other = (TokenData) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (link == null) {
+            if (other.link != null)
+                return false;
+        } else if (!link.equals(other.link))
+            return false;
+        if (userID == null) {
+            if (other.userID != null)
+                return false;
+        } else if (!userID.equals(other.userID))
+            return false;
+        return true;
+    }
+
+    public String toString() {
+        try {
+            StringWriter sw = new StringWriter();
+            marshaller.marshal(this, sw);
+            return sw.toString();
+
+        } catch (Exception e) {
+            return super.toString();
+        }
+    }
+
+    public static TokenData valueOf(String string) throws Exception {
+        try {
+            return (TokenData)unmarshaller.unmarshal(new StringReader(string));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static void main(String args[]) throws Exception {
+
+        TokenData before = new TokenData();
+        before.setID("token1");
+        before.setUserID("user1");
+        before.setStatus("revoked");
+        before.setReason("lost");
+        before.setAppletID("APPLET1234");
+        before.setKeyInfo("key info");
+        before.setCreateTimestamp(new Date());
+        before.setModifyTimestamp(new Date());
+
+        String string = before.toString();
+        System.out.println(string);
+
+        TokenData after = TokenData.valueOf(string);
+        System.out.println(before.equals(after));
+    }
+}
diff --git a/base/common/src/com/netscape/certsrv/token/TokenModifyRequest.java b/base/common/src/com/netscape/certsrv/token/TokenModifyRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf0c4b6eae857659a61df36d45239a286f7faa61
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/token/TokenModifyRequest.java
@@ -0,0 +1,152 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.certsrv.token;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+/**
+ * @author Endi S. Dewata
+ */
+@XmlRootElement(name="TokenModifyRequest")
+public class TokenModifyRequest {
+
+    public static Marshaller marshaller;
+    public static Unmarshaller unmarshaller;
+
+    static {
+        try {
+            marshaller = JAXBContext.newInstance(TokenModifyRequest.class).createMarshaller();
+            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            unmarshaller = JAXBContext.newInstance(TokenModifyRequest.class).createUnmarshaller();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    String id;
+    String userID;
+
+    Link link;
+
+    @XmlAttribute(name="id")
+    public String getID() {
+        return id;
+    }
+
+    public void setID(String id) {
+        this.id = id;
+    }
+
+    @XmlElement(name="UserID")
+    public String getUserID() {
+        return userID;
+    }
+
+    public void setUserID(String userID) {
+        this.userID = userID;
+    }
+
+    @XmlElement(name="Link")
+    public Link getLink() {
+        return link;
+    }
+
+    public void setLink(Link link) {
+        this.link = link;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((link == null) ? 0 : link.hashCode());
+        result = prime * result + ((userID == null) ? 0 : userID.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TokenModifyRequest other = (TokenModifyRequest) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (link == null) {
+            if (other.link != null)
+                return false;
+        } else if (!link.equals(other.link))
+            return false;
+        if (userID == null) {
+            if (other.userID != null)
+                return false;
+        } else if (!userID.equals(other.userID))
+            return false;
+        return true;
+    }
+
+    public String toString() {
+        try {
+            StringWriter sw = new StringWriter();
+            marshaller.marshal(this, sw);
+            return sw.toString();
+
+        } catch (Exception e) {
+            return super.toString();
+        }
+    }
+
+    public static TokenModifyRequest valueOf(String string) throws Exception {
+        try {
+            return (TokenModifyRequest)unmarshaller.unmarshal(new StringReader(string));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static void main(String args[]) throws Exception {
+
+        TokenModifyRequest before = new TokenModifyRequest();
+        before.setID("token1");
+        before.setUserID("user1");
+
+        String string = before.toString();
+        System.out.println(string);
+
+        TokenModifyRequest after = TokenModifyRequest.valueOf(string);
+        System.out.println(before.equals(after));
+    }
+}
diff --git a/base/common/src/com/netscape/certsrv/token/TokenResource.java b/base/common/src/com/netscape/certsrv/token/TokenResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fe733d8804996be3e4cec7ef22bf753e7e094dd
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/token/TokenResource.java
@@ -0,0 +1,80 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.certsrv.token;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.jboss.resteasy.annotations.ClientResponseType;
+
+
+/**
+ * @author Endi S. Dewata
+ */
+@Path("tokens")
+public interface TokenResource {
+
+    @GET
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public TokenCollection findTokens(
+            @QueryParam("start") Integer start,
+            @QueryParam("size") Integer size);
+
+    @GET
+    @Path("{tokenID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public TokenData getToken(@PathParam("tokenID") String tokenID);
+
+    @POST
+    @ClientResponseType(entityType=TokenData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response addToken(TokenData tokenData);
+
+    @PUT
+    @Path("{tokenID}")
+    @ClientResponseType(entityType=TokenData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response updateToken(
+            @PathParam("tokenID") String tokenID,
+            TokenData tokenData);
+
+    @POST
+    @Path("{tokenID}")
+    @ClientResponseType(entityType=TokenData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response modifyToken(
+            @PathParam("tokenID") String tokenID,
+            TokenModifyRequest request);
+
+    @DELETE
+    @Path("{tokenID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public void removeToken(@PathParam("tokenID") String tokenID);
+}
diff --git a/base/common/src/com/netscape/certsrv/tps/TPSClient.java b/base/common/src/com/netscape/certsrv/tps/TPSClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ab50ac1b20721e05d7b8adeaa0e744167511251
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/tps/TPSClient.java
@@ -0,0 +1,42 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//GNU General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2013 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+package com.netscape.certsrv.tps;
+
+import com.netscape.certsrv.client.ClientConfig;
+import com.netscape.certsrv.client.PKIClient;
+import com.netscape.certsrv.token.TokenClient;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TPSClient {
+
+    public TokenClient tokenClient;
+
+    public TPSClient(ClientConfig config) throws Exception {
+        this(new PKIClient(config));
+    }
+
+    public TPSClient(PKIClient client) throws Exception {
+        tokenClient = new TokenClient(client);
+    }
+
+    public TokenClient getTokenClient() {
+        return tokenClient;
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CLI.java
index a1fc4f7f95789b0f08fa4c23d78959270dce139e..40ac517af761e90789fe624598e466f5a9aa7b14 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/CLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/CLI.java
@@ -37,6 +37,8 @@ public class CLI {
     public static CommandLineParser parser = new PosixParser();
     public static HelpFormatter formatter = new HelpFormatter();
 
+    public CLI parent;
+
     public String name;
     public String description;
 
@@ -44,8 +46,13 @@ public class CLI {
     public Map<String, CLI> modules = new LinkedHashMap<String, CLI>();
 
     public CLI(String name, String description) {
+        this(name, description, null);
+    }
+
+    public CLI(String name, String description, CLI parent) {
         this.name = name;
         this.description = description;
+        this.parent = parent;
     }
 
     public String getName() {
@@ -56,6 +63,14 @@ public class CLI {
         this.name = name;
     }
 
+    public String getFullName() {
+        if (parent == null) {
+            return name;
+        } else {
+            return parent.getName() + "-" + name;
+        }
+    }
+
     public String getDescription() {
         return description;
     }
diff --git a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
index 1c6411d790ddc0347108b7a3316b2a23fcca9dec..257f0bcfbd5418c2cc8332c13ce287fb7748817c 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
@@ -76,6 +76,7 @@ public class MainCLI extends CLI {
         addModule(new KRAConnectorCLI(this));
         addModule(new ProfileCLI(this));
         addModule(new SecurityDomainCLI(this));
+        addModule(new TPSCLI(this));
         addModule(new UserCLI(this));
     }
 
@@ -343,9 +344,10 @@ public class MainCLI extends CLI {
             }
 
             // get command module
+            if (verbose) System.out.println("Module: " + moduleName);
             module = getModule(moduleName);
             if (module == null)
-                throw new Error("Invalid command \"" + command + "\".");
+                throw new Error("Invalid module \"" + moduleName + "\".");
 
             // prepare module arguments
             if (moduleCommand != null) {
diff --git a/base/java-tools/src/com/netscape/cmstools/cli/TPSCLI.java b/base/java-tools/src/com/netscape/cmstools/cli/TPSCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..4dd7e208cd73894f7abecf487baeafb863c32f4c
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/cli/TPSCLI.java
@@ -0,0 +1,109 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.cli;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.netscape.certsrv.tps.TPSClient;
+import com.netscape.cmstools.token.TokenCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TPSCLI extends CLI {
+
+    public MainCLI mainCLI;
+    public TPSClient tpsClient;
+
+    public TPSCLI(MainCLI mainCLI) {
+        super("tps", "TPS management commands", mainCLI);
+        this.mainCLI = mainCLI;
+
+        addModule(new TokenCLI(this));
+    }
+
+    public void printHelp() {
+
+        System.out.println("Commands:");
+
+        int leftPadding = 1;
+        int rightPadding = 25;
+
+        for (CLI module : modules.values()) {
+            String label = name + "-" + module.getName();
+
+            int padding = rightPadding - leftPadding - label.length();
+            if (padding < 1)
+                padding = 1;
+
+            System.out.print(StringUtils.repeat(" ", leftPadding));
+            System.out.print(label);
+            System.out.print(StringUtils.repeat(" ", padding));
+            System.out.println(module.getDescription());
+        }
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        tpsClient = new TPSClient(mainCLI.client);
+
+        if (args.length == 0) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String command = args[0];
+        String moduleName;
+        String moduleCommand;
+
+        // If a command contains a '-' sign it will be
+        // split into module name and module command.
+        // Otherwise it's a single command.
+        int i = command.indexOf('-');
+        if (i >= 0) { // <module name>-<module command>
+            moduleName = command.substring(0, i);
+            moduleCommand = command.substring(i+1);
+
+        } else { // <command>
+            moduleName = command;
+            moduleCommand = null;
+        }
+
+        // get command module
+        if (verbose) System.out.println("Module: " + moduleName);
+        CLI module = getModule(moduleName);
+        if (module == null) {
+            throw new Error("Invalid module \"" + moduleName + "\".");
+        }
+
+        // prepare module arguments
+        String[] moduleArgs;
+        if (moduleCommand != null) {
+            moduleArgs = new String[args.length];
+            moduleArgs[0] = moduleCommand;
+            System.arraycopy(args, 1, moduleArgs, 1, args.length-1);
+
+        } else {
+            moduleArgs = new String[args.length-1];
+            System.arraycopy(args, 1, moduleArgs, 0, args.length-1);
+        }
+
+        module.execute(moduleArgs);
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/token/TokenAddCLI.java b/base/java-tools/src/com/netscape/cmstools/token/TokenAddCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..10405a906b6987e7fb23b36f2eebc3f4ac793b77
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/token/TokenAddCLI.java
@@ -0,0 +1,81 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.token;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.token.TokenData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenAddCLI extends CLI {
+
+    public TokenCLI tokenCLI;
+
+    public TokenAddCLI(TokenCLI tokenCLI) {
+        super("add", "Add token", tokenCLI);
+        this.tokenCLI = tokenCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <Token ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "user", true, "User ID");
+        option.setArgName("User ID");
+        option.setRequired(true);
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String tokenID = cmdArgs[0];
+
+        TokenData tokenData = new TokenData();
+        tokenData.setID(tokenID);
+        tokenData.setUserID(cmd.getOptionValue("user"));
+
+        tokenData = tokenCLI.tokenClient.addToken(tokenData);
+
+        MainCLI.printMessage("Added token \"" + tokenID + "\"");
+
+        TokenCLI.printToken(tokenData);
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/token/TokenCLI.java b/base/java-tools/src/com/netscape/cmstools/token/TokenCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..539ce7bae01dc8cd9a8a91583a09d348e1fba8fb
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/token/TokenCLI.java
@@ -0,0 +1,136 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.token;
+
+import org.apache.commons.lang.StringUtils;
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+import com.netscape.certsrv.token.TokenClient;
+import com.netscape.certsrv.token.TokenData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.TPSCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenCLI extends CLI {
+
+    public TPSCLI tpsCLI;
+    public TokenClient tokenClient;
+
+    public TokenCLI(TPSCLI tpsCLI) {
+        super("token", "Token management commands", tpsCLI);
+        this.tpsCLI = tpsCLI;
+
+        addModule(new TokenAddCLI(this));
+        addModule(new TokenFindCLI(this));
+        addModule(new TokenModifyCLI(this));
+        addModule(new TokenRemoveCLI(this));
+        addModule(new TokenShowCLI(this));
+    }
+
+    public String getFullName() {
+        return parent.getName() + "-" + name;
+    }
+
+    public void printHelp() {
+
+        System.out.println("Commands:");
+
+        int leftPadding = 1;
+        int rightPadding = 25;
+
+        for (CLI module : modules.values()) {
+            String label = getFullName() + "-" + module.getName();
+
+            int padding = rightPadding - leftPadding - label.length();
+            if (padding < 1)
+                padding = 1;
+
+            System.out.print(StringUtils.repeat(" ", leftPadding));
+            System.out.print(label);
+            System.out.print(StringUtils.repeat(" ", padding));
+            System.out.println(module.getDescription());
+        }
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        tokenClient = new TokenClient(tpsCLI.mainCLI.client);
+
+        if (args.length == 0) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String command = args[0];
+        String moduleName;
+        String moduleCommand;
+
+        // If a command contains a '-' sign it will be
+        // split into module name and module command.
+        // Otherwise it's a single command.
+        int i = command.indexOf('-');
+        if (i >= 0) { // <module name>-<module command>
+            moduleName = command.substring(0, i);
+            moduleCommand = command.substring(i+1);
+
+        } else { // <command>
+            moduleName = command;
+            moduleCommand = null;
+        }
+
+        // get command module
+        if (verbose) System.out.println("Module: " + moduleName);
+        CLI module = getModule(moduleName);
+        if (module == null) {
+            throw new Error("Invalid module \"" + moduleName + "\".");
+        }
+
+        // prepare module arguments
+        String[] moduleArgs;
+        if (moduleCommand != null) {
+            moduleArgs = new String[args.length];
+            moduleArgs[0] = moduleCommand;
+            System.arraycopy(args, 1, moduleArgs, 1, args.length-1);
+
+        } else {
+            moduleArgs = new String[args.length-1];
+            System.arraycopy(args, 1, moduleArgs, 0, args.length-1);
+        }
+
+        module.execute(moduleArgs);
+    }
+
+    public static void printToken(TokenData token) {
+        System.out.println("  Token ID: " + token.getID());
+        if (token.getUserID() != null) System.out.println("  User ID: " + token.getUserID());
+        if (token.getStatus() != null) System.out.println("  Status: " + token.getStatus());
+        if (token.getReason() != null) System.out.println("  Reason: " + token.getReason());
+        if (token.getAppletID() != null) System.out.println("  Applet ID: " + token.getAppletID());
+        if (token.getKeyInfo() != null) System.out.println("  Key Info: " + token.getKeyInfo());
+        if (token.getCreateTimestamp() != null) System.out.println("  Date Created: " + token.getCreateTimestamp());
+        if (token.getModifyTimestamp() != null) System.out.println("  Date Modified: " + token.getModifyTimestamp());
+
+        Link link = token.getLink();
+        if (verbose && link != null) {
+            System.out.println("  Link: " + link.getHref());
+        }
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/token/TokenFindCLI.java b/base/java-tools/src/com/netscape/cmstools/token/TokenFindCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..a627d13fbeb11074e21284425b3455e3a21bdbd3
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/token/TokenFindCLI.java
@@ -0,0 +1,94 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.token;
+
+import java.util.Collection;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.token.TokenCollection;
+import com.netscape.certsrv.token.TokenData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenFindCLI extends CLI {
+
+    public TokenCLI tokenCLI;
+
+    public TokenFindCLI(TokenCLI tokenCLI) {
+        super("find", "Find tokens", tokenCLI);
+        this.tokenCLI = tokenCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "start", true, "Page start");
+        option.setArgName("start");
+        options.addOption(option);
+
+        option = new Option(null, "size", true, "Page size");
+        option.setArgName("size");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String s = cmd.getOptionValue("start");
+        Integer start = s == null ? null : Integer.valueOf(s);
+
+        s = cmd.getOptionValue("size");
+        Integer size = s == null ? null : Integer.valueOf(s);
+
+        TokenCollection result = tokenCLI.tokenClient.findTokens(start, size);
+        Collection<TokenData> tokens = result.getTokens();
+
+        MainCLI.printMessage(tokens.size() + " token(s) matched");
+
+        boolean first = true;
+
+        for (TokenData tokenData : tokens) {
+
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            TokenCLI.printToken(tokenData);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + tokens.size());
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/token/TokenModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/token/TokenModifyCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..29f1a0741b98318d3b4aab7ead2f098a1a8a0cb8
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/token/TokenModifyCLI.java
@@ -0,0 +1,81 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.token;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.token.TokenData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenModifyCLI extends CLI {
+
+    public TokenCLI tokenCLI;
+
+    public TokenModifyCLI(TokenCLI tokenCLI) {
+        super("mod", "Modify token", tokenCLI);
+        this.tokenCLI = tokenCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <Token ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "user", true, "User ID");
+        option.setArgName("User ID");
+        option.setRequired(true);
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String tokenID = cmdArgs[0];
+
+        TokenData tokenData = new TokenData();
+        tokenData.setID(tokenID);
+        tokenData.setUserID(cmd.getOptionValue("user"));
+
+        tokenData = tokenCLI.tokenClient.updateToken(tokenID, tokenData);
+
+        MainCLI.printMessage("Modified token \"" + tokenID + "\"");
+
+        TokenCLI.printToken(tokenData);
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/token/TokenRemoveCLI.java b/base/java-tools/src/com/netscape/cmstools/token/TokenRemoveCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e986313cf16aea89f71cfa4b10b4dbbb95c7a5d
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/token/TokenRemoveCLI.java
@@ -0,0 +1,53 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.token;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenRemoveCLI extends CLI {
+
+    public TokenCLI tokenCLI;
+
+    public TokenRemoveCLI(TokenCLI tokenCLI) {
+        super("del", "Remove token", tokenCLI);
+        this.tokenCLI = tokenCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <Token ID>", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String tokenID = args[0];
+
+        tokenCLI.tokenClient.removeToken(tokenID);
+
+        MainCLI.printMessage("Deleted token \"" + tokenID + "\"");
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/token/TokenShowCLI.java b/base/java-tools/src/com/netscape/cmstools/token/TokenShowCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f074f60f609d92c9e70e5bc068d396a071687e6
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/token/TokenShowCLI.java
@@ -0,0 +1,56 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.token;
+
+import com.netscape.certsrv.token.TokenData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenShowCLI extends CLI {
+
+    public TokenCLI tokenCLI;
+
+    public TokenShowCLI(TokenCLI tokenCLI) {
+        super("show", "Show token", tokenCLI);
+        this.tokenCLI = tokenCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <Token ID>", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String tokenID = args[0];
+
+        TokenData tokenData = tokenCLI.tokenClient.getToken(tokenID);
+
+        MainCLI.printMessage("Token \"" + tokenID + "\"");
+
+        TokenCLI.printToken(tokenData);
+    }
+}
diff --git a/base/tps/java/CMakeLists.txt b/base/tps/java/CMakeLists.txt
index 9cfad9f0cf9cfd6f34bd6f726cc1b2242eb9069c..c8f90e44e260e43b60732ffced1e70491ff98505 100644
--- a/base/tps/java/CMakeLists.txt
+++ b/base/tps/java/CMakeLists.txt
@@ -70,6 +70,13 @@ find_file(PKI_NSUTIL_JAR
         /usr/share/java/pki
 )
 
+find_file(RESTEASY_ATOM_PROVIDER_JAR
+    NAMES
+        resteasy-atom-provider.jar
+    PATHS
+        ${RESTEASY_LIB}
+)
+
 find_file(SERVLET_JAR
     NAMES
         servlet.jar
@@ -93,7 +100,7 @@ javac(pki-tps-classes
     CLASSPATH
         ${PKI_CERTSRV_JAR} ${PKI_CMS_JAR} ${PKI_CMSCORE_JAR}
         ${PKI_CMSUTIL_JAR} ${PKI_NSUTIL_JAR}
-        ${LDAPJDK_JAR} ${JAXRS_API_JAR}
+        ${LDAPJDK_JAR} ${JAXRS_API_JAR} ${RESTEASY_ATOM_PROVIDER_JAR}
         ${JSS_JAR} ${COMMONS_CODEC_JAR} ${SYMKEY_JAR} ${SERVLET_JAR}
     OUTPUT_DIR
         ${CMAKE_BINARY_DIR}/classes
diff --git a/base/tps/java/org/dogtagpki/tps/server/TPSApplication.java b/base/tps/java/org/dogtagpki/tps/server/TPSApplication.java
index 4b05be934b2396d85818033372b7543996b0b6be..2f2b2a63ab5fac290950df8d15152efe941e0bd1 100644
--- a/base/tps/java/org/dogtagpki/tps/server/TPSApplication.java
+++ b/base/tps/java/org/dogtagpki/tps/server/TPSApplication.java
@@ -22,6 +22,8 @@ import java.util.Set;
 
 import javax.ws.rs.core.Application;
 
+import org.dogtagpki.tps.token.TokenService;
+
 import com.netscape.certsrv.acls.ACLInterceptor;
 import com.netscape.certsrv.authentication.AuthMethodInterceptor;
 import com.netscape.certsrv.base.PKIException;
@@ -60,6 +62,9 @@ public class TPSApplication extends Application {
         // system certs
         classes.add(SystemCertService.class);
 
+        // tokens
+        classes.add(TokenService.class);
+
         // exception mapper
         classes.add(PKIException.Mapper.class);
 
diff --git a/base/tps/java/org/dogtagpki/tps/server/TPSAuthority.java b/base/tps/java/org/dogtagpki/tps/server/TPSSubsystem.java
similarity index 86%
rename from base/tps/java/org/dogtagpki/tps/server/TPSAuthority.java
rename to base/tps/java/org/dogtagpki/tps/server/TPSSubsystem.java
index 326945a64a5191e9baba1eb4181600f0cf0dc977..92017812cfc0b09058133a3b85aee5b0b6c5dc46 100644
--- a/base/tps/java/org/dogtagpki/tps/server/TPSAuthority.java
+++ b/base/tps/java/org/dogtagpki/tps/server/TPSSubsystem.java
@@ -17,6 +17,8 @@
 // --- END COPYRIGHT BLOCK ---
 package org.dogtagpki.tps.server;
 
+import org.dogtagpki.tps.token.TokenDatabase;
+
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.authority.IAuthority;
 import com.netscape.certsrv.base.EBaseException;
@@ -29,7 +31,9 @@ import com.netscape.certsrv.request.IRequestQueue;
 /**
  * @author Endi S. Dewata <edewata@redhat.com>
  */
-public class TPSAuthority implements IAuthority, ISubsystem {
+public class TPSSubsystem implements IAuthority, ISubsystem {
+
+    public final static TPSSubsystem INSTANCE = new TPSSubsystem();
 
     public ILogger logger = CMS.getLogger();
 
@@ -38,6 +42,12 @@ public class TPSAuthority implements IAuthority, ISubsystem {
     public ISubsystem owner;
     public IConfigStore config;
 
+    public TokenDatabase tokenDatabase = new TokenDatabase();
+
+    public static TPSSubsystem getInstance() {
+        return INSTANCE;
+    }
+
     @Override
     public String getId() {
         return id;
@@ -99,4 +109,7 @@ public class TPSAuthority implements IAuthority, ISubsystem {
         return "tps";
     }
 
+    public TokenDatabase getTokenDatabase() {
+        return tokenDatabase;
+    }
 }
diff --git a/base/tps/java/org/dogtagpki/tps/token/TokenDatabase.java b/base/tps/java/org/dogtagpki/tps/token/TokenDatabase.java
new file mode 100644
index 0000000000000000000000000000000000000000..3db76649f34dd4d4fa88aa1d5fe5365560ef8e5c
--- /dev/null
+++ b/base/tps/java/org/dogtagpki/tps/token/TokenDatabase.java
@@ -0,0 +1,76 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package org.dogtagpki.tps.token;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * This class implements in-memory token database. In the future this
+ * will be replaced with LDAP database.
+ *
+ * @author Endi S. Dewata
+ */
+public class TokenDatabase {
+
+    public final static int DEFAULT_SIZE = 20;
+
+    Map<String, TokenRecord> tokens = new LinkedHashMap<String, TokenRecord>();
+
+    public Collection<TokenRecord> getTokens() throws Exception {
+        return tokens.values();
+    }
+
+    public TokenRecord getToken(String tokenID) throws Exception {
+        if (!tokens.containsKey(tokenID)) {
+            throw new Exception("Token "+ tokenID + " does not exist.");
+        }
+        return tokens.get(tokenID);
+    }
+
+    public void addToken(TokenRecord tokenRecord) throws Exception {
+        if (tokens.containsKey(tokenRecord.getID())) {
+            throw new Exception("Token "+ tokenRecord.getID() + " already exists.");
+        }
+
+        tokenRecord.setStatus("ENABLED");
+        tokenRecord.setCreateTimestamp(new Date());
+
+        tokens.put(tokenRecord.getID(), tokenRecord);
+    }
+
+    public void updateToken(String tokenID, TokenRecord tokenRecord) throws Exception {
+        if (!tokens.containsKey(tokenRecord.getID())) {
+            throw new Exception("Token "+ tokenRecord.getID() + " does not exist.");
+        }
+
+        tokenRecord.setModifyTimestamp(new Date());
+
+        tokens.put(tokenRecord.getID(), tokenRecord);
+    }
+
+    public void removeToken(String tokenID) throws Exception {
+        if (!tokens.containsKey(tokenID)) {
+            throw new Exception("Token "+ tokenID + " does not exist.");
+        }
+        tokens.remove(tokenID);
+    }
+}
diff --git a/base/tps/java/org/dogtagpki/tps/token/TokenRecord.java b/base/tps/java/org/dogtagpki/tps/token/TokenRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f9d9caf5c7f5a47972d77f8d160c75f1d315fe8
--- /dev/null
+++ b/base/tps/java/org/dogtagpki/tps/token/TokenRecord.java
@@ -0,0 +1,188 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package org.dogtagpki.tps.token;
+
+import java.util.Date;
+
+import com.netscape.certsrv.token.TokenData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenRecord {
+
+    String id;
+    String userID;
+    String status;
+    String reason;
+    String appletID;
+    String keyInfo;
+    Date createTimestamp;
+    Date modifyTimestamp;
+
+    public String getID() {
+        return id;
+    }
+
+    public void setID(String id) {
+        this.id = id;
+    }
+
+    public String getUserID() {
+        return userID;
+    }
+
+    public void setUserID(String userID) {
+        this.userID = userID;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    public String getAppletID() {
+        return appletID;
+    }
+
+    public void setAppletID(String appletID) {
+        this.appletID = appletID;
+    }
+
+    public String getKeyInfo() {
+        return keyInfo;
+    }
+
+    public void setKeyInfo(String keyInfo) {
+        this.keyInfo = keyInfo;
+    }
+
+    public Date getCreateTimestamp() {
+        return createTimestamp;
+    }
+
+    public void setCreateTimestamp(Date createTimestamp) {
+        this.createTimestamp = createTimestamp;
+    }
+
+    public Date getModifyTimestamp() {
+        return modifyTimestamp;
+    }
+
+    public void setModifyTimestamp(Date modifyTimestamp) {
+        this.modifyTimestamp = modifyTimestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((appletID == null) ? 0 : appletID.hashCode());
+        result = prime * result + ((createTimestamp == null) ? 0 : createTimestamp.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((keyInfo == null) ? 0 : keyInfo.hashCode());
+        result = prime * result + ((modifyTimestamp == null) ? 0 : modifyTimestamp.hashCode());
+        result = prime * result + ((reason == null) ? 0 : reason.hashCode());
+        result = prime * result + ((status == null) ? 0 : status.hashCode());
+        result = prime * result + ((userID == null) ? 0 : userID.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TokenRecord other = (TokenRecord) obj;
+        if (appletID == null) {
+            if (other.appletID != null)
+                return false;
+        } else if (!appletID.equals(other.appletID))
+            return false;
+        if (createTimestamp == null) {
+            if (other.createTimestamp != null)
+                return false;
+        } else if (!createTimestamp.equals(other.createTimestamp))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (keyInfo == null) {
+            if (other.keyInfo != null)
+                return false;
+        } else if (!keyInfo.equals(other.keyInfo))
+            return false;
+        if (modifyTimestamp == null) {
+            if (other.modifyTimestamp != null)
+                return false;
+        } else if (!modifyTimestamp.equals(other.modifyTimestamp))
+            return false;
+        if (reason == null) {
+            if (other.reason != null)
+                return false;
+        } else if (!reason.equals(other.reason))
+            return false;
+        if (status == null) {
+            if (other.status != null)
+                return false;
+        } else if (!status.equals(other.status))
+            return false;
+        if (userID == null) {
+            if (other.userID != null)
+                return false;
+        } else if (!userID.equals(other.userID))
+            return false;
+        return true;
+    }
+
+    public static void main(String args[]) throws Exception {
+
+        TokenData before = new TokenData();
+        before.setID("token1");
+        before.setUserID("user1");
+        before.setStatus("revoked");
+        before.setReason("lost");
+        before.setAppletID("APPLET1234");
+        before.setKeyInfo("key info");
+        before.setCreateTimestamp(new Date());
+        before.setModifyTimestamp(new Date());
+
+        String string = before.toString();
+        System.out.println(string);
+
+        TokenData after = TokenData.valueOf(string);
+        System.out.println(before.equals(after));
+    }
+}
diff --git a/base/tps/java/org/dogtagpki/tps/token/TokenService.java b/base/tps/java/org/dogtagpki/tps/token/TokenService.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc8b35d59e5c66cee6f7759041cd4d1f3cf85cfb
--- /dev/null
+++ b/base/tps/java/org/dogtagpki/tps/token/TokenService.java
@@ -0,0 +1,245 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package org.dogtagpki.tps.token;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.Iterator;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.dogtagpki.tps.server.TPSSubsystem;
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.token.TokenCollection;
+import com.netscape.certsrv.token.TokenData;
+import com.netscape.certsrv.token.TokenModifyRequest;
+import com.netscape.certsrv.token.TokenResource;
+import com.netscape.cms.servlet.base.PKIService;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class TokenService extends PKIService implements TokenResource {
+
+    public final static int DEFAULT_SIZE = 20;
+
+    public TokenService() {
+        System.out.println("TokenService.<init>()");
+    }
+
+    public TokenData createTokenData(TokenRecord tokenRecord) {
+
+        TokenData tokenData = new TokenData();
+        tokenData.setID(tokenRecord.getID());
+        tokenData.setUserID(tokenRecord.getUserID());
+        tokenData.setStatus(tokenRecord.getStatus());
+        tokenData.setReason(tokenRecord.getReason());
+        tokenData.setAppletID(tokenRecord.getAppletID());
+        tokenData.setKeyInfo(tokenRecord.getKeyInfo());
+        tokenData.setCreateTimestamp(tokenRecord.getCreateTimestamp());
+        tokenData.setModifyTimestamp(tokenRecord.getModifyTimestamp());
+
+        String tokenID = tokenRecord.getID();
+        try {
+            tokenID = URLEncoder.encode(tokenID, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+
+        URI uri = uriInfo.getBaseUriBuilder().path(TokenResource.class).path("{tokenID}").build(tokenID);
+        tokenData.setLink(new Link("self", uri));
+
+        return tokenData;
+    }
+
+    public TokenRecord createTokenRecord(TokenData tokenData) {
+
+        TokenRecord tokenRecord = new TokenRecord();
+        tokenRecord.setID(tokenData.getID());
+        tokenRecord.setUserID(tokenData.getUserID());
+        tokenRecord.setStatus(tokenData.getStatus());
+        tokenRecord.setReason(tokenData.getReason());
+        tokenRecord.setAppletID(tokenData.getAppletID());
+        tokenRecord.setKeyInfo(tokenData.getKeyInfo());
+        tokenRecord.setCreateTimestamp(tokenData.getCreateTimestamp());
+        tokenRecord.setModifyTimestamp(tokenData.getModifyTimestamp());
+
+        return tokenRecord;
+    }
+
+    @Override
+    public TokenCollection findTokens(Integer start, Integer size) {
+
+        System.out.println("TokenService.findTokens()");
+
+        try {
+            start = start == null ? 0 : start;
+            size = size == null ? DEFAULT_SIZE : size;
+
+            TPSSubsystem subsystem = TPSSubsystem.getInstance();
+            TokenDatabase database = subsystem.getTokenDatabase();
+
+            Iterator<TokenRecord> tokens = database.getTokens().iterator();
+
+            TokenCollection response = new TokenCollection();
+
+            int i = 0;
+
+            // skip to the start of the page
+            for ( ; i<start && tokens.hasNext(); i++) tokens.next();
+
+            // return entries up to the page size
+            for ( ; i<start+size && tokens.hasNext(); i++) {
+                response.addToken(createTokenData(tokens.next()));
+            }
+
+            // count the total entries
+            for ( ; tokens.hasNext(); i++) tokens.next();
+
+            if (start > 0) {
+                URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", Math.max(start-size, 0)).build();
+                response.addLink(new Link("prev", uri));
+            }
+
+            if (start+size < i) {
+                URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", start+size).build();
+                response.addLink(new Link("next", uri));
+            }
+
+            return response;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
+    public TokenData getToken(String tokenID) {
+
+        System.out.println("TokenService.getToken(\"" + tokenID + "\")");
+
+        try {
+            TPSSubsystem subsystem = TPSSubsystem.getInstance();
+            TokenDatabase database = subsystem.getTokenDatabase();
+
+            return createTokenData(database.getToken(tokenID));
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
+    public Response addToken(TokenData tokenData) {
+
+        System.out.println("TokenService.addToken(\"" + tokenData.getID() + "\")");
+
+        try {
+            TPSSubsystem subsystem = TPSSubsystem.getInstance();
+            TokenDatabase database = subsystem.getTokenDatabase();
+
+            database.addToken(createTokenRecord(tokenData));
+            tokenData = createTokenData(database.getToken(tokenData.getID()));
+
+            return Response
+                    .created(tokenData.getLink().getHref())
+                    .entity(tokenData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
+    public Response updateToken(String tokenID, TokenData tokenData) {
+
+        System.out.println("TokenService.updateToken(\"" + tokenID + "\")");
+
+        try {
+            TPSSubsystem subsystem = TPSSubsystem.getInstance();
+            TokenDatabase database = subsystem.getTokenDatabase();
+
+            TokenRecord tokenRecord = database.getToken(tokenID);
+            tokenRecord.setUserID(tokenData.getUserID());
+            database.updateToken(tokenData.getID(), tokenRecord);
+
+            tokenData = createTokenData(database.getToken(tokenID));
+
+            return Response
+                    .ok(tokenData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
+    public Response modifyToken(String tokenID, TokenModifyRequest request) {
+
+        System.out.println("TokenService.modifyToken(\"" + tokenID + "\", request");
+
+        try {
+            TPSSubsystem subsystem = TPSSubsystem.getInstance();
+            TokenDatabase database = subsystem.getTokenDatabase();
+
+            TokenRecord tokenRecord = database.getToken(tokenID);
+            // TODO: perform modification
+
+            TokenData tokenData = createTokenData(tokenRecord);
+
+            return Response
+                    .ok(tokenData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
+    public void removeToken(String tokenID) {
+
+        System.out.println("TokenService.removeToken(\"" + tokenID + "\")");
+
+        try {
+            TPSSubsystem subsystem = TPSSubsystem.getInstance();
+            TokenDatabase database = subsystem.getTokenDatabase();
+            database.removeToken(tokenID);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+}
diff --git a/base/tps/shared/conf/CS.cfg.in b/base/tps/shared/conf/CS.cfg.in
index 046bb830840cb6138610957e2c2496de32aef6c8..e972bcb9d191272f8a1126fbf714c298d84ec46b 100644
--- a/base/tps/shared/conf/CS.cfg.in
+++ b/base/tps/shared/conf/CS.cfg.in
@@ -1509,7 +1509,7 @@ op.format.tokenKey.issuerinfo.value=http://[PKI_HOSTNAME]:[PKI_UNSECURE_PORT]/cg
 passwordFile=[PKI_INSTANCE_PATH]/conf/password.conf
 passwordClass=com.netscape.cmsutil.password.PlainPasswordFile
 registry.file=[PKI_INSTANCE_PATH]/conf/registry.cfg
-subsystem.0.class=org.dogtagpki.tps.server.TPSAuthority
+subsystem.0.class=org.dogtagpki.tps.server.TPSSubsystem
 subsystem.0.id=tps
 tokendb._000=#########################################
 tokendb._001=# tokendb.auditLog:
-- 
1.8.1.4

