first try of a HBase storage for Vysper

Project: http://git-wip-us.apache.org/repos/asf/mina-vysper/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-vysper/commit/843a6d6c
Tree: http://git-wip-us.apache.org/repos/asf/mina-vysper/tree/843a6d6c
Diff: http://git-wip-us.apache.org/repos/asf/mina-vysper/diff/843a6d6c

Branch: refs/heads/master
Commit: 843a6d6c1ee63a08d5830722e9c8f4607eb122c9
Parents: b055a22
Author: Bernd Fondermann <[email protected]>
Authored: Fri Jul 5 22:11:45 2013 +0200
Committer: Bernd Fondermann <[email protected]>
Committed: Fri Jul 5 22:11:45 2013 +0200

----------------------------------------------------------------------
 server/storage/hbase/pom.xml                    |   6 +
 .../vysper/storage/hbase/HBaseStorage.java      | 145 +++++++++++++++
 .../storage/hbase/HBaseStorageException.java    |  21 +++
 .../hbase/HBaseStorageProviderRegistry.java     |  37 ++++
 .../apache/vysper/storage/hbase/HBaseUtils.java |  34 ++++
 .../hbase/roster/HBaseRosterManager.java        | 177 +++++++++++++++++++
 .../storage/hbase/user/HBaseUserManagement.java | 144 +++++++++++++++
 7 files changed, 564 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/pom.xml
----------------------------------------------------------------------
diff --git a/server/storage/hbase/pom.xml b/server/storage/hbase/pom.xml
index 0a9ba92..480bd57 100644
--- a/server/storage/hbase/pom.xml
+++ b/server/storage/hbase/pom.xml
@@ -42,6 +42,12 @@
     </dependency>
 
     <dependency>
+      <groupId>org.apache.hadoop</groupId>
+      <artifactId>hadoop-core</artifactId>
+      <version>1.0.4</version>
+    </dependency>
+      
+    <dependency>
       <groupId>org.apache.hbase</groupId>
       <artifactId>hbase</artifactId>
       <version>0.94.7</version>

http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java
----------------------------------------------------------------------
diff --git 
a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java
 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java
