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

Last change on this file since 31541 was 31541, checked in by ak19, 7 years ago
  1. GS2Construct.modifyMetadata() now calls a more generic method to obtain docIDs of a set-metadata-array operation, close to the manner suggested by Dr Bainbridge (few minor differences because circumstances were different). The new method is not tied specifically to allowing user comments, which is rather a special case. 2. Refactored repetitive code for generating XML error responses into a function.
  • Property svn:keywords set to Author Date Id Revision
File size: 42.0 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(metaname == null) {
1028 logger.info("### Can't check null metaname against pattern");
1029 return false;
1030 }
1031
1032 Matcher m = allowedMetaFieldsPattern.matcher(metaname);
1033 if(!m.matches()) {
1034 logger.info("### metaname: " + metaname + " doesn't match allowed allowed fields: " + allowedMetaFieldsPattern.toString());
1035 return false;
1036 } else {
1037 return true;
1038 }
1039 }
1040
1041 protected String[] getDocIdsWithOptFilter(String json_str, Pattern filterFields) // boolean strictOrPermissible
1042 {
1043 if(json_str == null) {
1044 logger.error("### Shouldn't be happening: null json string");
1045 return null;
1046 }
1047
1048 String[] docids = null;
1049
1050 // check that the name of each metadata being set matches the pattern filterFields.
1051 // The presence of any other meta means something other than adding user comments is being attempted,
1052 // which is invalid
1053 try {
1054
1055 JSONArray docInfoList = new JSONArray(json_str);
1056 docids = new String[docInfoList.length()];
1057 for(int index = 0; index < docInfoList.length(); index++) {
1058 JSONObject docInfo = docInfoList.getJSONObject(index);
1059 if(docInfo.has("metatable")) { // should exist for metadata arrays
1060
1061 docids[index] = docInfo.getString("docid"); // should exist if metatable existed
1062
1063 logger.info("@@@ Found docid: " + docids[index]);
1064
1065 JSONArray metatable = docInfo.getJSONArray("metatable");
1066 for(int i = 0; i < metatable.length(); i++) {
1067 JSONObject meta = metatable.getJSONObject(i);
1068
1069 String metaname = meta.getString("metaname");
1070 logger.info("### metaname: " + metaname);
1071
1072 if(!isAllowedToSetMeta(metaname, filterFields)) {
1073 return null;
1074 }
1075 }
1076 }
1077 }
1078 } catch(JSONException jsonex) {
1079 logger.error("Exception when parsing json string: " + json_str);
1080 logger.error(jsonex);
1081
1082 }
1083
1084 // if we're here, then it means that the JSON only asked for username|usercomment|usertimestamp meta
1085 // meaning that the setmeta operation was a valid user comment operation.
1086 // In that case, we have a docid for which we need to add a user comment
1087 // set-metadata-array can take more docids, but doesn't happen for a user comment. And one comment
1088 // is added at a time, but 3 meta fields are set for each comment: username, usercomment and timestamp
1089 // hence the use of set-meta-array.
1090 return docids;
1091
1092 }
1093
1094 protected Element errorResponse(String serviceName, String errorMsg) {
1095 Document result_doc = XMLConverter.newDOM();
1096 Element result = GSXML.createBasicResponse(result_doc, serviceName);
1097 GSXML.addError(result, errorMsg);
1098 return result;
1099 }
1100
1101 /** Copy from DebugService.userHasEditPermissions
1102 This function checks that the user is logged in and that the user
1103 is in the right group to edit the collection */
1104 protected boolean userHasCollectionEditPermissions(Element request) {
1105 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
1106 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
1107
1108 return userHasCollectionEditPermissions(request, params);
1109
1110 }
1111
1112 protected boolean userHasCollectionEditPermissions(Element request, HashMap<String, Serializable> params) {
1113 String collection = (String) params.get(COL_PARAM); // could be null on newcoll operation
1114
1115
1116 UserContext context = new UserContext(request);
1117 if(collection == null) {
1118 return !context.getUsername().equals("");
1119 }
1120
1121 // START DEBUG
1122 Set<Map.Entry<String, Serializable>> entries = params.entrySet();
1123 Iterator<Map.Entry<String, Serializable>> i = entries.iterator();
1124
1125 StringBuffer parametersLine = new StringBuffer();
1126 while (i.hasNext()) {
1127
1128 Map.Entry<String, Serializable> entry = i.next();
1129 String paramname = entry.getKey();
1130 String paramvalue = (String) entry.getValue();
1131
1132 parametersLine.append("\t" + paramname + ": " + paramvalue + "\n");
1133 }
1134
1135 logger.info("XXXXXXXXXXXXX PARAMETERS:\n" + parametersLine);
1136 // END DEBUG
1137
1138 for (String group : context.getGroups()) {
1139 // administrator always has permission
1140 if (group.equals("administrator")) {
1141 return true;
1142 }
1143 // all-collections-editor can edit any collection
1144 if (!collection.equals("")) {
1145 if (group.equals("all-collections-editor")) {
1146 return true;
1147 }
1148 if (group.equals(collection+"-collection-editor")) {
1149 return true;
1150 }
1151 }
1152 }
1153 // haven't found a group with edit permissions
1154 return false;
1155
1156 }
1157 protected void markDocumentInFlatDatabase(String mark, String collection, String oid) {
1158
1159 Document msg_doc = XMLConverter.newDOM();
1160 Element message = msg_doc.createElement(GSXML.MESSAGE_ELEM);
1161 UserContext userContext = new UserContext();
1162 Element query_request = GSXML.createBasicRequest(msg_doc, GSXML.REQUEST_TYPE_DESCRIBE , collection, userContext);
1163 message.appendChild(query_request);
1164 Element result = (Element) this.router.process(message);
1165 Element resp_elem = (Element) GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM);
1166 Element coll_elem = (Element) GSXML.getChildByTagName(resp_elem, GSXML.COLLECTION_ELEM);
1167 String dbtype = coll_elem.getAttribute(GSXML.DB_TYPE_ATT);
1168
1169 SimpleCollectionDatabase coll_db = new SimpleCollectionDatabase(dbtype);
1170 if (!coll_db.databaseOK())
1171 {
1172 logger.error("Couldn't create the collection database of type " + dbtype);
1173 return;
1174 }
1175
1176 // Open database for reading. It may not exist if collection is pre-built without archives (such as demo collections)
1177 String coll_db_file = GSFile.archivesDatabaseFile(this.site_home, collection, dbtype);
1178 if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.READ))
1179 {
1180 logger.error("Could not open collection archives database. Database doesn't exist or else somebody's already using it?");
1181 return;
1182 }
1183 // now we know we have an archives folder
1184 String old_value = coll_db.getValue(oid);
1185 String new_value = old_value.replace("<index-status>B", "<index-status>" + mark);
1186 // Close database for reading
1187 coll_db.closeDatabase();
1188 if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.WRITE))
1189 {
1190 logger.error("Could not open collection archives database. Somebody already using this database!");
1191 return;
1192 }
1193 coll_db.setValue(oid, new_value);
1194 coll_db.closeDatabase();
1195
1196 }
1197}
Note: See TracBrowser for help on using the repository browser.