/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.server.unification.pipeline.http;
import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static org.jboss.netty.handler.codec.http.HttpMethod.GET;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.helios.apmrouter.catalog.api.impl.DataServiceInterceptor;
import org.helios.apmrouter.logging.APMLogLevel;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* <p>Title: MetricAutoCompleteService</p>
* <p>Description: Http service to provide autocomplete responses to UI components</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.server.unification.pipeline.http.MetricAutoCompleteService</code></p>
*/
public class MetricAutoCompleteService extends AbstractHttpRequestHandler {
/** The hibernate session factory from wot we's gettin the autoz complete */
@Autowired(required=true)
protected SessionFactory sessionFactory = null;
/** The logging interceptor for hibernate queries, used if the logging level is DEBUG */
protected final DataServiceInterceptor dsi = new DataServiceInterceptor();
/**
* Creates a new MetricAutoCompleteService
*/
public MetricAutoCompleteService() {
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.unification.pipeline.http.HttpRequestHandler#handle(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent, org.jboss.netty.handler.codec.http.HttpRequest, java.lang.String)
*/
@Override
public void handle(ChannelHandlerContext ctx, MessageEvent e, HttpRequest request, String path) throws Exception {
if (request.getMethod() != GET) {
sendError(ctx, METHOD_NOT_ALLOWED);
return;
}
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
Map<String, List<String>> params = queryStringDecoder.getParameters();
byte[] resolved = resolveLookup(params).toString().getBytes();
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, resolved.length);
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/json");
response.setContent(ChannelBuffers.wrappedBuffer(resolved));
final Channel channel = ctx.getChannel();
ChannelFuture writeFuture = channel.write(response);
// Decide whether to close the connection or not.
if (writeFuture!=null && !isKeepAlive(request)) {
// Close the connection when the whole content is written out.
writeFuture.addListener(ChannelFutureListener.CLOSE);
}
}
/** Forward slash splitter pattern */
protected static final Pattern SLASH_SPLITTER = Pattern.compile("/");
/** An empty string array constant */
protected static final String[] EMPTY_STR_ARR = {};
/**
* Returns the split and trimmed string array from splitting the passed value.
* The array will not include any null or empty strings
* @param value The string value to split
* @return a [possibly empty] array of string
*/
protected static String[] trimmedSplit(CharSequence value) {
if(value==null) return EMPTY_STR_ARR;
String[] fragments = SLASH_SPLITTER.split(value);
if(fragments.length==0) return EMPTY_STR_ARR;
List<String> nonblank = new ArrayList<String>(fragments.length);
for(String s: fragments) {
if(s==null || s.trim().isEmpty()) continue;
nonblank.add(s.trim());
}
if(nonblank.isEmpty()) return EMPTY_STR_ARR;
return nonblank.toArray(new String[0]);
}
/**
* Reads the autocomplete query from the request and resolves the matching values from hibernate
* @param params The http request paramters
* @return A JSON object containing the resolved items
* @throws Exception thrown on any error
*/
protected Object resolveLookup(Map<String, List<String>> params) throws Exception {
Session session = null;
Query query = null;
try {
String current = params.get("term").get(0);
String path = (current==null || current.trim().isEmpty()) ? "" : current.trim();
while(path.startsWith("/")) {
path = path.substring(1);
}
JSONObject resp = new JSONObject();
resp.putOnce("matches", new JSONArray());
String[] fragments = trimmedSplit(path);
if(level.isEnabledFor(APMLogLevel.DEBUG )) {
session = sessionFactory.openSession(dsi);
} else {
session = sessionFactory.openSession();
}
switch (fragments.length) {
case 0: // looking for a domain
query = session.getNamedQuery("allDomains");
break;
case 1:
query = session.getNamedQuery("searchDomains");
query.setString("domain", fragments[0] + "%");
break;
case 2:
query = session.getNamedQuery("searchHosts");
query.setString("domain", fragments[0] + "%");
query.setString("host", fragments[1] + "%");
break;
case 3:
break;
default: // we have a domain, host and agent, and possible some namespaces
}
if(query==null) {
throw new Exception("Failed to parse term [" + path + "]");
}
for(Object obj: query.list()) {
Map<String, String> entry = new HashMap<String, String>(2);
entry.put("label", obj.toString());
entry.put("value", obj.toString());
resp.append("matches", entry);
}
//
info("AutoComplete Result for [", path, "]\n", resp.toString(2));
return resp.getJSONArray("matches");
} catch (Exception ex) {
error("Metric AutoComplete Lookup Failure ", ex);
JSONObject err = new JSONObject();
err.put("err", ex.toString());
return err;
} finally {
if(session!=null) try { session.close(); } catch (Exception ex) {}
}
}
}