Skip to content

Commit 2e7a34e

Browse files
committed
Get doc OK
1 parent d8b5a50 commit 2e7a34e

7 files changed

Lines changed: 246 additions & 153 deletions

File tree

web/src/main/java/com/github/oeuvres/alix/web/AlixServlet.java

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ public class AlixServlet extends HttpServlet
5353
private static final Gson GSON = new Gson();
5454
private static final Type MAP_TYPE = new TypeToken<Map<String, Object>>(){}.getType();
5555

56-
5756
/** Loaded indices, swapped atomically on reload. */
5857
private volatile Map<String, LuceneIndex> indices = Map.of();
5958

@@ -63,10 +62,6 @@ public class AlixServlet extends HttpServlet
6362
/** Resolved config directory path. */
6463
private Path configDir;
6564

66-
// ================================================================
67-
// Lifecycle
68-
// ================================================================
69-
7065
@Override
7166
public void init(final ServletConfig config) throws ServletException
7267
{
@@ -99,19 +94,11 @@ public void destroy()
9994
*/
10095
private void registerOps()
10196
{
102-
register(new OpResults());
103-
// register(new OpCooc());
104-
// register(new OpFreqs());
105-
register(new OpTerms());
106-
// register(new OpDoc());
97+
ops.put("results", new OpResults());
98+
ops.put("terms", new OpTerms());
99+
ops.put("doc", new OpDoc());
107100
}
108101

109-
private void register(final Op op) { ops.put(op.name(), op); }
110-
111-
// ================================================================
112-
// Request handling
113-
// ================================================================
114-
115102
@Override
116103
protected void doGet(
117104
final HttpServletRequest req,
@@ -143,19 +130,22 @@ protected void doGet(
143130
final String opName = opFormat[0];
144131
final String format = opFormat[1];
145132

146-
final Op op = ops.get(opName);
147-
if (op == null) {
148-
jsonError(resp, 404, "Unknown operation: " + opName);
133+
// known operation
134+
Op op = ops.get(opName);
135+
if (op != null) {
136+
op.dispatch(index, format, req, resp);
149137
return;
150138
}
139+
// fallback to doc content
140+
op = ops.get("doc");
141+
if (op != null && op.offer(index, opName, format, req, resp)) {
142+
return;
143+
}
144+
151145

152-
op.dispatch(index, format, req, resp);
146+
jsonError(resp, 404, "Unknown operation: " + opName);
153147
}
154148

155-
// ================================================================
156-
// Built-in endpoints
157-
// ================================================================
158-
159149
/**
160150
* {@code GET /} — list available indices.
161151
*/
@@ -240,10 +230,6 @@ static String[] splitOpFormat(final String segment)
240230
return new String[] { segment, null };
241231
}
242232

243-
// ================================================================
244-
// Index loading
245-
// ================================================================
246-
247233
private static Map<String, LuceneIndex> loadIndices(final Path configDir)
248234
{
249235
final Map<String, LuceneIndex> map = new LinkedHashMap<>();
@@ -295,10 +281,6 @@ private Path resolveConfigDir(final ServletConfig config) throws ServletExceptio
295281
return Path.of(webInf);
296282
}
297283

298-
// ================================================================
299-
// Error handling
300-
// ================================================================
301-
302284
/**
303285
* Send an error response as JSON.
304286
*

web/src/main/java/com/github/oeuvres/alix/web/Op.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package com.github.oeuvres.alix.web;
22

33
import static com.github.oeuvres.alix.web.Pars.END;
4+
import static com.github.oeuvres.alix.web.Pars.F;
5+
import static com.github.oeuvres.alix.web.Pars.Q;
6+
import static com.github.oeuvres.alix.web.Pars.SLOP;
7+
import static com.github.oeuvres.alix.web.Pars.SLOP_DEFAULT;
8+
import static com.github.oeuvres.alix.web.Pars.SLOP_RANGE;
49
import static com.github.oeuvres.alix.web.Pars.START;
510
import static com.github.oeuvres.alix.web.Pars.YEAR;
611

712
import java.io.IOException;
13+
import java.util.logging.Logger;
814

915
import org.apache.lucene.document.IntPoint;
16+
import org.apache.lucene.queries.spans.SpanQuery;
1017
import org.apache.lucene.search.Query;
1118

1219
import com.google.gson.stream.JsonWriter;
@@ -16,6 +23,7 @@
1623

1724
import com.github.oeuvres.alix.lucene.FlucYear;
1825
import com.github.oeuvres.alix.lucene.LuceneIndex;
26+
import com.github.oeuvres.alix.lucene.spans.SpanQueryParser;
1927
import com.github.oeuvres.alix.web.util.HttpPars;
2028

2129
/**
@@ -43,8 +51,18 @@
4351
*/
4452
public abstract class Op
4553
{
46-
/** Operation name, used for URL routing (e.g. "kwic", "cooc"). */
47-
public abstract String name();
54+
protected static final Logger LOG = Logger.getLogger(Op.class.getName());
55+
56+
57+
/**
58+
* Try to claim an unmatched path segment as a resource.
59+
* Returns {@code false} if this op cannot handle the segment;
60+
* the router retains 404 responsibility.
61+
*/
62+
public boolean offer(LuceneIndex index, String segment, String format,
63+
HttpServletRequest req, HttpServletResponse resp) throws IOException {
64+
return true;
65+
}
4866

4967
/**
5068
* Dispatch to the appropriate format method.
@@ -71,7 +89,7 @@ else switch (format) {
7189
case "jsonl" -> jsonl(index, req, resp);
7290
case "csv" -> csv(index, req, resp);
7391
default -> AlixServlet.jsonError(resp, 406,
74-
name() + ": unsupported format: " + format);
92+
getClass().getSimpleName() + ": unsupported format: " + format);
7593
}
7694
}
7795

@@ -81,35 +99,35 @@ else switch (format) {
8199
protected void page(LuceneIndex index,
82100
HttpServletRequest req, HttpServletResponse resp) throws IOException
83101
{
84-
AlixServlet.jsonError(resp, 406, name() + ": default html not implemented");
102+
AlixServlet.jsonError(resp, 406, getClass().getSimpleName() + ": default html not implemented");
85103
}
86104

87105
/** Structured JSON. */
88106
protected void json(LuceneIndex index,
89107
HttpServletRequest req, HttpServletResponse resp) throws IOException
90108
{
91-
AlixServlet.jsonError(resp, 406, name() + ": json not implemented");
109+
AlixServlet.jsonError(resp, 406, getClass().getSimpleName() + ": json not implemented");
92110
}
93111

94112
/** HTML fragment for streaming insertion. */
95113
protected void html(LuceneIndex index,
96114
HttpServletRequest req, HttpServletResponse resp) throws IOException
97115
{
98-
AlixServlet.jsonError(resp, 406, name() + ": html fragment not implemented");
116+
AlixServlet.jsonError(resp, 406, getClass().getSimpleName() + ": html fragment not implemented");
99117
}
100118

101119
/** JSON Lines — one object per line. */
102120
protected void jsonl(LuceneIndex index,
103121
HttpServletRequest req, HttpServletResponse resp) throws IOException
104122
{
105-
AlixServlet.jsonError(resp, 406, name() + ": jsonl not implemented");
123+
AlixServlet.jsonError(resp, 406, getClass().getSimpleName() + ": jsonl not implemented");
106124
}
107125

108126
/** CSV tabular export. */
109127
protected void csv(LuceneIndex index,
110128
HttpServletRequest req, HttpServletResponse resp) throws IOException
111129
{
112-
AlixServlet.jsonError(resp, 406, name() + ": csv not implemented");
130+
AlixServlet.jsonError(resp, 406, getClass().getSimpleName() + ": csv not implemented");
113131
}
114132

115133
// ---- response utilities ----
@@ -164,4 +182,21 @@ Query yearQuery(LuceneIndex index, HttpPars pars) throws IOException {
164182
if (start == end) return IntPoint.newExactQuery(YEAR, start);
165183
return IntPoint.newRangeQuery(YEAR, start, end);
166184
}
185+
186+
/**
187+
* Build a SpanQuery from parameters
188+
* @param index
189+
* @param pars
190+
* @return
191+
* @throws IOException
192+
*/
193+
SpanQuery spanQuery(LuceneIndex index, HttpPars pars) throws IOException {
194+
final String q = pars.getString(Q, null);
195+
if (q == null) return null;
196+
final String content = pars.getString(F, index.content());
197+
final int slop = pars.getInt(SLOP, SLOP_RANGE, SLOP_DEFAULT, SLOP);
198+
SpanQuery spanQuery = new SpanQueryParser(content, slop).parse(q);
199+
return spanQuery;
200+
}
201+
167202
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.github.oeuvres.alix.web;
2+
3+
import java.io.IOException;
4+
import java.io.Writer;
5+
import java.util.logging.Logger;
6+
7+
import org.apache.lucene.document.Document;
8+
import org.apache.lucene.index.StoredFields;
9+
import org.apache.lucene.index.Term;
10+
import org.apache.lucene.queries.spans.SpanQuery;
11+
import org.apache.lucene.search.ScoreDoc;
12+
import org.apache.lucene.search.TermQuery;
13+
import org.apache.lucene.search.TopDocs;
14+
15+
import com.github.oeuvres.alix.lucene.LuceneIndex;
16+
import com.github.oeuvres.alix.web.util.HttpPars;
17+
18+
import jakarta.servlet.http.HttpServletRequest;
19+
import jakarta.servlet.http.HttpServletResponse;
20+
21+
import static com.github.oeuvres.alix.common.Names.*;
22+
import static com.github.oeuvres.alix.web.Pars.*;
23+
24+
public class OpDoc extends Op
25+
{
26+
protected static final Logger LOG = Logger.getLogger(OpDoc.class.getName());
27+
28+
@Override
29+
public boolean offer(
30+
LuceneIndex index,
31+
String docName,
32+
String format,
33+
HttpServletRequest request,
34+
HttpServletResponse response) throws IOException
35+
{
36+
final TopDocs topDocs = index.searcher().search(
37+
new TermQuery(new Term(ALIX_ID, docName)), 10);
38+
ScoreDoc[] docs = topDocs.scoreDocs;
39+
if (docs.length < 1) {
40+
return false;
41+
}
42+
// if more than one, log it
43+
if (docs.length > 1) {
44+
LOG.warning(docName + ", more than 1 doc with this name in index.");
45+
}
46+
final int docId = docs[0].doc;
47+
// attached the docId as a request attribute
48+
request.setAttribute(DOCID, docId);
49+
dispatch(index, format, request, response);
50+
return true;
51+
}
52+
53+
@Override
54+
protected void page(LuceneIndex index, HttpServletRequest req, HttpServletResponse resp)
55+
throws IOException
56+
{
57+
58+
resp.setContentType("text/html; charset=UTF-8");
59+
Writer writer = resp.getWriter();
60+
writer.write("""
61+
<!DOCTYPE html>
62+
<html>
63+
<head>
64+
<title>Alix, document</title>
65+
</head>
66+
<body>
67+
""");
68+
html(index, req, resp);
69+
writer.write("""
70+
</body>
71+
</html>
72+
""");
73+
74+
}
75+
76+
@Override
77+
protected void html(LuceneIndex index, HttpServletRequest request, HttpServletResponse response)
78+
throws IOException
79+
{
80+
Writer writer = response.getWriter();
81+
final HttpPars pars = new HttpPars(request);
82+
final int docId = pars.getInt(DOCID, -1);
83+
if (docId == -1) {
84+
response.setStatus(404);
85+
writer
86+
.append("<p class=\"error\">")
87+
.append(DOCID)
88+
.append("=")
89+
.append(String.valueOf(docId))
90+
.append(" not found</p>")
91+
;
92+
93+
}
94+
final StoredFields storedFields = index.reader().storedFields();
95+
Document doc = storedFields.document(docId);
96+
String content = doc.get(index.content());
97+
SpanQuery spanQuery = spanQuery(index, pars);
98+
writer.write(content);
99+
}
100+
}

0 commit comments

Comments
 (0)