docs: private void processLookup(final TSDB tsdb, final HttpQuery query, 
        final SearchQuery search_query) {
      if (search_query.getMetric() == null && 
          (search_query.getTags() == null || search_query.getTags().size() < 1)) {
        throw new BadRequestException(
            "Missing metric and tags. Please supply at least one value.");
      }
      final long start = System.currentTimeMillis();
      
      class MetricCB implements Callback<Object, String> {
        final Map<String, Object> series;
        MetricCB(final Map<String, Object> series) {
          this.series = series;
        }
        
        @Override
        public Object call(final String name) throws Exception {
          series.put("metric", name);
          return null;
        }
      }
      
      class TagsCB implements Callback<Object, HashMap<String, String>> {
        final Map<String, Object> series;
        TagsCB(final Map<String, Object> series) {
          this.series = series;
        }
        
        @Override
        public Object call(final HashMap<String, String> names) throws Exception {
          series.put("tags", names);
          return null;
        }
      }
      
      class Serialize implements Callback<Object, ArrayList<Object>> {
        final List<Object> results;
        Serialize(final List<Object> results) {
          this.results = results;
        }
        
        @Override
        public Object call(final ArrayList<Object> ignored) throws Exception {
          search_query.setResults(results);
          search_query.setTime(System.currentTimeMillis() - start);
          query.sendReply(query.serializer().formatSearchResultsV1(search_query));
          return null;
        }
      }
      
      class LookupCB implements Callback<Deferred<Object>, List<byte[]>> {
        @Override
        public Deferred<Object> call(final List<byte[]> tsuids) throws Exception {
          final List<Object> results = new ArrayList<Object>(tsuids.size());
          search_query.setTotalResults(tsuids.size());
          
          final ArrayList<Deferred<Object>> deferreds = 
              new ArrayList<Deferred<Object>>(tsuids.size());
          
          for (final byte[] tsuid : tsuids) {
            // has to be concurrent if the uid table is split across servers
            final Map<String, Object> series = 
                new ConcurrentHashMap<String, Object>(3);
            results.add(series);
            
            series.put("tsuid", UniqueId.uidToString(tsuid));
            byte[] metric_uid = Arrays.copyOfRange(tsuid, 0, TSDB.metrics_width());
            deferreds.add(tsdb.getUidName(UniqueIdType.METRIC, metric_uid)
                .addCallback(new MetricCB(series)));
            
            final List<byte[]> tag_ids = UniqueId.getTagPairsFromTSUID(tsuid);
            deferreds.add(Tags.resolveIdsAsync(tsdb, tag_ids)
                .addCallback(new TagsCB(series)));
          }
          
          return Deferred.group(deferreds).addCallback(new Serialize(results));
        }
      }
      
      class ErrCB implements Callback<Object, Exception> {
        @Override
        public Object call(final Exception e) throws Exception {
          if (e instanceof NoSuchUniqueId) {
            query.sendReply(HttpResponseStatus.NOT_FOUND,
                query.serializer().formatErrorV1(
                    new BadRequestException(HttpResponseStatus.NOT_FOUND, 
                "Unable to resolve one or more TSUIDs", (NoSuchUniqueId)e)));
          } else if (e instanceof NoSuchUniqueName) {
            query.sendReply(HttpResponseStatus.NOT_FOUND,
              query.serializer().formatErrorV1(
                new BadRequestException(HttpResponseStatus.NOT_FOUND, 
                "Unable to resolve one or more UIDs", (NoSuchUniqueName)e)));
          } else if (e instanceof DeferredGroupException) {
            final Throwable ex = Exceptions.getCause((DeferredGroupException)e);
            if (ex instanceof NoSuchUniqueId) {
              query.sendReply(HttpResponseStatus.NOT_FOUND,
                  query.serializer().formatErrorV1(
                      new BadRequestException(HttpResponseStatus.NOT_FOUND, 
                  "Unable to resolve one or more TSUIDs", (NoSuchUniqueId)ex)));
            } else if (ex instanceof NoSuchUniqueName) {
              query.sendReply(HttpResponseStatus.NOT_FOUND,
                query.serializer().formatErrorV1(
                  new BadRequestException(HttpResponseStatus.NOT_FOUND, 
                  "Unable to resolve one or more UIDs", (NoSuchUniqueName)ex)));
            } else {
              query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, 
                  query.serializer().formatErrorV1(
                      new BadRequestException(HttpResponseStatus.INTERNAL_SERVER_ERROR, 
                  "Unexpected exception", ex)));
            }
          } else {
            query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, 
                query.serializer().formatErrorV1(
                    new BadRequestException(HttpResponseStatus.INTERNAL_SERVER_ERROR, 
                "Unexpected exception", e)));
          }
          return null;
        }
      }
      
      new TimeSeriesLookup(tsdb, search_query).lookupAsync()
        .addCallback(new LookupCB())
        .addErrback(new ErrCB());
    }