source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/GS2Construct.java@ 31546

Last change on this file since 31546 was 31546, checked in by ak19, 7 years ago

Minor spelling correction ahead of major commits to do with GS3 user comments.

  • Property svn:keywords set to Author Date Id Revision
File size: 42.2 KB
Line 
1/*
2 * GS2Construct.java
3 * Copyright (C) 2002 New Zealand Digital Library, http://www.nzdl.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19package org.greenstone.gsdl3.service;
20
21import java.io.BufferedWriter;
22import java.io.File;
23import java.io.FileWriter;
24import java.io.Serializable;
25import java.util.Collections;
26import java.util.Iterator;
27import java.util.Map.Entry;
28import java.util.HashMap;
29import java.util.Map;
30import java.util.Set;
31import java.util.regex.Matcher;
32import java.util.regex.Pattern;
33
34import org.apache.log4j.Logger;
35import org.greenstone.gsdl3.build.GS2PerlConstructor;
36import org.greenstone.gsdl3.build.GS2PerlListener;
37import org.greenstone.gsdl3.util.GSFile;
38import org.greenstone.gsdl3.util.GSParams;
39import org.greenstone.gsdl3.util.GSPath;
40import org.greenstone.gsdl3.util.GSStatus;
41import org.greenstone.gsdl3.util.GSXML;
42import org.greenstone.gsdl3.util.OID;
43import org.greenstone.gsdl3.util.SimpleCollectionDatabase;
44import org.greenstone.gsdl3.util.UserContext;
45import org.greenstone.gsdl3.util.XMLConverter;
46
47// https://developer.android.com/reference/org/json/JSONObject.html
48// https://developer.android.com/reference/org/json/JSONArray.html
49import org.json.JSONArray;
50import org.json.JSONException;
51import org.json.JSONObject;
52
53
54import org.w3c.dom.Document;
55import org.w3c.dom.Element;
56import org.w3c.dom.Node;
57import org.w3c.dom.Text;
58
59/**
60 * A Services class for building collections provides a wrapper around the old
61 * perl scripts
62 *
63 * @author Katherine Don
64 */
65public class GS2Construct extends ServiceRack
66{
67
68 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.service.GS2Construct.class.getName());
69
70 // default error message
71 private static final String NO_PERMISSIONS_ERROR = "This user does not have the required permissions to perform this action.";
72
73 // services offered
74 private static final String NEW_SERVICE = "NewCollection";
75 private static final String ADD_DOC_SERVICE = "AddDocument";
76 private static final String IMPORT_SERVICE = "ImportCollection";
77 private static final String BUILD_SERVICE = "BuildCollection";
78 private static final String ACTIVATE_SERVICE = "ActivateCollection";
79 private static final String BUILD_AND_ACTIVATE_SERVICE = "BuildAndActivateCollection";
80 private static final String DELETE_SERVICE = "DeleteCollection";
81 private static final String RELOAD_SERVICE = "ReloadCollection";
82 private static final String MODIFY_METADATA_SERVICE = "ModifyMetadata"; // set or remove metadata
83
84
85 // params used
86 private static final String COL_PARAM = "collection";
87 private static final String NEW_COL_TITLE_PARAM = "collTitle";
88 private static final String NEW_COL_ABOUT_PARAM = "collAbout";
89 private static final String CREATOR_PARAM = "creator";
90 private static final String NEW_FILE_PARAM = "newfile";
91 private static final String PROCESS_ID_PARAM = GSParams.PROCESS_ID;
92 private static final String BUILDTYPE_PARAM = "buildType";
93 private static final String BUILDTYPE_MG = "mg";
94 private static final String BUILDTYPE_MGPP = "mgpp";
95
96 protected static String DATABASE_TYPE = null;
97 protected SimpleCollectionDatabase coll_db = null;
98
99 // the list of the collections - store between some method calls
100 private String[] collection_list = null;
101
102 // set of listeners for any construction commands
103 protected Map<String, GS2PerlListener> listeners = null;
104 protected HashMap<String, Boolean> collectionOperationMap = new HashMap<String, Boolean>();
105
106 public GS2Construct()
107 {
108 this.listeners = Collections.synchronizedMap(new HashMap<String, GS2PerlListener>());
109 }
110
111 /** returns a specific service description */
112 protected Element getServiceDescription(Document doc, String service, String lang, String subset)
113 {
114
115 Element description = doc.createElement(GSXML.SERVICE_ELEM);
116 description.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
117 description.setAttribute(GSXML.NAME_ATT, service);
118 if (subset == null || subset.equals(GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER))
119 {
120 description.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_NAME, getTextString(service + ".name", lang)));
121 description.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_DESCRIPTION, getTextString(service + ".description", lang)));
122 description.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_SUBMIT, getTextString(service + ".submit", lang)));
123 }
124 if (subset == null || subset.equals(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER))
125 {
126 Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
127 description.appendChild(param_list);
128
129 if (service.equals(NEW_SERVICE))
130 {
131
132 Element param = GSXML.createParameterDescription(doc, NEW_COL_TITLE_PARAM, getTextString("param." + NEW_COL_TITLE_PARAM, lang), GSXML.PARAM_TYPE_STRING, null, null, null);
133 param_list.appendChild(param);
134 param = GSXML.createParameterDescription(doc, CREATOR_PARAM, getTextString("param." + CREATOR_PARAM, lang), GSXML.PARAM_TYPE_STRING, null, null, null);
135 param_list.appendChild(param);
136 param = GSXML.createParameterDescription(doc, NEW_COL_ABOUT_PARAM, getTextString("param." + NEW_COL_ABOUT_PARAM, lang), GSXML.PARAM_TYPE_TEXT, null, null, null);
137 param_list.appendChild(param);
138 String[] types = { BUILDTYPE_MGPP, BUILDTYPE_MG };
139 String[] type_texts = { getTextString("param." + BUILDTYPE_PARAM + "." + BUILDTYPE_MGPP, lang), getTextString("param." + BUILDTYPE_PARAM + "." + BUILDTYPE_MG, lang) };
140
141 param = GSXML.createParameterDescription(doc, BUILDTYPE_PARAM, getTextString("param." + BUILDTYPE_PARAM, lang), GSXML.PARAM_TYPE_ENUM_SINGLE, BUILDTYPE_MGPP, types, type_texts);
142 param_list.appendChild(param);
143 }
144 else if (service.equals(ACTIVATE_SERVICE) || service.equals(IMPORT_SERVICE) || service.equals(BUILD_SERVICE) || service.equals(RELOAD_SERVICE) || service.equals(DELETE_SERVICE) || service.equals(MODIFY_METADATA_SERVICE))
145 {
146
147 this.collection_list = getCollectionList();
148 Element param = GSXML.createParameterDescription(doc, COL_PARAM, getTextString("param." + COL_PARAM, lang), GSXML.PARAM_TYPE_ENUM_SINGLE, null, this.collection_list, this.collection_list);
149 param_list.appendChild(param);
150 }
151 else
152 {
153 // invalid service name
154 return null;
155 }
156 }
157 return description;
158 }
159
160 // each service must have a method "process<New service name>"
161
162 protected Element processNewCollection(Element request)
163 {
164 if (!userHasCollectionEditPermissions(request)) {
165 return errorResponse("processNewCollection", NO_PERMISSIONS_ERROR);
166 }
167 return runCommand(request, GS2PerlConstructor.NEW);
168 }
169
170 /** TODO:implement this */
171 protected Element processAddDocument(Element request)
172 {
173 if (!userHasCollectionEditPermissions(request)) {
174 return errorResponse("processAddDocument", NO_PERMISSIONS_ERROR);
175 }
176
177 Document result_doc = XMLConverter.newDOM();
178 // decode the file name, add it to the import directory
179 String name = GSPath.getFirstLink(request.getAttribute(GSXML.TO_ATT));
180 Element response = result_doc.createElement(GSXML.RESPONSE_ELEM);
181 response.setAttribute(GSXML.FROM_ATT, name);
182 Element status = result_doc.createElement(GSXML.STATUS_ELEM);
183 response.appendChild(status);
184 //String lang = request.getAttribute(GSXML.LANG_ATT);
185 //String request_type = request.getAttribute(GSXML.TYPE_ATT);
186 Text t = result_doc.createTextNode("AddDocument: not implemented yet");
187 status.appendChild(t);
188 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
189 return response;
190 }
191
192 protected Element processBuildAndActivateCollection(Element request)
193 {
194 // check permissions
195 if (!userHasCollectionEditPermissions(request)) {
196 return errorResponse("processBuildAndActivateCollection", NO_PERMISSIONS_ERROR);
197 }
198
199
200 waitUntilReady(request);
201 Element buildResponse = processBuildCollection(request);
202 if (buildResponse.getElementsByTagName(GSXML.ERROR_ELEM).getLength() > 0)
203 {
204 signalReady(request);
205 return buildResponse;
206 }
207
208 Element statusElem = (Element) buildResponse.getElementsByTagName(GSXML.STATUS_ELEM).item(0);
209 String id = statusElem.getAttribute("pid");
210
211 GS2PerlListener currentListener = this.listeners.get(id);
212 int statusCode = currentListener.getStatus();
213 while (!GSStatus.isCompleted(statusCode))
214 {
215 // wait for the process, and keep checking the status code
216 // there is probably a better way to do this.
217 try
218 {
219 Thread.currentThread().sleep(100);
220 }
221 catch (Exception e)
222 { // ignore
223 }
224 statusCode = currentListener.getStatus();
225 }
226
227 Element activateResponse = processActivateCollection(request);
228 signalReady(request);
229 return activateResponse;
230 }
231
232 protected Element processImportCollection(Element request)
233 {
234 if (!userHasCollectionEditPermissions(request)) {
235 return errorResponse("processImportCollection", NO_PERMISSIONS_ERROR);
236 }
237
238 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
239 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
240
241 if (params == null)
242 {
243 return null;
244 }
245
246 //If we have been requested to only build certain documents then we need to create a manifest file
247 String documentsParam = (String) params.get("documents");
248 if (documentsParam != null && !documentsParam.equals(""))
249 {
250 String s = File.separator;
251 String manifestFolderPath = this.site_home + s + "collect" + s + params.get(COL_PARAM) + s + "manifests";
252 String manifestFilePath = manifestFolderPath + File.separator + "tempManifest.xml";
253
254 File manifestFolderFile = new File(manifestFolderPath);
255 if (!manifestFolderFile.exists())
256 {
257 manifestFolderFile.mkdirs();
258 }
259
260 File manifestFile = new File(manifestFilePath);
261 if (!manifestFile.exists())
262 {
263 try
264 {
265 manifestFile.createNewFile();
266 }
267 catch (Exception ex)
268 {
269 ex.printStackTrace();
270 return null; //Probably should return an actual error
271 }
272 }
273 String[] docList = documentsParam.split(",");
274
275 try
276 {
277 BufferedWriter bw = new BufferedWriter(new FileWriter(manifestFile));
278 bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
279 bw.write("<Manifest>\n");
280 bw.write(" <Index>\n");
281 for (int j = 0; j < docList.length; j++)
282 {
283 bw.write(" <Filename>" + docList[j] + "</Filename>\n");
284 }
285 bw.write(" </Index>\n");
286 bw.write("</Manifest>\n");
287 bw.close();
288 }
289 catch (Exception ex)
290 {
291 ex.printStackTrace();
292 return null; //Probably should return an actual error
293 }
294 }
295
296 return runCommand(request, GS2PerlConstructor.IMPORT);
297 }
298
299 protected Element processBuildCollection(Element request)
300 {
301 if (!userHasCollectionEditPermissions(request)) {
302 return errorResponse("processBuildCollection", NO_PERMISSIONS_ERROR);
303 }
304
305 return runCommand(request, GS2PerlConstructor.BUILD);
306 }
307
308 protected Element processModifyMetadata(Element request)
309 {
310
311 // There are two types of operations whereby metadata gets modified:
312 // - document including document-meta editing: user needs document editing powers
313 // - adding user comments: user just needs an account and needs to be logged in
314 // We handle both cases in this service.
315
316 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
317 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
318
319 String metaserver_command = (String) params.get("a"); // e.g. set-archives-metadata or set-metadata-array
320 boolean supportsSettingMultipleMeta = metaserver_command.equals("set-metadata-array") ? true : false;
321 String json_str = (String) params.get("json"); // String or null if no param named "json"
322
323 String[] docids = null;
324
325
326 if (userHasCollectionEditPermissions(request, params)) { // means user can modify ANY metadata
327
328 // if dealing with an array of meta, then parse out the docids from the json
329 if(supportsSettingMultipleMeta) {
330 docids = getDocIdsWithOptFilter(json_str, null);
331 } // else set-meta operation on single metadata field of single doc,
332 // and docid will be obtained in runCommand() where it's needed
333
334 } else {
335 // check if user logged in
336 // shouldn't be able to do any meta modification if not logged in
337
338 UserContext context = new UserContext(request);
339 if (context.getUsername().equals("")) {
340
341 return errorResponse("processModifyMetadata", "Cannot modify any metadata when not logged in.");
342 } else { // User is logged in at least, see whether they can do any restricted set-meta ops
343 // that are open to regular users (those without permissions to edit this collection).
344 // For now, there's only one restricted set-meta operation open to any logged in users
345 // who don't otherwise have editing permissions for the collection: adding user comments.
346
347 boolean isAddingUserComments = false;
348 Pattern allowedMetaFieldsPattern = Pattern.compile("^(username|usertimestamp|usercomment)$");
349 if(supportsSettingMultipleMeta) {
350
351 docids = getDocIdsWithOptFilter(json_str, allowedMetaFieldsPattern);
352 if(docids != null) {
353 isAddingUserComments = true;
354 }
355 } else {
356 String metaname = (String) params.get("metaname");
357 if(isAllowedToSetMeta(metaname, allowedMetaFieldsPattern)) {
358 isAddingUserComments = true;
359 }
360 }
361
362 if(!isAddingUserComments) { // logged in user is attempting to set meta outside restricted set,
363 // In this case, they're attempting to set meta not related to user comments
364 return errorResponse("processModifyMetadata", NO_PERMISSIONS_ERROR);
365 }
366 }
367 }
368
369 // wait until we can reserve the collection for processing
370 waitUntilReady(request);
371
372
373 // process
374 Element response = runCommand(request, GS2PerlConstructor.MODIFY_METADATA_SERVER, docids);
375
376 if (response.getElementsByTagName(GSXML.ERROR_ELEM).getLength() <= 0) // if no errors, wait for process to finish
377 {
378 Element statusElem = (Element) response.getElementsByTagName(GSXML.STATUS_ELEM).item(0);
379 String id = statusElem.getAttribute("pid");
380
381 GS2PerlListener currentListener = this.listeners.get(id);
382 int statusCode = currentListener.getStatus();
383 while (!GSStatus.isCompleted(statusCode))
384 {
385 // wait for the process, and keep checking the status code
386 // there is probably a better way to do this.
387 try
388 {
389 Thread.currentThread().sleep(100);
390 }
391 catch (Exception e)
392 { // ignore
393 }
394 statusCode = currentListener.getStatus();
395 }
396 }
397
398 Element statusElem = (Element) response.getElementsByTagName(GSXML.STATUS_ELEM).item(0);
399 String statusString = GSXML.getNodeText(statusElem);
400 statusString += " and monitored until done.";
401 GSXML.setNodeText(statusElem, statusString);
402
403 // release hold on collection
404 signalReady(request);
405 return response;
406
407 }
408
409 protected Element processActivateCollection(Element request)
410 {
411
412 if (!userHasCollectionEditPermissions(request)) {
413 return errorResponse("processActivateCollection", NO_PERMISSIONS_ERROR);
414 }
415
416 // this activates the collection on disk. but now we need to tell
417 // the MR about it. but we have to wait until the process is finished.
418 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
419 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
420 String coll_name = (String) params.get(COL_PARAM);
421 String lang = request.getAttribute(GSXML.LANG_ATT);
422
423 UserContext userContext = new UserContext(request);
424 String request_type = request.getAttribute(GSXML.TYPE_ATT);
425
426 // now we de-activate the collection before running activate.pl, and then re-activate at end
427 // So activate.pl only does the moving, no activation. This way will prevent java from launching
428 // perl, exiting and then leaving dangling file handles (on index/text/col.gdb) in perl.
429 if (!request_type.equals(GSXML.REQUEST_TYPE_STATUS)) {
430 systemRequest("delete", coll_name, null, userContext); // deactivate collection
431 }
432
433 Element response = runCommand(request, GS2PerlConstructor.ACTIVATE); // if request is for STATUS, then this won't run activate.pl
434
435 Element status = (Element) GSXML.getChildByTagName(response, GSXML.STATUS_ELEM);
436
437 // check for finished
438 int status_code = Integer.parseInt(status.getAttribute(GSXML.STATUS_ERROR_CODE_ATT));
439 if (GSStatus.isCompleted(status_code) && GSStatus.isError(status_code))
440 {
441 // we shouldn't carry out the next bit, just return the response
442 return response;
443 }
444 String id = status.getAttribute(GSXML.STATUS_PROCESS_ID_ATT);
445 GS2PerlListener listener = this.listeners.get(id);
446 if (listener == null)
447 {
448 logger.error("somethings gone wrong, couldn't find the listener");
449 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
450 return response;
451 }
452
453 while (!GSStatus.isCompleted(status_code))
454 {
455 // wait for the process, and keep checking the status code
456 // there is probably a better way to do this.
457 try
458 {
459 Thread.currentThread().sleep(100);
460 }
461 catch (Exception e)
462 { // ignore
463 }
464 status_code = listener.getStatus();
465 }
466
467 // add the rest of the messages to the status node
468 Text t = status.getOwnerDocument().createTextNode("\n" + listener.getUpdate());
469 status.appendChild(t);
470 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(listener.getStatus()));
471 if (GSStatus.isError(status_code))
472 {
473 return response; // without doing the next bit
474 }
475
476 t = status.getOwnerDocument().createTextNode("\n");
477 status.appendChild(t);
478 // once have got here, we assume
479 // the first bit proceeded successfully, now reload the collection (sends a collection reactivation request)
480 systemRequest("reload", coll_name, status, userContext); // this will append more messages to the status, and overwrite the error code att
481 return response;
482
483 }
484
485 protected Element processDeleteCollection(Element request)
486 {
487 if (!userHasCollectionEditPermissions(request)) {
488 return errorResponse("processDeleteCollection", NO_PERMISSIONS_ERROR);
489 }
490
491 Document result_doc = XMLConverter.newDOM();
492 // the response to send back
493 String name = GSPath.getFirstLink(request.getAttribute(GSXML.TO_ATT));
494 Element response = result_doc.createElement(GSXML.RESPONSE_ELEM);
495 response.setAttribute(GSXML.FROM_ATT, name);
496 Element status = result_doc.createElement(GSXML.STATUS_ELEM);
497 response.appendChild(status);
498 Text t = null; // the text node for the error/success message
499 String lang = request.getAttribute(GSXML.LANG_ATT);
500 String request_type = request.getAttribute(GSXML.TYPE_ATT);
501
502 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
503 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
504
505 boolean get_status_only = false;
506 if (request_type.equals(GSXML.REQUEST_TYPE_STATUS))
507 {
508 get_status_only = true;
509 }
510 if (get_status_only)
511 {
512 // at the moment, delete is synchronous. but it may take ages so should do the command in another thread maybe? in which case we will want to ask for status
513 logger.error("had a status request for delete - this shouldn't happen!!");
514 //t = result_doc.createTextNode("");
515 //status.appendChild(t);
516 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
517 return response;
518 }
519 String coll_name = (String) params.get(COL_PARAM);
520 String[] args = { coll_name };
521 File coll_dir = new File(GSFile.collectionBaseDir(this.site_home, coll_name));
522 // check that the coll is there in the first place
523 if (!coll_dir.exists())
524 {
525 t = result_doc.createTextNode(getTextString("delete.exists_error", lang, args));
526 status.appendChild(t);
527 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
528 return response;
529 }
530
531 // try to delete the directory
532 if (!GSFile.deleteFile(coll_dir))
533 {
534 t = result_doc.createTextNode(getTextString("delete.delete_error", lang, args));
535 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
536 status.appendChild(t);
537 return response;
538 }
539
540 UserContext userContext = new UserContext(request);
541
542 systemRequest("delete", coll_name, status, userContext);
543 return response;
544 }
545
546 protected Element processReloadCollection(Element request)
547 {
548 if (!userHasCollectionEditPermissions(request)) {
549 return errorResponse("processReloadCollection", NO_PERMISSIONS_ERROR);
550 }
551
552 Document result_doc = XMLConverter.newDOM();
553 // the response to send back
554 String name = GSPath.getFirstLink(request.getAttribute(GSXML.TO_ATT));
555 Element response = result_doc.createElement(GSXML.RESPONSE_ELEM);
556 response.setAttribute(GSXML.FROM_ATT, name);
557 Element status = result_doc.createElement(GSXML.STATUS_ELEM);
558 response.appendChild(status);
559 Text t = null; // the text node for the error/success message
560
561 String lang = request.getAttribute(GSXML.LANG_ATT);
562 String request_type = request.getAttribute(GSXML.TYPE_ATT);
563
564 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
565 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
566
567 boolean get_status_only = false;
568 if (request_type.equals(GSXML.REQUEST_TYPE_STATUS))
569 {
570 get_status_only = true;
571 }
572 if (get_status_only)
573 {
574 // reload is synchronous - this makes no sense
575 logger.error("had a status request for reload - this shouldn't happen!!");
576 //t = result_doc.createTextNode("");
577 //status.appendChild(t);
578 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
579 return response;
580 }
581
582 String coll_name = (String) params.get(COL_PARAM);
583
584 UserContext userContext = new UserContext(request);
585
586 systemRequest("reload", coll_name, status, userContext);
587 return response;
588
589 }
590
591 /**
592 * send a configure request to the message router action name should be
593 * "delete" or "reload" response will be put into the status element
594 */
595 protected void systemRequest(String operation, String coll_name, Element status, UserContext userContext)
596 {
597 // send the request to the MR
598 Document doc = XMLConverter.newDOM();
599 Element message = doc.createElement(GSXML.MESSAGE_ELEM);
600 Element request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_SYSTEM, "", userContext);
601 message.appendChild(request);
602 Element command = doc.createElement(GSXML.SYSTEM_ELEM);
603 request.appendChild(command);
604 command.setAttribute(GSXML.SYSTEM_MODULE_TYPE_ATT, GSXML.COLLECTION_ELEM);
605 command.setAttribute(GSXML.SYSTEM_MODULE_NAME_ATT, coll_name);
606
607 if (operation.equals("delete"))
608 {
609 command.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_DEACTIVATE);
610 }
611 else if (operation.equals("reload"))
612 {
613 command.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_ACTIVATE);
614 }
615 else
616 {
617 logger.error("invalid action name passed to systemRequest:" + operation);
618 return;
619 }
620 request.appendChild(command);
621 Node response = this.router.process(message); // at the moment, get no info in response so ignore it
622 Text t;
623 String[] args = { coll_name };
624
625 if (status != null)
626 {
627 if (response == null)
628 {
629 t = status.getOwnerDocument().createTextNode(getTextString(operation + ".configure_error", userContext.getLanguage(), args));
630 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
631 status.appendChild(t);
632 return;
633 }
634
635 // if we got here, we have succeeded!
636 t = status.getOwnerDocument().createTextNode(getTextString(operation + ".success", userContext.getLanguage(), args));
637 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.SUCCESS));
638 status.appendChild(t);
639 }
640 }
641
642 /**
643 * configure the service module for now, all services have type=build - need
644 * to think about this
645 */
646 public boolean configure(Element info, Element extra_info)
647 {
648 if (!super.configure(info, extra_info))
649 {
650 return false;
651 }
652
653 logger.info("configuring GS2Construct");
654
655 Element e = null;
656 // hard code in the services for now
657
658 // set up short_service_info_ - for now just has name and type
659
660 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
661 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
662 e.setAttribute(GSXML.NAME_ATT, NEW_SERVICE);
663 this.short_service_info.appendChild(e);
664
665 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
666 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
667 e.setAttribute(GSXML.NAME_ATT, IMPORT_SERVICE);
668 this.short_service_info.appendChild(e);
669
670 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
671 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
672 e.setAttribute(GSXML.NAME_ATT, BUILD_SERVICE);
673 this.short_service_info.appendChild(e);
674
675 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
676 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
677 e.setAttribute(GSXML.NAME_ATT, ACTIVATE_SERVICE);
678 this.short_service_info.appendChild(e);
679
680 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
681 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
682 e.setAttribute(GSXML.NAME_ATT, BUILD_AND_ACTIVATE_SERVICE);
683 this.short_service_info.appendChild(e);
684
685 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
686 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
687 e.setAttribute(GSXML.NAME_ATT, DELETE_SERVICE);
688 this.short_service_info.appendChild(e);
689
690 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
691 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
692 e.setAttribute(GSXML.NAME_ATT, RELOAD_SERVICE);
693 this.short_service_info.appendChild(e);
694
695 //e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
696 //e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
697 //e.setAttribute(GSXML.NAME_ATT, ADD_DOC_SERVICE);
698 //this.short_service_info.appendChild(e);
699
700 e = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
701 e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
702 e.setAttribute(GSXML.NAME_ATT, MODIFY_METADATA_SERVICE);
703 this.short_service_info.appendChild(e);
704
705 return true;
706 }
707
708 protected Element runCommand(Element request, int type) {
709 return runCommand(request, type, null);
710 }
711
712 /** returns a response element */
713 protected Element runCommand(Element request, int type, String[] docids)
714 {
715 Document result_doc = XMLConverter.newDOM();
716 // the response to send back
717 String name = GSPath.getFirstLink(request.getAttribute(GSXML.TO_ATT));
718 Element response = result_doc.createElement(GSXML.RESPONSE_ELEM);
719 response.setAttribute(GSXML.FROM_ATT, name);
720 Element status = result_doc.createElement(GSXML.STATUS_ELEM);
721 response.appendChild(status);
722
723 String lang = request.getAttribute(GSXML.LANG_ATT);
724 String request_type = request.getAttribute(GSXML.TYPE_ATT);
725
726 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
727 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
728
729 boolean get_status_only = false;
730 if (request_type.equals(GSXML.REQUEST_TYPE_STATUS))
731 {
732 get_status_only = true;
733 }
734
735 // just check for status messages if that's all that's required
736 if (get_status_only)
737 {
738 String id = (String) params.get(PROCESS_ID_PARAM);
739 status.setAttribute(GSXML.STATUS_PROCESS_ID_ATT, id);
740 GS2PerlListener listener = this.listeners.get(id);
741 if (listener == null)
742 {
743 Text t = result_doc.createTextNode(getTextString("general.process_id_error", lang));
744 status.appendChild(t);
745 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
746 }
747 else
748 {
749 Text t = result_doc.createTextNode(listener.getUpdate());
750 status.appendChild(t);
751 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(listener.getStatus()));
752 // check that we actually should be removing the listener here
753 if (listener.isFinished())
754 { // remove this listener - its job is done
755 this.listeners.remove(id); // not working
756 }
757 }
758 return response;
759
760 }
761
762 // do the actual command
763 String coll_name = null;
764 if (type == GS2PerlConstructor.NEW)
765 {
766 String coll_title = (String) params.get(NEW_COL_TITLE_PARAM);
767 coll_name = createNewCollName(coll_title);
768 }
769 else
770 {
771 coll_name = (String) params.get(COL_PARAM);
772 }
773
774 // makes a paramList of the relevant params
775 Element other_params = extractOtherParams(params, type);
776
777 //create the constructor to do the work
778 GS2PerlConstructor constructor = new GS2PerlConstructor("perl_build");
779 if (!constructor.configure())
780 {
781 Text t = result_doc.createTextNode(getTextString("general.configure_constructor_error", lang));
782 status.appendChild(t);
783 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
784 return response;
785 }
786
787 constructor.setSiteHome(this.site_home);
788 constructor.setCollectionName(coll_name);
789 constructor.setActionType(type);
790 constructor.setProcessParams(other_params);
791 if (type == GS2PerlConstructor.IMPORT)
792 {
793 constructor.setManifestFile(this.site_home + File.separator + "collect" + File.separator + params.get(COL_PARAM) + File.separator + "manifests" + File.separator + "tempManifest.xml");
794 }
795 else if (type == GS2PerlConstructor.MODIFY_METADATA_SERVER) {
796 StringBuffer querystring = new StringBuffer();
797
798 // convert params into a single string again?
799 Set<Map.Entry<String, Serializable>> entries = params.entrySet();
800 Iterator<Map.Entry<String, Serializable>> i = entries.iterator();
801
802 String oid = null;
803
804 while (i.hasNext()) {
805
806 Map.Entry<String, Serializable> entry = i.next();
807 String paramname = entry.getKey();
808 paramname = paramname.replace("s1.", ""); // replaces all occurrences
809 if (paramname.equals("collection")) {
810 paramname = "c";
811 }
812 if (paramname.equals("d")){
813 oid = (String) entry.getValue();
814 }
815
816 String paramvalue = (String) entry.getValue();
817
818 querystring.append(paramname + "=" + paramvalue);
819 if (i.hasNext()) {
820 querystring.append("&");
821 }
822 }
823
824 if(oid != null) { // if we have only one oid
825 markDocumentInFlatDatabase("R", coll_name, OID.getTop(oid));
826 } else if (docids != null) { // check if we are dealing with many doc ids, as cold in theory happen when set-metadata-array is called
827
828 for(int index = 0; index < docids.length; index++) {
829 String docid = docids[index];
830 markDocumentInFlatDatabase("R", coll_name, OID.getTop(docid));
831 }
832 } else {
833 String msg = getTextString("general.no_valid_docid_error", lang);
834 logger.error("*** " + msg);
835 Text t = result_doc.createTextNode(msg);
836 status.appendChild(t);
837 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
838 return response;
839 }
840
841 constructor.setQueryString(querystring.toString());
842 }
843
844 GS2PerlListener listener = new GS2PerlListener();
845 constructor.addListener(listener);
846 constructor.start();
847
848 String id = newID();
849 this.listeners.put(id, listener);
850
851 status.setAttribute(GSXML.STATUS_PROCESS_ID_ATT, id);
852 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ACCEPTED));
853 Text t = result_doc.createTextNode(getTextString("general.process_start", lang));
854 status.appendChild(t);
855 return response;
856 }
857
858 //************************
859 // some helper functions
860 //************************
861
862 /** parse the collect directory and return a list of collection names */
863 protected String[] getCollectionList()
864 {
865
866 File collectDir = new File(GSFile.collectDir(this.site_home));
867 if (!collectDir.exists())
868 {
869 logger.error("couldn't find collect dir: " + collectDir.toString());
870 return null;
871 }
872 logger.info("GS2Construct: reading thru directory " + collectDir.getPath() + " to find collections.");
873 File[] contents = collectDir.listFiles();
874 int num_colls = 0;
875 for (int i = 0; i < contents.length; i++)
876 {
877 if (contents[i].isDirectory() && !contents[i].getName().startsWith("CVS"))
878 {
879 num_colls++;
880 }
881 }
882
883 String[] names = new String[num_colls];
884
885 for (int i = 0, j = 0; i < contents.length; i++)
886 {
887 if (contents[i].isDirectory())
888 {
889 String colName = contents[i].getName();
890 if (!colName.startsWith("CVS"))
891 {
892 names[j] = colName;
893 j++;
894 }
895
896 }
897 }
898
899 return names;
900
901 }
902
903 /** ids used for process id */
904 private int current_id = 0;
905
906 private String newID()
907 {
908 current_id++;
909 return Integer.toString(current_id);
910 }
911
912 /** creates a new short name from the collection title */
913 protected String createNewCollName(String coll_title)
914 {
915
916 String base_name = null;
917 // take the first 6 letters
918 if (coll_title.length() < 6)
919 {
920 base_name = coll_title;
921 }
922 else
923 {
924 base_name = coll_title.substring(0, 6);
925 }
926 File coll_dir = new File(GSFile.collectionBaseDir(this.site_home, base_name));
927 if (!coll_dir.exists())
928 { // this name is ok - not used yet
929 return base_name;
930 }
931
932 // now we have to make a new name until we get a good one
933 // try name1, name2 name3 etc
934 int i = 0;
935 while (coll_dir.exists())
936 {
937 i++;
938 coll_dir = new File(GSFile.collectionBaseDir(this.site_home, base_name + Integer.toString(i)));
939 }
940 return base_name + Integer.toString(i);
941
942 }
943
944 /**
945 * takes the params from the request (in the HashMap) and extracts any that
946 * need to be passed to the constructor and puts them into a paramList
947 * element
948 */
949 protected Element extractOtherParams(HashMap<String, Serializable> params, int type)
950 {
951 Document doc = XMLConverter.newDOM();
952 Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
953 if (type == GS2PerlConstructor.NEW)
954 {
955 Element param = doc.createElement(GSXML.PARAM_ELEM);
956 param.setAttribute(GSXML.NAME_ATT, "creator");
957 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(CREATOR_PARAM));
958
959 param_list.appendChild(param);
960 param = doc.createElement(GSXML.PARAM_ELEM);
961 param.setAttribute(GSXML.NAME_ATT, "about");
962 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(NEW_COL_ABOUT_PARAM));
963 param_list.appendChild(param);
964 param = doc.createElement(GSXML.PARAM_ELEM);
965 param.setAttribute(GSXML.NAME_ATT, "title");
966 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(NEW_COL_TITLE_PARAM));
967 param_list.appendChild(param);
968 param = doc.createElement(GSXML.PARAM_ELEM);
969 param.setAttribute(GSXML.NAME_ATT, "buildtype");
970 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(BUILDTYPE_PARAM));
971 param_list.appendChild(param);
972 return param_list;
973 }
974
975 // other ones dont have params yet
976 return null;
977 }
978
979 protected void waitUntilReady(Element request)
980 {
981 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
982 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
983
984 String collection = (String) params.get(COL_PARAM);
985
986 if (checkCollectionIsNotBusy(collection))
987 {
988 return;
989 }
990
991 while (!checkCollectionIsNotBusy(collection)) // When the collection ceases to be busy, we place a hold on it
992 {
993 try
994 {
995 Thread.currentThread().sleep(1000);
996 }
997 catch (Exception ex)
998 {
999 ex.printStackTrace();
1000 }
1001 }
1002 }
1003
1004 protected void signalReady(Element request)
1005 {
1006 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
1007 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
1008
1009 String collection = (String) params.get(COL_PARAM);
1010
1011 collectionOperationMap.remove(collection);
1012 }
1013
1014 // If collection is NOT busy, then reserve it
1015 protected synchronized boolean checkCollectionIsNotBusy(String collection)
1016 {
1017 if (collectionOperationMap.get(collection) == null)
1018 {
1019 collectionOperationMap.put(collection, true);
1020 return true;
1021 }
1022 return false;
1023 }
1024
1025 protected boolean isAllowedToSetMeta(String metaname, Pattern allowedMetaFieldsPattern)
1026 {
1027 if(allowedMetaFieldsPattern == null) { // null when user has edit permissions, so they can set any meta
1028 logger.info("### User has permissions to set any meta.");
1029 return true;
1030 }
1031 if(metaname == null) {
1032 logger.info("### Can't check null metaname against pattern");
1033 return false;
1034 }
1035
1036 Matcher m = allowedMetaFieldsPattern.matcher(metaname);
1037 if(!m.matches()) {
1038 logger.info("### metaname: " + metaname + " doesn't match allowed allowed fields: " + allowedMetaFieldsPattern.toString());
1039 return false;
1040 } else {
1041 return true;
1042 }
1043 }
1044
1045 protected String[] getDocIdsWithOptFilter(String json_str, Pattern filterFields) // boolean strictOrPermissible
1046 {
1047 if(json_str == null) {
1048 logger.error("### Shouldn't be happening: null json string");
1049 return null;
1050 }
1051
1052 String[] docids = null;
1053
1054 // check that the name of each metadata being set matches the pattern filterFields.
1055 // The presence of any other meta means something other than adding user comments is being attempted,
1056 // which is invalid
1057 try {
1058
1059 JSONArray docInfoList = new JSONArray(json_str);
1060 docids = new String[docInfoList.length()];
1061 for(int index = 0; index < docInfoList.length(); index++) {
1062 JSONObject docInfo = docInfoList.getJSONObject(index);
1063 if(docInfo.has("metatable")) { // should exist for metadata arrays
1064
1065 docids[index] = docInfo.getString("docid"); // should exist if metatable existed
1066
1067 logger.info("@@@ Found docid: " + docids[index]);
1068
1069 JSONArray metatable = docInfo.getJSONArray("metatable");
1070 for(int i = 0; i < metatable.length(); i++) {
1071 JSONObject meta = metatable.getJSONObject(i);
1072
1073 String metaname = meta.getString("metaname");
1074 logger.info("### metaname: " + metaname);
1075
1076 if(!isAllowedToSetMeta(metaname, filterFields)) {
1077 return null;
1078 }
1079 }
1080 }
1081 }
1082 } catch(JSONException jsonex) {
1083 logger.error("Exception when parsing json string: " + json_str);
1084 logger.error(jsonex);
1085
1086 }
1087
1088 // if we're here, then it means that the JSON only asked for username|usercomment|usertimestamp meta
1089 // meaning that the setmeta operation was a valid user comment operation.
1090 // In that case, we have a docid for which we need to add a user comment
1091 // set-metadata-array can take more docids, but doesn't happen for a user comment. And one comment
1092 // is added at a time, but 3 meta fields are set for each comment: username, usercomment and timestamp
1093 // hence the use of set-meta-array.
1094 return docids;
1095
1096 }
1097
1098 protected Element errorResponse(String serviceName, String errorMsg) {
1099 Document result_doc = XMLConverter.newDOM();
1100 Element result = GSXML.createBasicResponse(result_doc, serviceName);
1101 GSXML.addError(result, errorMsg);
1102 return result;
1103 }
1104
1105 /** Copy from DebugService.userHasEditPermissions
1106 This function checks that the user is logged in and that the user
1107 is in the right group to edit the collection */
1108 protected boolean userHasCollectionEditPermissions(Element request) {
1109 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
1110 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
1111
1112 return userHasCollectionEditPermissions(request, params);
1113
1114 }
1115
1116 protected boolean userHasCollectionEditPermissions(Element request, HashMap<String, Serializable> params) {
1117 String collection = (String) params.get(COL_PARAM); // could be null on newcoll operation
1118
1119
1120 UserContext context = new UserContext(request);
1121 if(collection == null) {
1122 return !context.getUsername().equals("");
1123 }
1124
1125 // START DEBUG
1126 Set<Map.Entry<String, Serializable>> entries = params.entrySet();
1127 Iterator<Map.Entry<String, Serializable>> i = entries.iterator();
1128
1129 StringBuffer parametersLine = new StringBuffer();
1130 while (i.hasNext()) {
1131
1132 Map.Entry<String, Serializable> entry = i.next();
1133 String paramname = entry.getKey();
1134 String paramvalue = (String) entry.getValue();
1135
1136 parametersLine.append("\t" + paramname + ": " + paramvalue + "\n");
1137 }
1138
1139 logger.info("XXXXXXXXXXXXX PARAMETERS:\n" + parametersLine);
1140 // END DEBUG
1141
1142 for (String group : context.getGroups()) {
1143 // administrator always has permission
1144 if (group.equals("administrator")) {
1145 return true;
1146 }
1147 // all-collections-editor can edit any collection
1148 if (!collection.equals("")) {
1149 if (group.equals("all-collections-editor")) {
1150 return true;
1151 }
1152 if (group.equals(collection+"-collection-editor")) {
1153 return true;
1154 }
1155 }
1156 }
1157 // haven't found a group with edit permissions
1158 return false;
1159
1160 }
1161 protected void markDocumentInFlatDatabase(String mark, String collection, String oid) {
1162
1163 Document msg_doc = XMLConverter.newDOM();
1164 Element message = msg_doc.createElement(GSXML.MESSAGE_ELEM);
1165 UserContext userContext = new UserContext();
1166 Element query_request = GSXML.createBasicRequest(msg_doc, GSXML.REQUEST_TYPE_DESCRIBE , collection, userContext);
1167 message.appendChild(query_request);
1168 Element result = (Element) this.router.process(message);
1169 Element resp_elem = (Element) GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM);
1170 Element coll_elem = (Element) GSXML.getChildByTagName(resp_elem, GSXML.COLLECTION_ELEM);
1171 String dbtype = coll_elem.getAttribute(GSXML.DB_TYPE_ATT);
1172
1173 SimpleCollectionDatabase coll_db = new SimpleCollectionDatabase(dbtype);
1174 if (!coll_db.databaseOK())
1175 {
1176 logger.error("Couldn't create the collection database of type " + dbtype);
1177 return;
1178 }
1179
1180 // Open database for reading. It may not exist if collection is pre-built without archives (such as demo collections)
1181 String coll_db_file = GSFile.archivesDatabaseFile(this.site_home, collection, dbtype);
1182 if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.READ))
1183 {
1184 logger.error("Could not open collection archives database. Database doesn't exist or else somebody's already using it?");
1185 return;
1186 }
1187 // now we know we have an archives folder
1188 String old_value = coll_db.getValue(oid);
1189 String new_value = old_value.replace("<index-status>B", "<index-status>" + mark);
1190 // Close database for reading
1191 coll_db.closeDatabase();
1192 if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.WRITE))
1193 {
1194 logger.error("Could not open collection archives database. Somebody already using this database!");
1195 return;
1196 }
1197 coll_db.setValue(oid, new_value);
1198 coll_db.closeDatabase();
1199
1200 }
1201}
Note: See TracBrowser for help on using the repository browser.