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

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

First commit for getting user comments working for GS3. It all works, but there's debugging statements still and haven't cleaned up commented out code. After that, would like to make POST rather than GET AJAX calls, so more refactoring required. 1. config_format.xsl is just minor spelling corrections. 2. header.xsl has a new function generateLogoutURL. 3. document.xsl imports and calls new usercomments.xsl to set up the user comments area. 4. New usercomments.js is imported by new usercomments.xsl, and sets up the interaction of the usercomments area. 5. javascript-global-functions.js now contains setMetadataArray and getMetadataArray functions to parallel what GS2 used in gsajaxapi.js, but for GS3 need to go through GS2Construct.processModifyMetadata() service in java code. 5. GS2Construct.java does different checking for users adding user comments versus users doing document editing. For the latter, the user needs to have editing permissions for the document. But any user is allowed to add comments on any accessible document. But ModifyMetadata should not allow any other metadata to be modified other than meta fields. 6. New language strings for usercomment area and GS2Construct errors in the 2 changed properties files.

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