/*
* Copyright 2010 Bizosys Technologies Limited
*
* Licensed to the Bizosys Technologies Limited (Bizosys) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Bizosys 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 com.bizosys.hsearch;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import com.bizosys.hsearch.common.Account;
import com.bizosys.hsearch.common.ByteField;
import com.bizosys.hsearch.common.Account.AccountInfo;
import com.bizosys.hsearch.hbase.NVBytes;
import com.bizosys.hsearch.index.DocumentType;
import com.bizosys.hsearch.index.IndexWriter;
import com.bizosys.hsearch.index.InvertedIndex;
import com.bizosys.hsearch.index.TermType;
import com.bizosys.hsearch.index.WeightType;
import com.bizosys.hsearch.inpipe.util.StopwordManager;
import com.bizosys.hsearch.inpipe.util.StopwordRefresh;
import com.bizosys.hsearch.util.Hash;
import com.bizosys.oneline.ApplicationFault;
import com.bizosys.oneline.SystemFault;
import com.bizosys.oneline.conf.Configuration;
import com.bizosys.oneline.services.Request;
import com.bizosys.oneline.services.Response;
import com.bizosys.oneline.services.Service;
import com.bizosys.oneline.services.ServiceMetaData;
import com.bizosys.oneline.util.StringUtils;
import com.bizosys.oneline.util.XmlUtils;
public class ManagementService implements Service {
public static Logger l = Logger.getLogger(ManagementService.class.getName());
private static final boolean DEBUG_ENABLED = l.isDebugEnabled();
Configuration conf = null;
private String secretKey = StringUtils.Empty;
public void process(Request req, Response res) {
String action = req.action;
try {
if ( "account.create".equals(action) ) {
this.createAccount(req, res);
} else if ( "account.get".equals(action) ) {
this.getAccountInformation(req, res);
} else if ( "account.modify".equals(action) ) {
this.modifyAccountInformation(req, res);
} else if ( "account.buckets.set".equals(action) ) {
this.setBuckets(req, res);
} else if ( "account.bucket.detail".equals(action) ) {
this.getInvertedIndex(req, res);
} else if ( "account.bucket.truncate".equals(action) ) {
this.deleteBucket(req, res);
} else if ( "account.truncate".equals(action) ) {
this.truncate(req, res);
} else if ( "account.doccodes.get".equals(action) ) {
this.getDocumentTypeCodes(req, res);
} else if ( "account.doccodes.set".equals(action) ) {
this.setDocumentTypeCodes(req, res);
} else if ( "account.doccodes.append".equals(action) ) {
this.appendDocumentTypeCodes(req, res);
} else if ( "account.termcodes.get".equals(action) ) {
this.getTermTypeCodes(req, res);
} else if ( "account.termcodes.set".equals(action) ) {
this.setTermTypeCodes(req, res);
} else if ( "account.termcodes.append".equals(action) ) {
this.appendTermTypeCodes(req, res);
} else if ( "account.fieldweight.get".equals(action) ) {
this.getWeightCodes(req, res);
} else if ( "account.fieldweight.set".equals(action) ) {
this.setWeightCodes(req, res);
} else if ( "account.fieldweight.append".equals(action) ) {
this.appendWeightCodes(req, res);
} else if ( "hsearch.stopwords.add".equals(action) ) {
this.addStopwords(req, res);
} else if ( "hsearch.stopwords.get".equals(action) ) {
this.getStopwords(req, res);
} else {
res.error("Failed Unknown operation : " + action);
}
} catch (Exception ix) {
l.fatal("Failure > ", ix);
res.error("Failure : ManagementService: " + ix.getMessage() + "\n" + req.toString() );
}
}
/**
* Creates an account and provides the API KEy.
* @param req
* @param res
* @throws ApplicationFault
* @throws IOException
* @throws SystemFault
*/
private void createAccount(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
if ( DEBUG_ENABLED) l.debug("Account creation Start");
String accName = req.getString("name", false, true, false);
String accDetail = req.getString("detail", false, true, false);
int maxBuckets = req.getInteger("buckets", 1);
String hashKey = Hash.createHex(this.secretKey, accName);
AccountInfo existingAccount = Account.getAccount(hashKey);
if ( null != existingAccount) {
res.error("Account already exists.");
if ( l.isInfoEnabled() ) l.info(existingAccount.toXml());
return;
}
AccountInfo acc = new AccountInfo(hashKey);
acc.active = true;
acc.name = accName;
acc.notes = accDetail;
acc.maxbuckets = maxBuckets;
Account.storeAccount(acc);
if ( DEBUG_ENABLED) l.debug("Account creation Sucessful");
Account.getCurrentBucket(acc);
if ( DEBUG_ENABLED) l.debug("Bucket creation Sucessful");
res.writeXml("<APIKEY>" + hashKey + "</APIKEY>");
}
/**
* Get detail information about the account.
* @param req
* @param res
* @throws ApplicationFault
* @throws IOException
* @throws SystemFault
*/
private void getAccountInformation(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo existingAcc = Account.getActiveAccountInfo(req, res);
if ( null == existingAcc) return;
res.writeXml(existingAcc.toXml());
}
/**
* Modify the account detail information.
* @param req
* @param res
* @throws ApplicationFault
* @throws IOException
* @throws SystemFault
*/
private void modifyAccountInformation(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo existingAcc = Account.getActiveAccountInfo(req, res);
if ( null == existingAcc) return;
String notes = req.getString("notes", false, true, true);
existingAcc.notes = notes;
Account.storeAccount(existingAcc);
res.writeXml(existingAcc.toXml());
}
/**
* Allocates bucket to an account
* @param req
* @param res
* @throws ApplicationFault
* @throws IOException
* @throws SystemFault
*/
private void setBuckets(Request req, Response res)
throws ApplicationFault, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) throw new ApplicationFault("Account is not found");
int maxBuckets = req.getInteger("buckets", -1);
if ( maxBuckets < 1) throw new ApplicationFault("Bucket should be 1 or above");
if ( null != acc.buckets) {
int usedBuckets = acc.buckets.size();
if ( usedBuckets > maxBuckets) throw new ApplicationFault(
"You have already used " + usedBuckets +
" buckets and setting max buckets to " + maxBuckets);
}
acc.maxbuckets = maxBuckets;
Account.storeAccount(acc);
res.writeXml("<account>" + acc.toXml() + "</account>");
}
private void deleteBucket(Request req, Response res)
throws ApplicationFault, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) throw new ApplicationFault("Account is not found");
Long bucketId = req.getLong("bucket", true);
if ( null == bucketId) throw new ApplicationFault("Bucket Id is not found.");
byte[] bucketIdB = ByteField.putLong(bucketId);
boolean isBucketOwner = false;
for (byte[] aBucket : acc.buckets) {
if ( ByteField.compareBytes(aBucket, bucketIdB)) {
isBucketOwner = true;
break;
}
}
if ( !isBucketOwner) {
throw new ApplicationFault("Tenant does not own the bucket.");
}
IndexWriter.getInstance().truncate(acc.APIKEY, bucketId);
res.writeXml("OK");
}
private void truncate(Request req, Response res)
throws ApplicationFault, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) throw new ApplicationFault("Account is not found");
IndexWriter.getInstance().truncate(acc.APIKEY);
res.writeXml(Account.getAccount(acc.APIKEY).toXml());
}
/**
* Get the index of the account for a given bucket
* @param req
* @param res
* @throws ApplicationFault
* @throws IOException
* @throws SystemFault
*/
private void getInvertedIndex(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
long bucketId = req.getLong("bucketid", true);
if ( null == acc) return;
boolean isMyBucket = false;
long allocatedBucketId = 0L;
for (byte[] bucketIdB : acc.buckets) {
allocatedBucketId = ByteField.getLong(0, bucketIdB);
if ( allocatedBucketId == bucketId) {
isMyBucket = true;
break;
}
}
if ( !isMyBucket) {
res.error("You do not own the requested bucket id." );
return;
}
PrintWriter pw = res.getWriter();
pw.write(Response.XML_VERSION_LINE);
res.writeHeader();
List<NVBytes> nvs = Account.get(bucketId);
if ( null == nvs) {
res.writeFooter();
return;
}
for (NVBytes nv : nvs) {
if ( null == nv.data) continue;
List<InvertedIndex> indexes = InvertedIndex.read(nv.data);
if ( null == indexes) continue;
for (InvertedIndex index : indexes) {
pw.write("<invindex>");
pw.write(index.toString());
pw.write("<invindex>");
}
}
res.writeFooter();
}
private void getDocumentTypeCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
DocumentType dt = DocumentType.getInstance();
Map<String, Byte> codes = dt.load(acc.name);
res.writeXml(dt.toXml(codes));
}
@SuppressWarnings("unchecked")
private void setDocumentTypeCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
Map<String,Byte> codes = (Map<String,Byte>) req.getObject("typecodes", true);
DocumentType dt = DocumentType.getInstance();
dt.persist(acc.name, codes);
res.writeXml("OK");
}
@SuppressWarnings("unchecked")
private void appendDocumentTypeCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
Map<String,Byte> codes = (Map<String,Byte>) req.getObject("typecodes", true);
DocumentType dt = DocumentType.getInstance();
dt.append(acc.name, codes);
res.writeXml("OK");
}
private void getTermTypeCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
TermType tt = TermType.getInstance(true);
Map<String, Byte> codes = tt.load(acc.name);
res.writeXml(tt.toXml(codes));
}
@SuppressWarnings("unchecked")
private void setTermTypeCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
Map<String,Byte> codes = (Map<String,Byte>) req.getObject("typecodes", true);
TermType dt = TermType.getInstance(true);
dt.persist(acc.name, codes);
res.writeXml("OK");
}
@SuppressWarnings("unchecked")
private void appendTermTypeCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
Map<String,Byte> codes = (Map<String,Byte>) req.getObject("typecodes", true);
TermType dt = TermType.getInstance(true);
dt.append(acc.name, codes);
res.writeXml("OK");
}
private void getWeightCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
WeightType wt = WeightType.getInstance(true);
Map<String, Byte> codes = wt.load(acc.name);
res.writeXml(wt.toXml(codes));
}
@SuppressWarnings("unchecked")
private void setWeightCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
Map<String,Byte> codes = (Map<String,Byte>) req.getObject("weightcodes", true);
WeightType wt = WeightType.getInstance(true);
wt.persist(acc.name, codes);
res.writeXml("OK");
}
@SuppressWarnings("unchecked")
private void appendWeightCodes(Request req, Response res)
throws ApplicationFault, IOException, SystemFault{
AccountInfo acc = Account.getActiveAccountInfo(req, res);
if ( null == acc) return;
Map<String,Byte> codes = (Map<String,Byte>) req.getObject("weightcodes", true);
WeightType wt = WeightType.getInstance(true);
wt.append(acc.name, codes);
res.writeXml("OK");
}
private void addStopwords(Request req, Response res) throws ApplicationFault, SystemFault{
String stopwords = req.getString("stopwords", true, true, true);
List<String> stopwordL = StringUtils.fastSplit(stopwords, ',');
StopwordManager.getInstance().setStopwords(stopwordL);
new StopwordRefresh().process(); //A 30mins job is done in sync mode
res.writeXml("OK");
}
private void getStopwords(Request req, Response res) throws ApplicationFault, SystemFault{
Set<String> words = StopwordManager.getInstance().getStopwords();
StringBuilder sb = new StringBuilder();
sb.append("<words>");
for (String word : words) {
sb.append(word).append('\n');
}
sb.append("</words>");
res.writeXml(sb.toString());
}
public boolean init(Configuration conf, ServiceMetaData meta) {
XmlUtils.xstream.alias("account", AccountInfo.class);
this.conf = conf;
this.secretKey = conf.get("privateKey", "E64FCAE0CBC836F034A0FE4BBF6726007FCAB08BE16EB729D92FE22A219FB7EC");
l.info("Hex code initialized.");
return true;
}
public void stop() {
}
public String getName() {
return "ManagementService";
}
}