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

Last change on this file since 32453 was 32453, checked in by kjdon, 6 years ago

replacing hard coded param names with static string variables. set up save params for those params we need to save to the session.

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