new file mode 100644
index 0000000..b2faeb9
--- /dev/null
+++ 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java
@@ -0,0 +1,145 @@
+/*
+ *  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.vysper.storage.hbase;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.MasterNotRunningException;
+import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.hadoop.hbase.client.HTableInterface;
+import org.apache.hadoop.hbase.client.HTablePool;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import static org.apache.vysper.storage.hbase.HBaseUtils.*;
+
+/**
+ * back-end adaptor for HBase
+ * @author The Apache MINA Project ([email protected])
+ */
+public class HBaseStorage {
+
+    final Logger LOG = LoggerFactory.getLogger(HBaseStorage.class);
+
+    public static final String TABLE_NAME_USER = "vysper_user";
+    public static final String COLUMN_FAMILY_NAME_BASIC = "bsc";
+    public static final String COLUMN_FAMILY_NAME_CONTACT = "cct";
+    public static final byte[] COLUMN_FAMILY_NAME_CONTACT_BYTES = 
COLUMN_FAMILY_NAME_CONTACT.getBytes();
+    public static final String COLUMN_FAMILY_NAME_ROSTER = "rst";
+    public static final byte[] COLUMN_FAMILY_NAME_ROSTER_BYTES = 
COLUMN_FAMILY_NAME_ROSTER.getBytes();
+    
+    protected static HBaseStorage hbaseStorageSingleton;
+
+    protected HBaseStorage() {
+        super();
+        // protected
+    }
+
+    public static HBaseStorage getInstance() throws HBaseStorageException {
+        if (hbaseStorageSingleton != null) return hbaseStorageSingleton;
+        synchronized (HBaseStorage.class) {
+            if (hbaseStorageSingleton == null) hbaseStorageSingleton = new 
HBaseStorage();
+            hbaseStorageSingleton.init();
+            return hbaseStorageSingleton;
+        }
+    }
+
+    protected Configuration hbaseConfiguration = null;
+    protected HBaseAdmin hbaseAdmin;
+    protected HTablePool tablePool;
+
+    public void init() throws HBaseStorageException {
+        try {
+            hbaseConfiguration = HBaseConfiguration.create();
+        } catch (Exception e) {
+            throw new HBaseStorageException("failed to load HBase 
configuration from file hbase-site.xml");
+        }
+        final int size = hbaseConfiguration.size();
+        if (size == 0) throw new HBaseStorageException("HBase configuration is 
empty");
+
+        try {
+            connectHBase();
+        } catch (HBaseStorageException e) {
+            LOG.error("connection to HBase failed", e);
+            throw e;
+        }
+    }
+
+    protected void connectHBase() throws HBaseStorageException {
+        try {
+            LOG.info("connecting to HBase...");
+            hbaseAdmin = new HBaseAdmin(hbaseConfiguration);
+            tablePool = new HTablePool(hbaseConfiguration, Integer.MAX_VALUE);
+            LOG.info("HBase connected.");
+        } catch (MasterNotRunningException e) {
+            throw new HBaseStorageException("failed connecting to HBase Master 
Server", e);
+        } catch (ZooKeeperConnectionException e) {
+            throw new HBaseStorageException("failed connecting to HBase 
Zookeeper Cluster", e);
+        }
+    }
+
+    public HTableInterface getTable(String tableName) {
+        return tablePool.getTable(tableName);
+    }
+
+    public Result getEntityRow(Entity entity, String... columnFamilyNames) {
+        if (columnFamilyNames == null || columnFamilyNames.length == 0) {
+            columnFamilyNames = new String[]{COLUMN_FAMILY_NAME_CONTACT};
+        }
+
+        final HTableInterface userTable = getTable(TABLE_NAME_USER);
+        try {
+            final Get get = new Get(entityAsBytes(entity.getBareJID()));
+            for (String columnFamilyName : columnFamilyNames) {
+                get.addFamily(asBytes(columnFamilyName));
+            }
+            final Result result = userTable.get(get);
+            return result;
+        } catch (IOException e) {
+            e.printStackTrace();  //To change body of catch statement use File 
| Settings | File Templates.
+            return null;
+        } finally {
+            putTable(userTable);
+        }
+    }
+
+    public void putTable(HTableInterface userTable) {
+        if (userTable == null) return;
+        try {
+            userTable.close();
+        } catch (IOException e) {
+            String tableName = "unknown";
+            try {
+                tableName = new String(userTable.getTableName(), "UTF-8");
+            } catch (UnsupportedEncodingException e1) {
+                e1.printStackTrace();  // encoding exceptions are killing me
+            }
+            LOG.warn("failed to return table " + tableName + " to pool");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java
----------------------------------------------------------------------
diff --git 
a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java
 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java
new file mode 100644
index 0000000..86ad141
--- /dev/null
+++ 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java
@@ -0,0 +1,21 @@
+package org.apache.vysper.storage.hbase;
+
+/**
+ */
+public class HBaseStorageException extends Exception {
+    public HBaseStorageException() {
+        super();
+    }
+
+    public HBaseStorageException(String s) {
+        super(s);
+    }
+
+    public HBaseStorageException(String s, Throwable throwable) {
+        super(s, throwable);
+    }
+
+    public HBaseStorageException(Throwable throwable) {
+        super(throwable);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java
----------------------------------------------------------------------
diff --git 
a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java
 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java
new file mode 100644
index 0000000..32ad50a
--- /dev/null
+++ 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java
@@ -0,0 +1,37 @@
+/*
+ *  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.vysper.storage.hbase;
+
+import org.apache.vysper.storage.OpenStorageProviderRegistry;
+import org.apache.vysper.storage.hbase.roster.HBaseRosterManager;
+import org.apache.vysper.storage.hbase.user.HBaseUserManagement;
+
+/**
+ *
+ * @author The Apache MINA Project ([email protected])
+ */
+public class HBaseStorageProviderRegistry extends OpenStorageProviderRegistry {
+
+    public HBaseStorageProviderRegistry() throws HBaseStorageException {
+        add(new HBaseUserManagement(HBaseStorage.getInstance()));
+        add(new HBaseRosterManager(HBaseStorage.getInstance()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java
----------------------------------------------------------------------
diff --git 
a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java
 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java
new file mode 100644
index 0000000..a0d8f49
--- /dev/null
+++ 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java
@@ -0,0 +1,34 @@
+package org.apache.vysper.storage.hbase;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ */
+public class HBaseUtils {
+
+    public static byte[] asBytes(String str) {
+        if (str == null) return null;
+        try {
+            return str.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();  // won't happen! UTF-8 is supported
+            return null;
+        }
+    }
+
+    public static byte[] entityAsBytes(Entity entity) {
+        if (entity == null) return null;
+        return asBytes(entity.getFullQualifiedName());
+    }
+
+    public static String toStr(byte[] bytes) {
+        if (bytes == null) return null;
+        try {
+            return new String(bytes, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            return null; // will not happen for UTF-8
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java
----------------------------------------------------------------------
diff --git 
a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java
 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java
new file mode 100644
index 0000000..a7417d6
--- /dev/null
+++ 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java
@@ -0,0 +1,177 @@
+/*
+ *  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.vysper.storage.hbase.roster;
+
+import org.apache.hadoop.hbase.client.Delete;
+import org.apache.hadoop.hbase.client.HTableInterface;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.vysper.storage.hbase.HBaseStorage;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityFormatException;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.modules.roster.AskSubscriptionType;
+import org.apache.vysper.xmpp.modules.roster.MutableRoster;
+import org.apache.vysper.xmpp.modules.roster.Roster;
+import org.apache.vysper.xmpp.modules.roster.RosterException;
+import org.apache.vysper.xmpp.modules.roster.RosterGroup;
+import org.apache.vysper.xmpp.modules.roster.RosterItem;
+import org.apache.vysper.xmpp.modules.roster.SubscriptionType;
+import org.apache.vysper.xmpp.modules.roster.persistence.AbstractRosterManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NavigableMap;
+
+import static org.apache.vysper.storage.hbase.HBaseStorage.*;
+import static org.apache.vysper.storage.hbase.HBaseUtils.asBytes;
+import static org.apache.vysper.storage.hbase.HBaseUtils.entityAsBytes;
+import static org.apache.vysper.storage.hbase.HBaseUtils.toStr;
+
+/**
+ * @author The Apache MINA Project ([email protected])
+ */
+public class HBaseRosterManager extends AbstractRosterManager {
+
+    final Logger LOG = LoggerFactory.getLogger(HBaseRosterManager.class);
+    
+    public static final String COLUMN_PREFIX_NAME = "n:";
+    public static final String COLUMN_PREFIX_TYPE = "t:";
+    public static final String COLUMN_PREFIX_ASKTYPE = "a:";
+
+    protected HBaseStorage hBaseStorage;
+
+    public HBaseRosterManager(HBaseStorage hBaseStorage) {
+        this.hBaseStorage = hBaseStorage;
+    }
+
+    @Override
+    protected Roster retrieveRosterInternal(Entity bareJid) {
+        final Result entityRow = hBaseStorage.getEntityRow(bareJid);
+
+        MutableRoster roster = new MutableRoster();
+
+        final NavigableMap<byte[],byte[]> contacts = 
entityRow.getFamilyMap(COLUMN_FAMILY_NAME_CONTACT_BYTES);
+        for (byte[] contactBytes : contacts.keySet()) {
+            String contactAsString = null;
+            EntityImpl contactJID = null;
+            try {
+                contactAsString = new String(contactBytes, "UTF-8");
+                contactJID = EntityImpl.parse(contactAsString);
+            } catch (Exception e) {
+                LOG.warn("failed to read contact identified by '{}' for user 
{}", bareJid, contactAsString);
+                continue;
+            }
+
+            final NavigableMap<byte[],byte[]> contactDetails = 
entityRow.getFamilyMap(COLUMN_FAMILY_NAME_ROSTER_BYTES);
+            String name = toStr(contactDetails.get(asBytes(COLUMN_PREFIX_NAME 
+ contactAsString)));
+            String typeString = 
toStr(contactDetails.get(asBytes(COLUMN_PREFIX_TYPE + contactAsString)));
+            String askTypeString = 
toStr(contactDetails.get(asBytes(COLUMN_PREFIX_ASKTYPE + contactAsString)));
+
+            SubscriptionType subscriptionType = null;
+            try {
+                subscriptionType = SubscriptionType.valueOf(typeString == null 
? "NONE" : typeString.toUpperCase());
+            } catch (IllegalArgumentException e) {
+                LOG.warn("when loading roster for user " + bareJid + ", 
contact " + contactJID + " misses a subscription type");
+            }
+
+            AskSubscriptionType askSubscriptionType = 
AskSubscriptionType.NOT_SET;
+            try {
+                if (askTypeString != null)
+                    askSubscriptionType = 
AskSubscriptionType.valueOf(askTypeString);
+            } catch (IllegalArgumentException e) {
+                LOG.warn("when loading roster for user " + 
bareJid.getFullQualifiedName() + ", contact "
+                        + contactJID.getFullQualifiedName() + ", the ask 
subscription type is unparsable. skipping!");
+                continue; // don't return it, don't set a default!
+            }
+
+            List<RosterGroup> groups = new ArrayList<RosterGroup>();
+            // TODO read groups
+
+            RosterItem item = new RosterItem(contactJID, name, 
subscriptionType, askSubscriptionType, groups);
+            LOG.info("item loaded for " + bareJid.getFullQualifiedName() + ": 
" + item.toString());
+            roster.addItem(item);
+        }
+        return roster;
+    }
+
+    @Override
+    protected Roster addNewRosterInternal(Entity jid) {
+        return new MutableRoster();
+    }
+
+    @Override
+    public void addContact(Entity jid, RosterItem rosterItem) throws 
RosterException {
+        if (jid == null)
+            throw new RosterException("jid not provided");
+        if (rosterItem.getJid() == null)
+            throw new RosterException("contact jid not provided");
+
+        Entity contactJid = rosterItem.getJid().getBareJID();
+        final String contactIdentifier = contactJid.getFullQualifiedName();
+        
+        // prepare contact entries
+        final Put put = new Put(entityAsBytes(jid.getBareJID()));
+        put.add(COLUMN_FAMILY_NAME_CONTACT_BYTES, asBytes(contactIdentifier), 
asBytes(rosterItem.getSubscriptionType().value()));
+        put.add(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_NAME + 
contactIdentifier), asBytes(rosterItem.getName()));
+        put.add(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_TYPE + 
contactIdentifier), asBytes(rosterItem.getSubscriptionType().value()));
+        put.add(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_ASKTYPE 
+ contactIdentifier), asBytes(rosterItem.getAskSubscriptionType().value()));
+
+        final HTableInterface userTable = 
hBaseStorage.getTable(TABLE_NAME_USER);
+        try {
+            userTable.put(put);
+            LOG.info("contact {} saved to HBase for user {}", 
rosterItem.getJid(), jid);
+        } catch (IOException e) {
+            throw new RosterException("failed to add contact node to roster 
for user = " + jid.getFullQualifiedName()
+                    + " and contact jid = " + 
rosterItem.getJid().getFullQualifiedName(), e);
+        } finally {
+            hBaseStorage.putTable(userTable);
+        }
+    }
+
+    @Override
+    public void removeContact(Entity jidUser, Entity jidContact) throws 
RosterException {
+        if (jidUser == null)
+            throw new RosterException("jid not provided");
+        if (jidContact == null)
+            throw new RosterException("contact jid not provided");
+
+        final String contactIdentifier = jidContact.getFullQualifiedName();
+        final Delete delete = new Delete(entityAsBytes(jidUser.getBareJID()));
+        delete.deleteColumns(COLUMN_FAMILY_NAME_CONTACT_BYTES, 
asBytes(contactIdentifier));
+        delete.deleteColumns(COLUMN_FAMILY_NAME_ROSTER_BYTES, 
asBytes(COLUMN_PREFIX_NAME + contactIdentifier));
+        delete.deleteColumns(COLUMN_FAMILY_NAME_ROSTER_BYTES, 
asBytes(COLUMN_PREFIX_TYPE + contactIdentifier));
+        delete.deleteColumns(COLUMN_FAMILY_NAME_ROSTER_BYTES, 
asBytes(COLUMN_PREFIX_ASKTYPE + contactIdentifier));
+        
+        final HTableInterface userTable = 
hBaseStorage.getTable(TABLE_NAME_USER);
+        try {
+            userTable.delete(delete);
+            LOG.info("contact {} removed from HBase for user {}", jidContact, 
jidUser);
+        } catch (IOException e) {
+            throw new RosterException("failed to add contact node to roster 
for user = " + jidUser.getFullQualifiedName()
+                    + " and contact jid = " + 
jidContact.getFullQualifiedName(), e);
+        } finally {
+            hBaseStorage.putTable(userTable);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java
----------------------------------------------------------------------
diff --git 
a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java
 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java
new file mode 100644
index 0000000..72a2b0e
--- /dev/null
+++ 
b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java
@@ -0,0 +1,144 @@
+/*
+ *  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.vysper.storage.hbase.user;
+
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.vysper.storage.hbase.HBaseStorage;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityFormatException;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.authentication.AccountCreationException;
+import org.apache.vysper.xmpp.authentication.AccountManagement;
+import org.apache.vysper.xmpp.authentication.UserAuthentication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+
+import static 
org.apache.vysper.storage.hbase.HBaseStorage.COLUMN_FAMILY_NAME_BASIC;
+import static org.apache.vysper.storage.hbase.HBaseUtils.entityAsBytes;
+
+/**
+ *
+ * @author The Apache MINA Project ([email protected])
+ */
+public class HBaseUserManagement implements UserAuthentication, 
AccountManagement {
+
+    final Logger logger = LoggerFactory.getLogger(HBaseUserManagement.class);
+
+    public static final byte[] PASSWORD_COLUMN = "pwd".getBytes();
+    
+    protected HBaseStorage hBaseStorage;
+
+    /**
+     * the salt before encrypting all passwords
+     * change once before creating the first account
+     */
+    private String encryptionSalt = "saltetForVysper";
+
+    /**
+     * the number of hashing rounds for encrypting all passwords
+     * change once before creating the first account
+     */
+    private int hashingRounds = 5;
+    
+    public HBaseUserManagement(HBaseStorage hBaseStorage) {
+        this.hBaseStorage = hBaseStorage;
+    }
+
+    public boolean verifyCredentials(Entity jid, String passwordCleartext, 
Object credentials) {
+        if (passwordCleartext == null)
+            return false;
+        try {
+            final Result entityRow = hBaseStorage.getEntityRow(jid, 
COLUMN_FAMILY_NAME_BASIC);
+            if (entityRow == null) return false;
+
+            final String encryptedGivenPassword = 
encryptPassword(passwordCleartext);
+            final byte[] passwordSavedBytes = 
entityRow.getValue(COLUMN_FAMILY_NAME_BASIC.getBytes(), PASSWORD_COLUMN);
+            return new String(passwordSavedBytes, 
"UTF-8").equals(encryptedGivenPassword);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    protected String encryptPassword(String passwordCleartext) {
+        if (passwordCleartext == null) passwordCleartext = "";
+        try {
+            passwordCleartext = passwordCleartext + encryptionSalt;
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+
+            int rounds = Math.max(1, hashingRounds);
+            byte[] pwdBytes = passwordCleartext.getBytes("UTF-8");
+            for (int i = 0; i < rounds; i++) {
+                pwdBytes = digest.digest(pwdBytes);
+            }
+            return new String(pwdBytes, "UTF-8");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean verifyCredentials(String username, String 
passwordCleartext, Object credentials) {
+        try {
+            return verifyCredentials(EntityImpl.parse(username), 
passwordCleartext, credentials);
+        } catch (EntityFormatException e) {
+            return false;
+        }
+    }
+
+    public boolean verifyAccountExists(Entity jid) {
+        final Result entityRow = hBaseStorage.getEntityRow(jid, 
COLUMN_FAMILY_NAME_BASIC);
+        return !entityRow.isEmpty();
+    }
+
+    public void addUser(Entity username, String password) throws 
AccountCreationException {
+        // if already existent, don't create, throw error
+        if (verifyAccountExists(username)) {
+            throw new AccountCreationException("account already exists: " + 
username.getFullQualifiedName());
+        }
+
+        // now, finally, create
+        try {
+            // row is created when first column for it is created.
+            setPasswordInHBase(username, password);
+            logger.info("account created in HBase for " + username);
+        } catch (Exception e) {
+            throw new AccountCreationException("failed to creating in HBase 
account " + username, e);
+        }
+
+    }
+
+    private void setPasswordInHBase(Entity username, String password) throws 
IOException {
+        final Put put = new Put(entityAsBytes(username));
+        put.add(COLUMN_FAMILY_NAME_BASIC.getBytes(), PASSWORD_COLUMN, 
encryptPassword(password).getBytes("UTF-8"));
+        hBaseStorage.getTable(HBaseStorage.TABLE_NAME_USER).put(put);
+    }
+
+    public void changePassword(Entity username, String password) throws 
AccountCreationException {
+        try {
+            setPasswordInHBase(username, password);
+            logger.info("password changed for " + username);
+        } catch (Exception e) {
+            throw new AccountCreationException("failed to change password for 
" + username, e);
+        }
+    }
+}

Reply via email to