package com.bizosys.hsearch.common; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import com.bizosys.hsearch.filter.IStorable; import com.bizosys.hsearch.filter.Storable; import com.bizosys.hsearch.hbase.HReader; import com.bizosys.hsearch.hbase.HWriter; import com.bizosys.hsearch.hbase.NV; import com.bizosys.hsearch.hbase.NVBytes; import com.bizosys.hsearch.schema.ILanguageMap; import com.bizosys.hsearch.schema.IOConstants; import com.bizosys.hsearch.util.RecordScalar; import com.bizosys.oneline.ApplicationFault; import com.bizosys.oneline.SystemFault; import com.bizosys.oneline.services.Request; import com.bizosys.oneline.services.Response; import com.bizosys.oneline.util.StringUtils; public class Account { public static Logger l = CommonLog.l; private static final boolean INFO_ENABLED = l.isInfoEnabled(); private static final boolean DEBUG_ENABLED = l.isDebugEnabled(); public static final String ACCOUNT_KEY_NAME = "hkey"; public static final byte[] BUCKET_COUNTER_BYTES = "BUCKET_COUNTER".getBytes(); /** * Gives the account details for the given API KEY * @param APIKEY * @return * @throws IOException * @throws SystemFault */ public static AccountInfo getAccount(String APIKEY) throws SystemFault{ Account.init(); NV nv = new NV(IOConstants.NAME_VALUE_BYTES,IOConstants.NAME_VALUE_BYTES); RecordScalar scalar = new RecordScalar(new Storable(APIKEY), nv); HReader.getScalar(IOConstants.TABLE_CONFIG,scalar); if ( null == nv.data ) return null; return new AccountInfo(APIKEY, nv.data.toBytes(),0); } /** * store the account information (create/modify) * @param APIKEY * @param acc * @throws IOException */ public static void storeAccount(AccountInfo acc) throws SystemFault { Account.init(); NV nv = new NV(IOConstants.NAME_VALUE_BYTES, IOConstants.NAME_VALUE_BYTES, acc); RecordScalar record = new RecordScalar(new Storable(acc.APIKEY), nv); try { HWriter.getInstance(true).insertScalar(IOConstants.TABLE_CONFIG, record); if ( INFO_ENABLED ) { int buckets = ( null == acc.buckets) ? 0 : acc.buckets.size(); l.info(acc.name + " stored with total buckets " + buckets); } } catch (IOException ex) { throw new SystemFault(ex); } } /** * Get the current bucket for this account * There could be no bucket created * There are buckets * @return * @throws ApplicationFault */ public static long getCurrentBucket(AccountInfo acc) throws ApplicationFault, SystemFault { if ( null != acc.curBucket) return acc.curBucket; // Refresh before creating a bucket. AccountInfo freshAccount = Account.getAccount(acc.APIKEY); if ( null == freshAccount) throw new ApplicationFault("Account is not found."); if ( null == acc.buckets) acc.refresh(freshAccount); //No buckets created yet. if ( null == acc.buckets) acc.buckets = new ArrayList<byte[]>(); if ( 0 == acc.buckets.size()) { if ( DEBUG_ENABLED) l.debug("No buckets allocated."); if ( 0 == acc.maxbuckets) throw new ApplicationFault ("Allowed maximum buckets is 0."); acc.curBucket = createBucketId(1); if ( DEBUG_ENABLED) l.debug("Bucket " + acc.curBucket + " created for " + acc.name ); acc.buckets.add( ByteField.putLong(acc.curBucket)); Account.storeAccount(acc); return acc.curBucket; } acc.curBucket = ByteField.getLong(0, acc.buckets.get(acc.buckets.size() - 1)); return acc.curBucket; } /** * Get the Next Bucket. If not available create it. * @return * @throws ApplicationFault */ public static long getNextBucket(AccountInfo acc) throws ApplicationFault, SystemFault { acc.refresh(Account.getAccount(acc.APIKEY)); if ( null == acc.buckets) { acc.buckets = new ArrayList<byte[]>(); } int used = acc.buckets.size(); if ( used >= acc.maxbuckets) throw new ApplicationFault ( "Reached allocated bucket limit. Used/max is " + used + "/" + acc.maxbuckets); acc.curBucket = createBucketId(1); acc.buckets.add( ByteField.putLong(acc.curBucket)); Account.storeAccount(acc); return acc.curBucket; } /** * This creates bucket Id, unique across machines. * @return The bucket Id * @throws SystemFault */ private static long createBucketId(int amount) throws SystemFault { Account.init(); if ( DEBUG_ENABLED ) l.debug("Account > Creating a new bucket Zone"); /** * Get next bucket Id */ NV nv = new NV(IOConstants.NAME_VALUE_BYTES,IOConstants.NAME_VALUE_BYTES); RecordScalar scalar = new RecordScalar(BUCKET_COUNTER_BYTES, nv); long bucketId = HReader.idGenerationByAutoIncr(IOConstants.TABLE_CONFIG,scalar,amount); /** * Put the bucket as a row for counting document serials. */ if ( DEBUG_ENABLED ) l.debug("Account > The bucket counter is moved till :" + bucketId); /** * This document will contain many documents. We need to generate IDs for these * documents too.. This will be an AUTO INCREMENTAL way too. Means, we need to * enter row for each generate Bucket Ids */ long startPos = 1; nv.data = new Storable(startPos); try { for (int i=0; i< amount; i++) { long curBucket = bucketId - i; /** Counting from back */ RecordScalar docSerial = new RecordScalar(Storable.putLong(curBucket), nv); HWriter.getInstance(true).insertScalar(IOConstants.TABLE_CONFIG, docSerial); if ( DEBUG_ENABLED) l.debug("Account > bucket [" + curBucket + "], document serial counter set at 1."); } return bucketId; } catch (IOException ex) { StringBuilder buckets = new StringBuilder(); for (int i=0; i< amount; i++) { buckets.append('[').append(bucketId - i).append(']'); } l.fatal("Account > Problem during setting " + "document serial counter : " + buckets.toString(), ex); throw new SystemFault(ex); } } public static void resetDocumentCounter(long bucketId) throws SystemFault { NV nv = new NV(IOConstants.NAME_VALUE_BYTES,IOConstants.NAME_VALUE_BYTES); long startPos = 1; nv.data = new Storable(startPos); RecordScalar docSerial = new RecordScalar(Storable.putLong(bucketId), nv); try { HWriter.getInstance(true).insertScalar(IOConstants.TABLE_CONFIG, docSerial); } catch (IOException ex) { StringBuilder buckets = new StringBuilder(); l.fatal("Account > Problem during resetting document serial counter : " + buckets.toString(), ex); throw new SystemFault(ex); } if ( INFO_ENABLED ) l.info("Account > bucket [" + bucketId + "], document serial counter set at 1."); } /** * This create document serial no inside a bucket id, unique across machines * @param bucketId The current bucket id * @return Moved document serial position * @throws SystemFault * @throws BucketIsFullException */ public static short generateADocumentSerialId(long bucketId) throws SystemFault, BucketIsFullException { /** * Generate Ids for this bucket */ Account.init(); if ( DEBUG_ENABLED ) l.debug("Generating buckets keys"); NV nv = new NV(IOConstants.NAME_VALUE_BYTES,IOConstants.NAME_VALUE_BYTES); byte[] pkBucketId = Storable.putLong(bucketId); RecordScalar scalar = new RecordScalar(pkBucketId, nv); long bucketMaxPos = HReader.idGenerationByAutoIncr(IOConstants.TABLE_CONFIG,scalar,1); if ( DEBUG_ENABLED) l.debug("Buckets keys generated :" + bucketMaxPos); if ( bucketMaxPos > IOConstants.BUCKET_PACKING_LIMIT) { l.warn("Crossed the bucket limit of storage :" + bucketMaxPos); BucketIsFullException bife = new BucketIsFullException(bucketMaxPos); throw bife; } return new Long(bucketMaxPos).shortValue(); } /** * This create document serial no inside a bucket id, unique across machines * @param bucketId The current bucket id * @return Moved document serial position * @throws SystemFault * @throws BucketIsFullException */ public static AutoIncrIdRange generateAvailableDocumentSerialIds( long bucketId, int askedAmount) throws SystemFault { /** * Generate Ids for this bucket */ Account.init(); if ( DEBUG_ENABLED ) l.debug("Generating buckets keys"); NV nv = new NV(IOConstants.NAME_VALUE_BYTES,IOConstants.NAME_VALUE_BYTES); byte[] pkBucketId = Storable.putLong(bucketId); RecordScalar scalar = new RecordScalar(pkBucketId, nv); long bucketMaxPos = HReader.idGenerationByAutoIncr(IOConstants.TABLE_CONFIG,scalar,askedAmount); if ( DEBUG_ENABLED ) l.debug("Buckets keys incremented to :" + bucketMaxPos); int maxValue = IOConstants.BUCKET_PACKING_LIMIT; Long startPosition = bucketMaxPos - askedAmount; AutoIncrIdRange keyRanges = null; if ( bucketMaxPos >= maxValue) { Long avlAmount = maxValue - startPosition + 1; l.warn("Crossed the bucket limit of storage :" + bucketMaxPos); keyRanges = new AutoIncrIdRange( startPosition.shortValue(), avlAmount.shortValue() ); } else { keyRanges = new AutoIncrIdRange( startPosition.shortValue(), new Integer(askedAmount).shortValue()); } if ( INFO_ENABLED ) l.info(keyRanges.toString()); return keyRanges; } /** * Initializes the term buckets * Initial System: There will be no bucket. Start from Long.MIN_VALUE * Second time onwards : Continue */ private static boolean isInitialized = false; public static void init() { if (isInitialized) return; isInitialized = true; try { NV nv = new NV(IOConstants.NAME_VALUE_BYTES,IOConstants.NAME_VALUE_BYTES); if ( ! HReader.exists(IOConstants.TABLE_CONFIG, BUCKET_COUNTER_BYTES)) { if ( DEBUG_ENABLED) l.debug("Bucket Counter setup is not there. Setting up bucket id counter."); RecordScalar bucketCounter = new RecordScalar(new Storable(BUCKET_COUNTER_BYTES), nv); nv.data = new Storable(Long.MIN_VALUE); HWriter.getInstance(true).insertScalar(IOConstants.TABLE_CONFIG, bucketCounter); if ( INFO_ENABLED ) l.info("Bucket Counter setup is complete."); } } catch (IOException ex) { l.fatal("TermBucket > Bucket Counter Creation Failure:", ex); System.exit(1); } catch (SystemFault ex) { l.fatal("TermBucket > Bucker Bucket Counter Creation Failure:", ex); System.exit(1); } } /** * This gives all the rows from all tables. * @param bucketId Bucket Id * @return List of name-value bytes * @throws SystemFault */ public static List<NVBytes> get(long bucketId) throws SystemFault { Account.init(); List<NVBytes> allFields = null; for (Character c : ILanguageMap.ALL_TABLES) { List<NVBytes> nvs = HReader.getCompleteRow(c.toString(),Storable.putLong(bucketId)); if ( null == allFields) allFields = nvs; if ( null != nvs) allFields.addAll(nvs); } return allFields; } public static AccountInfo getActiveAccountInfo(Request req, Response res) throws SystemFault { if ( !req.isAuthenticated) { res.error( "Bad Key or no " + ACCOUNT_KEY_NAME + " parameter for API Access"); return null; } AccountInfo account = null; if ( null == req.user) { String hKey = req.getString(ACCOUNT_KEY_NAME, false, true, true); account = getAccount(hKey); } else { account = (AccountInfo) req.user; } if ( account.active) return account; res.error( "Your account is not active."); return null; } public static class AccountInfo implements IStorable { public String APIKEY = StringUtils.Empty; public boolean active = true; public String name = StringUtils.Empty; public String notes = StringUtils.Empty; public int maxbuckets = 1; public List<byte[]> buckets = null; public Long curBucket = null; public AccountInfo(String APIKEY) { this.APIKEY = APIKEY; } public AccountInfo(String APIKEY, byte[] data, int pos) { this.APIKEY = APIKEY; this.fromBytes(data, pos); } /** * Load from the byte values */ public int fromBytes(byte[] data, int pos) { if ( null == data) return -1; this.active = (data[pos++] == (byte)1); int idLen = ByteField.getInt(pos, data); pos = pos + 4; int keyLen = ByteField.getInt(pos, data); pos = pos + 4; int bucketLen = ByteField.getInt(pos, data); pos = pos + 4; byte[] idB = new byte[idLen]; System.arraycopy(data, pos, idB, 0, idLen); name = ByteField.getString(idB); pos = pos + idLen; byte[] keyB = new byte[keyLen]; System.arraycopy(data, pos, keyB, 0, keyLen); notes = ByteField.getString(keyB); pos = pos + keyLen; int totalBuckets = bucketLen/8; buckets = new ArrayList<byte[]>(totalBuckets); for ( int i=0; i< totalBuckets; i++) { byte[] bucketNo = new byte[8]; System.arraycopy(data,pos,bucketNo,0,8) ; buckets.add(bucketNo); pos = pos + 8; } this.maxbuckets = ByteField.getInt(pos, data); pos = pos + 4; return pos; } public byte[] toBytes() { byte[] idB = ByteField.putString(name); byte[] keyB = ByteField.putString(notes); byte[] bucketsB = null; if ( null == this.buckets) { bucketsB = new byte[0]; } else { bucketsB = new byte[this.buckets.size() * 8]; for (int i=0; i< this.buckets.size(); i++) { System.arraycopy( this.buckets.get(i), 0,bucketsB, i*8, 8); } } int idLen = idB.length; int keyLen = keyB.length; int bucketLen = bucketsB.length; int totalB = 1 + idLen + keyLen + bucketLen + 4 /** bucket Index */; byte[] bytes = new byte[12 + totalB]; bytes[0] = (active) ? (byte)1 : (byte)0; int pos = 1; pos = writeSize(bytes,ByteField.putInt(idLen),pos); pos = writeSize(bytes,ByteField.putInt(keyLen),pos); pos = writeSize(bytes,ByteField.putInt(bucketLen),pos); System.arraycopy(idB, 0, bytes, pos, idLen); pos = pos + idLen; System.arraycopy(keyB, 0, bytes, pos, keyLen); pos = pos + keyLen; System.arraycopy(bucketsB, 0, bytes, pos, bucketLen); pos = pos + bucketLen; pos = writeSize(bytes,ByteField.putInt(maxbuckets),pos); pos = pos + 4; return bytes; } public int writeSize(byte[] bytes, byte[] lenB, int pos) { bytes[pos++] = lenB[0]; bytes[pos++] = lenB[1]; bytes[pos++] = lenB[2]; bytes[pos++] = lenB[3]; return pos; } public void refresh(AccountInfo freshAccount) throws ApplicationFault{ if ( null == freshAccount) throw new ApplicationFault("Account is deleted."); this.buckets = freshAccount.buckets; this.maxbuckets = freshAccount.maxbuckets; this.notes = freshAccount.notes; this.active = freshAccount.active; } public void refresh() throws ApplicationFault, SystemFault{ AccountInfo freshAccount = Account.getAccount(APIKEY); refresh(freshAccount); } @Override public String toString() { return this.name; } public String toXml() { StringBuilder sb = new StringBuilder(); sb.append("<account><name>").append(name).append("</name>"); sb.append("<active>").append(active).append("</active>"); sb.append("<buckets>"); for (byte[] bucket : buckets) { sb.append("<bucket>").append(ByteField.getLong(0, bucket)).append("</bucket>"); } sb.append("</buckets>"); sb.append("<maxbuckets>").append(maxbuckets).append("</maxbuckets>"); sb.append("<notes>").append(notes).append("</notes></account>"); return sb.toString(); } } }