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

Last change on this file since 31771 was 31771, checked in by kjdon, 7 years ago

we need to pass library_name to the incremental-buildcol so that it can deactivate the collection before modifying the database

  • Property svn:keywords set to Author Date Id Revision
File size: 42.6 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.setLibraryName(this.library_name);
789 constructor.setCollectionName(coll_name);
790 constructor.setActionType(type);
791 constructor.setProcessParams(other_params);
792 if (type == GS2PerlConstructor.IMPORT)
793 {
794 constructor.setManifestFile(this.site_home + File.separator + "collect" + File.separator + params.get(COL_PARAM) + File.separator + "manifests" + File.separator + "tempManifest.xml");
795 }
796 else if (type == GS2PerlConstructor.MODIFY_METADATA_SERVER) {
797 StringBuffer querystring = new StringBuffer();
798
799 // convert params into a single string again?
800 Set<Map.Entry<String, Serializable>> entries = params.entrySet();
801 Iterator<Map.Entry<String, Serializable>> i = entries.iterator();
802
803 String oid = null;
804
805 while (i.hasNext()) {
806
807 Map.Entry<String, Serializable> entry = i.next();
808 String paramname = entry.getKey();
809 paramname = paramname.replace("s1.", ""); // replaces all occurrences
810 if (paramname.equals("collection")) {
811 paramname = "c";
812 }
813 if (paramname.equals("d")){
814 oid = (String) entry.getValue();
815 }
816
817 String paramvalue = (String) entry.getValue();
818
819 querystring.append(paramname + "=" + paramvalue);
820 if (i.hasNext()) {
821 querystring.append("&");
822 }
823 }
824
825 if(oid != null) { // if we have only one oid
826 markDocumentInFlatDatabase("R", coll_name, OID.getTop(oid));
827 } else if (docids != null) { // check if we are dealing with many doc ids, as cold in theory happen when set-metadata-array is called
828
829 for(int index = 0; index < docids.length; index++) {
830 String docid = docids[index];
831 markDocumentInFlatDatabase("R", coll_name, OID.getTop(docid));
832 }
833 } else {
834 String msg = getTextString("general.no_valid_docid_error", lang);
835 logger.error("*** " + msg);
836 Text t = result_doc.createTextNode(msg);
837 status.appendChild(t);
838 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ERROR));
839 return response;
840 }
841
842 constructor.setQueryString(querystring.toString());
843 }
844
845 GS2PerlListener listener = new GS2PerlListener();
846 constructor.addListener(listener);
847 constructor.start();
848
849 String id = newID();
850 this.listeners.put(id, listener);
851
852 status.setAttribute(GSXML.STATUS_PROCESS_ID_ATT, id);
853 status.setAttribute(GSXML.STATUS_ERROR_CODE_ATT, Integer.toString(GSStatus.ACCEPTED));
854 Text t = result_doc.createTextNode(getTextString("general.process_start", lang));
855 status.appendChild(t);
856 return response;
857 }
858
859 //************************
860 // some helper functions
861 //************************
862
863 /** parse the collect directory and return a list of collection names */
864 protected String[] getCollectionList()
865 {
866
867 File collectDir = new File(GSFile.collectDir(this.site_home));
868 if (!collectDir.exists())
869 {
870 logger.error("couldn't find collect dir: " + collectDir.toString());
871 return null;
872 }
873 logger.info("GS2Construct: reading thru directory " + collectDir.getPath() + " to find collections.");
874 File[] contents = collectDir.listFiles();
875 int num_colls = 0;
876 for (int i = 0; i < contents.length; i++)
877 {
878 if (contents[i].isDirectory() && !contents[i].getName().startsWith("CVS"))
879 {
880 num_colls++;
881 }
882 }
883
884 String[] names = new String[num_colls];
885
886 for (int i = 0, j = 0; i < contents.length; i++)
887 {
888 if (contents[i].isDirectory())
889 {
890 String colName = contents[i].getName();
891 if (!colName.startsWith("CVS"))
892 {
893 names[j] = colName;
894 j++;
895 }
896
897 }
898 }
899
900 return names;
901
902 }
903
904 /** ids used for process id */
905 private int current_id = 0;
906
907 private String newID()
908 {
909 current_id++;
910 return Integer.toString(current_id);
911 }
912
913 /** creates a new short name from the collection title */
914 protected String createNewCollName(String coll_title)
915 {
916
917 String base_name = null;
918 // take the first 6 letters
919 if (coll_title.length() < 6)
920 {
921 base_name = coll_title;
922 }
923 else
924 {
925 base_name = coll_title.substring(0, 6);
926 }
927 File coll_dir = new File(GSFile.collectionBaseDir(this.site_home, base_name));
928 if (!coll_dir.exists())
929 { // this name is ok - not used yet
930 return base_name;
931 }
932
933 // now we have to make a new name until we get a good one
934 // try name1, name2 name3 etc
935 int i = 0;
936 while (coll_dir.exists())
937 {
938 i++;
939 coll_dir = new File(GSFile.collectionBaseDir(this.site_home, base_name + Integer.toString(i)));
940 }
941 return base_name + Integer.toString(i);
942
943 }
944
945 /**
946 * takes the params from the request (in the HashMap) and extracts any that
947 * need to be passed to the constructor and puts them into a paramList
948 * element
949 */
950 protected Element extractOtherParams(HashMap<String, Serializable> params, int type)
951 {
952 Document doc = XMLConverter.newDOM();
953 Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
954 if (type == GS2PerlConstructor.NEW)
955 {
956 Element param = doc.createElement(GSXML.PARAM_ELEM);
957 param.setAttribute(GSXML.NAME_ATT, "creator");
958 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(CREATOR_PARAM));
959
960 param_list.appendChild(param);
961 param = doc.createElement(GSXML.PARAM_ELEM);
962 param.setAttribute(GSXML.NAME_ATT, "about");
963 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(NEW_COL_ABOUT_PARAM));
964 param_list.appendChild(param);
965 param = doc.createElement(GSXML.PARAM_ELEM);
966 param.setAttribute(GSXML.NAME_ATT, "title");
967 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(NEW_COL_TITLE_PARAM));
968 param_list.appendChild(param);
969 param = doc.createElement(GSXML.PARAM_ELEM);
970 param.setAttribute(GSXML.NAME_ATT, "buildtype");
971 param.setAttribute(GSXML.VALUE_ATT, (String) params.get(BUILDTYPE_PARAM));
972 param_list.appendChild(param);
973 return param_list;
974 }
975
976 // other ones dont have params yet
977 return null;
978 }
979
980 protected void waitUntilReady(Element request)
981 {
982 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
983 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
984
985 String collection = (String) params.get(COL_PARAM);
986
987 if (checkCollectionIsNotBusy(collection))
988 {
989 return;
990 }
991
992 while (!checkCollectionIsNotBusy(collection)) // When the collection ceases to be busy, we place a hold on it
993 {
994 try
995 {
996 Thread.currentThread().sleep(1000);
997 }
998 catch (Exception ex)
999 {
1000 ex.printStackTrace();
1001 }
1002 }
1003 }
1004
1005 protected void signalReady(Element request)
1006 {
1007 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
1008 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
1009
1010 String collection = (String) params.get(COL_PARAM);
1011
1012 collectionOperationMap.remove(collection);
1013 }
1014
1015 // If collection is NOT busy, then reserve it
1016 protected synchronized boolean checkCollectionIsNotBusy(String collection)
1017 {
1018 if (collectionOperationMap.get(collection) == null)
1019 {
1020 collectionOperationMap.put(collection, true);
1021 return true;
1022 }
1023 return false;
1024 }
1025
1026 protected boolean isAllowedToSetMeta(String metaname, Pattern allowedMetaFieldsPattern)
1027 {
1028 if(allowedMetaFieldsPattern == null) { // null when user has edit permissions, so they can set any meta
1029 ///logger.info("### User has permissions to set any meta.");
1030 return true;
1031 }
1032 if(metaname == null) {
1033 ///logger.info("### Can't check null metaname against pattern");
1034 return false;
1035 }
1036
1037 Matcher m = allowedMetaFieldsPattern.matcher(metaname);
1038 if(!m.matches()) {
1039 ///logger.info("### metaname: " + metaname + " doesn't match allowed allowed fields: " + allowedMetaFieldsPattern.toString());
1040 return false;
1041 } else {
1042 return true;
1043 }
1044 }
1045
1046 protected String[] getDocIdsWithOptFilter(String json_str, Pattern filterFields) // boolean strictOrPermissible
1047 {
1048 if(json_str == null) {
1049 logger.error("### Shouldn't be happening: null json string");
1050 return null;
1051 }
1052
1053 String[] docids = null;
1054
1055 // check that the name of each metadata being set matches the pattern filterFields.
1056 // The presence of any other meta means something other than adding user comments is being attempted,
1057 // which is invalid
1058 try {
1059
1060 JSONArray docInfoList = new JSONArray(json_str);
1061 docids = new String[docInfoList.length()];
1062 for(int index = 0; index < docInfoList.length(); index++) {
1063 JSONObject docInfo = docInfoList.getJSONObject(index);
1064 if(docInfo.has("metatable")) { // should exist for metadata arrays
1065
1066 docids[index] = docInfo.getString("docid"); // should exist if metatable existed
1067
1068 ///logger.info("@@@ Found docid: " + docids[index]);
1069
1070 JSONArray metatable = docInfo.getJSONArray("metatable");
1071 for(int i = 0; i < metatable.length(); i++) {
1072 JSONObject meta = metatable.getJSONObject(i);
1073
1074 String metaname = meta.getString("metaname");
1075 ///logger.info("### metaname: " + metaname);
1076
1077 if(!isAllowedToSetMeta(metaname, filterFields)) {
1078 return null;
1079 }
1080 }
1081 }
1082 }
1083 } catch(JSONException jsonex) {
1084 logger.error("Exception when parsing json string: " + json_str);
1085 logger.error(jsonex);
1086
1087 }
1088
1089 // if we're here, then it means that the JSON only asked for username|usercomment|usertimestamp meta
1090 // meaning that the setmeta operation was a valid user comment operation.
1091 // In that case, we have a docid for which we need to add a user comment
1092 // set-metadata-array can take more docids, but doesn't happen for a user comment. And one comment
1093 // is added at a time, but 3 meta fields are set for each comment: username, usercomment and timestamp
1094 // hence the use of set-meta-array.
1095 return docids;
1096
1097 }
1098
1099 protected Element errorResponse(String serviceName, String errorMsg) {
1100 Document result_doc = XMLConverter.newDOM();
1101 Element result = GSXML.createBasicResponse(result_doc, serviceName);
1102 GSXML.addError(result, errorMsg);
1103 return result;
1104 }
1105
1106 /** Copy from DebugService.userHasEditPermissions
1107 This function checks that the user is logged in and that the user
1108 is in the right group to edit the collection */
1109 protected boolean userHasCollectionEditPermissions(Element request) {
1110 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
1111 HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
1112
1113 return userHasCollectionEditPermissions(request, params);
1114
1115 }
1116
1117 protected boolean userHasCollectionEditPermissions(Element request, HashMap<String, Serializable> params) {
1118 String collection = (String) params.get(COL_PARAM); // could be null on newcoll operation
1119
1120
1121 UserContext context = new UserContext(request);
1122 if(collection == null) {
1123 return !context.getUsername().equals("");
1124 }
1125
1126 /*
1127 // FOR DEBUGGING
1128 Set<Map.Entry<String, Serializable>> entries = params.entrySet();
1129 Iterator<Map.Entry<String, Serializable>> i = entries.iterator();
1130
1131 StringBuffer parametersLine = new StringBuffer();
1132 while (i.hasNext()) {
1133
1134 Map.Entry<String, Serializable> entry = i.next();
1135 String paramname = entry.getKey();
1136 String paramvalue = (String) entry.getValue();
1137
1138 parametersLine.append("\t" + paramname + ": " + paramvalue + "\n");
1139 }
1140
1141 logger.info("XXXXXXXXXXXXX PARAMETERS:\n" + parametersLine);
1142 */
1143
1144 for (String group : context.getGroups()) {
1145 // administrator always has permission
1146 if (group.equals("administrator")) {
1147 return true;
1148 }
1149 // all-collections-editor can edit any collection
1150 if (!collection.equals("")) {
1151 if (group.equals("all-collections-editor")) {
1152 return true;
1153 }
1154 if (group.equals(collection+"-collection-editor")) {
1155 return true;
1156 }
1157 }
1158 }
1159 // haven't found a group with edit permissions
1160 return false;
1161
1162 }
1163 protected void markDocumentInFlatDatabase(String mark, String collection, String oid) {
1164
1165 Document msg_doc = XMLConverter.newDOM();
1166 Element message = msg_doc.createElement(GSXML.MESSAGE_ELEM);
1167 UserContext userContext = new UserContext();
1168 Element query_request = GSXML.createBasicRequest(msg_doc, GSXML.REQUEST_TYPE_DESCRIBE , collection, userContext);
1169 message.appendChild(query_request);
1170 Element result = (Element) this.router.process(message);
1171 Element resp_elem = (Element) GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM);
1172 Element coll_elem = (Element) GSXML.getChildByTagName(resp_elem, GSXML.COLLECTION_ELEM);
1173 String dbtype = coll_elem.getAttribute(GSXML.DB_TYPE_ATT);
1174
1175 SimpleCollectionDatabase coll_db = new SimpleCollectionDatabase(dbtype);
1176 if (!coll_db.databaseOK())
1177 {
1178 logger.error("Couldn't create the collection database of type " + dbtype);
1179 return;
1180 }
1181
1182 // Open database for reading. It may not exist if collection is pre-built without archives (such as demo collections)
1183 String coll_db_file = GSFile.archivesDatabaseFile(this.site_home, collection, dbtype);
1184 if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.READ))
1185 {
1186 logger.error("Could not open collection archives database. Database doesn't exist or else somebody's already using it?");
1187 return;
1188 }
1189 // now we know we have an archives folder
1190 String old_value = coll_db.getValue(oid);
1191 String new_value = "<index-status>" + mark;
1192 if(old_value == null) {
1193 logger.error("### null old_value in flat DB for oid " + oid);
1194 } else {
1195 new_value = old_value.replace("<index-status>B", "<index-status>" + mark);
1196 logger.info("### Replacing db entry for oid " + oid + " which has old_value " + old_value);
1197 logger.info("### with new value " + new_value);
1198
1199 }
1200 // Close database for reading
1201 coll_db.closeDatabase();
1202
1203 if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.WRITE))
1204 {
1205 logger.error("Could not open collection archives database. Somebody already using this database!");
1206 return;
1207 }
1208
1209 coll_db.setValue(oid, new_value);
1210
1211 coll_db.closeDatabase();
1212 }
1213}
Note: See TracBrowser for help on using the repository browser.