1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.frontend.servlets;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Comparator;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.function.Function;
30
31 import javax.xml.transform.TransformerException;
32
33 import org.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.jdom2.Element;
36 import org.mycore.common.MCRException;
37 import org.mycore.common.MCRUtils;
38 import org.mycore.common.config.MCRConfiguration2;
39 import org.mycore.common.content.MCRJDOMContent;
40 import org.mycore.datamodel.classifications2.MCRCategLinkServiceFactory;
41 import org.mycore.datamodel.classifications2.MCRCategory;
42 import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
43 import org.mycore.datamodel.classifications2.MCRCategoryID;
44 import org.mycore.datamodel.classifications2.MCRLabel;
45 import org.xml.sax.SAXException;
46
47 import jakarta.servlet.http.HttpServletRequest;
48 import jakarta.servlet.http.HttpServletResponse;
49
50
51
52
53
54
55
56
57
58 public class MCRClassificationBrowser2 extends MCRServlet {
59 private static final long serialVersionUID = 1L;
60
61 private static final Logger LOGGER = LogManager.getLogger(MCRClassificationBrowser2.class);
62
63 protected MCRQueryAdapter getQueryAdapter(final String fieldName) {
64 MCRQueryAdapter adapter = MCRConfiguration2
65 .getOrThrow("MCR.Module-classbrowser.QueryAdapter", MCRConfiguration2::instantiateClass);
66 adapter.setFieldName(fieldName);
67 return adapter;
68 }
69
70 protected void configureQueryAdapter(MCRQueryAdapter queryAdapter, HttpServletRequest req) {
71 queryAdapter.configure(req);
72 }
73
74 @Override
75 public void doGetPost(MCRServletJob job) throws Exception {
76 LOGGER.info("ClassificationBrowser finished in {} ms.", MCRUtils.measure(() -> processRequest(job)).toMillis());
77 }
78
79 private void processRequest(MCRServletJob job) throws IOException, TransformerException, SAXException {
80 HttpServletRequest req = job.getRequest();
81 Settings settings = Settings.fromRequest(req);
82
83 LOGGER.info("ClassificationBrowser {}", settings);
84
85 MCRCategoryID id = settings.getCategID()
86 .map(categId -> new MCRCategoryID(settings.getClassifID(), categId))
87 .orElse(MCRCategoryID.rootID(settings.getClassifID()));
88 MCRCategory category = MCRCategoryDAOFactory.getInstance().getCategory(id, 1);
89 if (category == null) {
90 job.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find category: " + id);
91 return;
92 }
93 Element xml = getClassificationBrowserData(req, settings, category);
94 renderToHTML(job, settings, xml);
95 }
96
97 private Element getClassificationBrowserData(HttpServletRequest req, Settings settings,
98 MCRCategory category) {
99 Element xml = new Element("classificationBrowserData");
100 xml.setAttribute("classification", settings.getClassifID());
101 xml.setAttribute("webpage", settings.getWebpage());
102 settings.getParameters().ifPresent(p -> xml.setAttribute("parameters", p));
103
104 Optional<MCRQueryAdapter> queryAdapter = configureQueryAdapter(req, settings, xml);
105
106 Function<MCRCategoryID, String> toIdSearchValue = settings.addClassId() ? MCRCategoryID::toString
107 : MCRCategoryID::getID;
108 List<Element> data = new ArrayList<>();
109 for (MCRCategory child : category.getChildren()) {
110 queryAdapter.ifPresent(qa -> qa.setCategory(toIdSearchValue.apply(child.getId())));
111 long numResults = queryAdapter
112 .filter(qa -> settings.countResults())
113 .map(MCRQueryAdapter::getResultCount)
114 .orElse(0L);
115
116 if ((settings.removeEmptyLeaves()) && (numResults < 1)) {
117 continue;
118 }
119 if(settings.excludeCategories().contains(child.getId().getID())) {
120 continue;
121 }
122
123 Element categoryE = getCategoryElement(child, numResults, settings);
124 queryAdapter.ifPresent(qa -> categoryE.setAttribute("query", qa.getQueryAsString()));
125 data.add(categoryE);
126 }
127
128 String objectType = queryAdapter.map(MCRQueryAdapter::getObjectType).orElse(null);
129 countLinks(settings, objectType, category, data);
130 sortCategories(settings.getSortBy(), data);
131 xml.addContent(data);
132 return xml;
133 }
134
135 private Element getCategoryElement(MCRCategory category, long numResults,
136 Settings settings) {
137 Element categoryE = new Element("category");
138 if (settings.countResults()) {
139 categoryE.setAttribute("numResults", String.valueOf(numResults));
140 }
141
142 categoryE.setAttribute("id", category.getId().getID());
143 categoryE.setAttribute("children", Boolean.toString(category.hasChildren()));
144
145 if (settings.addUri() && (category.getURI() != null)) {
146 categoryE.addContent(new Element("uri").setText(category.getURI().toString()));
147 }
148
149 addLabel(settings, category, categoryE);
150 return categoryE;
151 }
152
153 private Optional<MCRQueryAdapter> configureQueryAdapter(HttpServletRequest req, Settings settings, Element xml) {
154 if (settings.countResults() || settings.getField().isPresent()) {
155 MCRQueryAdapter queryAdapter = getQueryAdapter(settings.getField().orElse(null));
156
157 configureQueryAdapter(queryAdapter, req);
158 if (queryAdapter.getObjectType() != null) {
159 xml.setAttribute("objectType", queryAdapter.getObjectType());
160 }
161 return Optional.of(queryAdapter);
162 }
163 return Optional.empty();
164 }
165
166
167
168
169
170 private void addLabel(Settings settings, MCRCategory child, Element category) {
171 MCRLabel label = child.getCurrentLabel()
172 .orElseThrow(() -> new MCRException("Category " + child.getId() + " has no labels."));
173
174 category.addContent(new Element("label").setText(label.getText()));
175
176
177 if (settings.addDescription() && (label.getDescription() != null)) {
178 category.addContent(new Element("description").setText(label.getDescription()));
179 }
180 }
181
182
183 private void countLinks(Settings settings, String objectType, MCRCategory category,
184 List<Element> data) {
185 if (!settings.countLinks()) {
186 return;
187 }
188 String objType = objectType;
189 if (objType != null && objType.trim().length() == 0) {
190 objType = null;
191 }
192
193 String classifID = category.getId().getRootID();
194 Map<MCRCategoryID, Number> count = MCRCategLinkServiceFactory.getInstance().countLinksForType(category,
195 objType, true);
196 for (Iterator<Element> it = data.iterator(); it.hasNext();) {
197 Element child = it.next();
198 MCRCategoryID childID = new MCRCategoryID(classifID, child.getAttributeValue("id"));
199 int num = (count.containsKey(childID) ? count.get(childID).intValue() : 0);
200 child.setAttribute("numLinks", String.valueOf(num));
201 if ((settings.removeEmptyLeaves()) && (num < 1)) {
202 it.remove();
203 }
204 }
205 }
206
207
208 private void sortCategories(String sortBy, List<Element> data) {
209 switch (sortBy) {
210 case "id":
211 data.sort(Comparator.comparing(e -> e.getAttributeValue("id")));
212 break;
213 case "label":
214 data.sort(Comparator.comparing(e -> e.getChildText("label"), String::compareToIgnoreCase));
215 break;
216 default:
217
218 }
219 }
220
221
222
223 private void renderToHTML(MCRServletJob job, Settings settings, Element xml)
224 throws IOException, TransformerException,
225 SAXException {
226 settings.getStyle()
227 .ifPresent(style -> job.getRequest().setAttribute("XSL.Style", style));
228 MCRServlet.getLayoutService().doLayout(job.getRequest(), job.getResponse(), new MCRJDOMContent(xml));
229 }
230
231 @Override
232 protected boolean allowCrossDomainRequests() {
233 return true;
234 }
235
236 public interface MCRQueryAdapter {
237 void setFieldName(String fieldname);
238
239 void setRestriction(String text);
240
241 void setCategory(String text);
242
243 String getObjectType();
244
245 void setObjectType(String text);
246
247 long getResultCount();
248
249 String getQueryAsString();
250
251 void configure(HttpServletRequest request);
252 }
253
254 private static class Settings {
255 private String classifID;
256
257 private String categID;
258
259 private boolean countResults;
260
261 private boolean addClassId;
262
263 private List<String> excludeCategories;
264
265 private boolean addUri;
266
267 private boolean removeEmptyLeaves;
268
269 private String webpage;
270
271 private String field;
272
273 private String parameters;
274
275 private boolean addDescription;
276
277 private boolean countLinks;
278
279 private String sortBy;
280
281 private String style;
282
283 static Settings fromRequest(HttpServletRequest req) {
284 Settings s = new Settings();
285 s.classifID = req.getParameter("classification");
286 s.categID = req.getParameter("category");
287 s.countResults = Boolean.parseBoolean(req.getParameter("countresults"));
288 s.addClassId = Boolean.parseBoolean(req.getParameter("addclassid"));
289 s.excludeCategories = Arrays.asList(
290 Optional.ofNullable(req.getParameter("excludeCategories")).orElse("").split(",")
291 );
292 s.addUri = Boolean.parseBoolean(req.getParameter("adduri"));
293 s.removeEmptyLeaves = !MCRUtils.filterTrimmedNotEmpty(req.getParameter("emptyleaves"))
294 .map(Boolean::valueOf)
295 .orElse(true);
296 s.webpage = req.getParameter("webpage");
297 s.field = req.getParameter("field");
298 s.parameters = req.getParameter("parameters");
299 s.addDescription = Boolean.parseBoolean(req.getParameter("adddescription"));
300 s.countLinks = Boolean.parseBoolean(req.getParameter("countlinks"));
301 s.sortBy = req.getParameter("sortby");
302 s.style = req.getParameter("style");
303 return s;
304 }
305
306 String getSortBy() {
307 return sortBy;
308 }
309
310 Optional<String> getStyle() {
311 return MCRUtils.filterTrimmedNotEmpty(style);
312 }
313
314 String getClassifID() {
315 return classifID;
316 }
317
318 Optional<String> getCategID() {
319 return MCRUtils.filterTrimmedNotEmpty(categID);
320 }
321
322 boolean countResults() {
323 return countResults;
324 }
325
326 boolean countLinks() {
327 return countLinks;
328 }
329
330 boolean addClassId() {
331 return addClassId;
332 }
333
334 List<String> excludeCategories() {
335 return excludeCategories;
336 }
337
338 boolean addUri() {
339 return addUri;
340 }
341
342 boolean removeEmptyLeaves() {
343 return removeEmptyLeaves;
344 }
345
346 boolean addDescription() {
347 return addDescription;
348 }
349
350 String getWebpage() {
351 return webpage;
352 }
353
354 Optional<String> getField() {
355 return MCRUtils.filterTrimmedNotEmpty(field);
356 }
357
358 Optional<String> getParameters() {
359 return MCRUtils.filterTrimmedNotEmpty(parameters);
360 }
361
362 @Override
363 public String toString() {
364 return "Settings{" +
365 "classifID='" + classifID + '\'' +
366 ", categID='" + categID + '\'' +
367 ", countResults=" + countResults +
368 ", addClassId=" + addClassId +
369 ", excludeCategories=" + excludeCategories +
370 ", addUri=" + addUri +
371 ", removeEmptyLeaves=" + removeEmptyLeaves +
372 ", webpage='" + webpage + '\'' +
373 ", field='" + field + '\'' +
374 ", parameters='" + parameters + '\'' +
375 ", addDescription=" + addDescription +
376 ", countLinks=" + countLinks +
377 ", sortBy='" + sortBy + '\'' +
378 ", style='" + style + '\'' +
379 '}';
380 }
381 }
382 }