001: /*
002: * FlowContainerUtils.java
003: *
004: * Version: $Revision: 1.3 $
005: *
006: * Date: $Date: 2006/07/13 23:20:54 $
007: *
008: * Copyright (c) 2002, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */package org.dspace.app.xmlui.aspect.administrative;
040:
041: import java.io.ByteArrayInputStream;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.sql.SQLException;
045: import java.util.List;
046:
047: import org.apache.cocoon.environment.Request;
048: import org.apache.cocoon.servlet.multipart.Part;
049: import org.dspace.app.xmlui.utils.UIException;
050: import org.dspace.app.xmlui.wing.Message;
051: import org.dspace.authorize.AuthorizeException;
052: import org.dspace.authorize.AuthorizeManager;
053: import org.dspace.authorize.ResourcePolicy;
054: import org.dspace.content.Collection;
055: import org.dspace.content.Community;
056: import org.dspace.content.Item;
057: import org.dspace.core.Constants;
058: import org.dspace.core.Context;
059: import org.dspace.eperson.Group;
060: import org.jdom.JDOMException;
061: import org.jdom.input.SAXBuilder;
062:
063: /**
064: * Utility methods to processes actions on Communities and Collections.
065: *
066: * @author scott phillips
067: */
068: public class FlowContainerUtils {
069:
070: /** Possible Collection roles */
071: public static final String ROLE_ADMIN = "ADMIN";
072: public static final String ROLE_WF_STEP1 = "WF_STEP1";
073: public static final String ROLE_WF_STEP2 = "WF_STEP2";
074: public static final String ROLE_WF_STEP3 = "WF_STEP3";
075: public static final String ROLE_SUBMIT = "SUBMIT";
076: public static final String ROLE_DEFAULT_READ = "DEFAULT_READ";
077:
078: // Collection related functions
079:
080: /**
081: * Process the collection metadata edit form.
082: *
083: * @param context The current DSpace context.
084: * @param collectionID The collection id.
085: * @param deleteLogo Determines if the logo should be deleted along with the metadata editing action.
086: * @param request the Cocoon request object
087: * @return A process result's object.
088: */
089: public static FlowResult processEditCollection(Context context,
090: int collectionID, boolean deleteLogo, Request request)
091: throws SQLException, IOException, AuthorizeException {
092: FlowResult result = new FlowResult();
093:
094: Collection collection = Collection.find(context, collectionID);
095:
096: // Get the metadata
097: String name = request.getParameter("name");
098: String shortDescription = request
099: .getParameter("short_description");
100: String introductoryText = request
101: .getParameter("introductory_text");
102: String copyrightText = request.getParameter("copyright_text");
103: String sideBarText = request.getParameter("side_bar_text");
104: String license = request.getParameter("license");
105: String provenanceDescription = request
106: .getParameter("provenance_description");
107:
108: // If they don't have a name then make it untitled.
109: if (name == null || name.length() == 0)
110: name = "Untitled";
111:
112: // If empty, make it null.
113: if (shortDescription != null && shortDescription.length() == 0)
114: shortDescription = null;
115: if (introductoryText != null && introductoryText.length() == 0)
116: introductoryText = null;
117: if (copyrightText != null && copyrightText.length() == 0)
118: copyrightText = null;
119: if (sideBarText != null && sideBarText.length() == 0)
120: sideBarText = null;
121: if (license != null && license.length() == 0)
122: license = null;
123: if (provenanceDescription != null
124: && provenanceDescription.length() == 0)
125: provenanceDescription = null;
126:
127: // Save the metadata
128: collection.setMetadata("name", name);
129: collection.setMetadata("short_description", shortDescription);
130: collection.setMetadata("introductory_text", introductoryText);
131: collection.setMetadata("copyright_text", copyrightText);
132: collection.setMetadata("side_bar_text", sideBarText);
133: collection.setMetadata("license", license);
134: collection.setMetadata("provenance_description",
135: provenanceDescription);
136:
137: // Change or delete the logo
138: if (deleteLogo) {
139: // Remove the logo
140: collection.setLogo(null);
141: } else {
142: // Update the logo
143: Object object = request.get("logo");
144: Part filePart = null;
145: if (object instanceof Part)
146: filePart = (Part) object;
147:
148: if (filePart != null && filePart.getSize() > 0) {
149: InputStream is = filePart.getInputStream();
150:
151: collection.setLogo(is);
152: }
153: }
154:
155: // Save everything
156: collection.update();
157: context.commit();
158:
159: // No notice...
160: result.setContinue(true);
161:
162: return result;
163: }
164:
165: /**
166: * Look up the id of the template item for a given collection.
167: *
168: * @param context The current DSpace context.
169: * @param collectionID The collection id.
170: * @return The id of the template item.
171: * @throws IOException
172: */
173: public static int getTemplateItemID(Context context,
174: int collectionID) throws SQLException, AuthorizeException,
175: IOException {
176: Collection collection = Collection.find(context, collectionID);
177: Item template = collection.getTemplateItem();
178:
179: if (template == null) {
180: collection.createTemplateItem();
181: template = collection.getTemplateItem();
182:
183: collection.update();
184: template.update();
185: context.commit();
186: }
187:
188: return template.getID();
189: }
190:
191: /**
192: * Look up the id of a group authorized for one of the given roles. If no group is currently
193: * authorized to preform this role then a new group will be created and assigned the role.
194: *
195: * @param context The current DSpace context.
196: * @param collectionID The collection id.
197: * @param roleName ADMIN, WF_STEP1, WF_STEP2, WF_STEP3, SUBMIT, DEFAULT_READ.
198: * @return The id of the group associated with that particular role, or -1 if the role was not found.
199: */
200: public static int getCollectionRole(Context context,
201: int collectionID, String roleName) throws SQLException,
202: AuthorizeException, IOException {
203: Collection collection = Collection.find(context, collectionID);
204:
205: // Determine the group based upon wich role we are looking for.
206: Group role = null;
207: if (ROLE_ADMIN.equals(roleName)) {
208: role = collection.getAdministrators();
209: if (role == null)
210: role = collection.createAdministrators();
211: } else if (ROLE_SUBMIT.equals(roleName)) {
212: role = collection.getSubmitters();
213: if (role == null)
214: role = collection.createSubmitters();
215: } else if (ROLE_WF_STEP1.equals(roleName)) {
216: role = collection.getWorkflowGroup(1);
217: if (role == null)
218: role = collection.createWorkflowGroup(1);
219:
220: } else if (ROLE_WF_STEP2.equals(roleName)) {
221: role = collection.getWorkflowGroup(2);
222: if (role == null)
223: role = collection.createWorkflowGroup(2);
224: } else if (ROLE_WF_STEP3.equals(roleName)) {
225: role = collection.getWorkflowGroup(3);
226: if (role == null)
227: role = collection.createWorkflowGroup(3);
228:
229: }
230:
231: // In case we needed to create a group, save our changes
232: collection.update();
233: context.commit();
234:
235: // If the role name was valid then role should be non null,
236: if (role != null)
237: return role.getID();
238:
239: return -1;
240: }
241:
242: /**
243: * Delete one of collection's roles
244: *
245: * @param context The current DSpace context.
246: * @param collectionID The collection id.
247: * @param roleName ADMIN, WF_STEP1, WF_STEP2, WF_STEP3, SUBMIT, DEFAULT_READ.
248: * @param groupID The id of the group associated with this role.
249: * @return A process result's object.
250: */
251: public static FlowResult processDeleteCollectionRole(
252: Context context, int collectionID, String roleName,
253: int groupID) throws SQLException, UIException, IOException,
254: AuthorizeException {
255: FlowResult result = new FlowResult();
256:
257: Collection collection = Collection.find(context, collectionID);
258: Group role = Group.find(context, groupID);
259:
260: // First, Unregister the role
261: if (ROLE_ADMIN.equals(roleName)) {
262: // FIXME: This should unregister this role from the collection
263: // object however there is no method available to do this. Once
264: // Manakin is integrated into dspace this situatiotion should be
265: // resolved.
266:
267: throw new UIException(
268: "This operation can not be preformed untill the DSpace API is modified to enable the removal of collection administrators.");
269: } else if (ROLE_SUBMIT.equals(roleName)) {
270: // FIXME: This should unregister this role from the collection
271: // object however there is no method available to do this. Once
272: // Manakin is integrated into dspace this situatiotion should be
273: // resolved.
274:
275: throw new UIException(
276: "This operation can not be preformed untill the DSpace API is modified to enable the removal of collection submitters.");
277: } else if (ROLE_WF_STEP1.equals(roleName)) {
278: collection.setWorkflowGroup(1, null);
279: } else if (ROLE_WF_STEP2.equals(roleName)) {
280: collection.setWorkflowGroup(2, null);
281: } else if (ROLE_WF_STEP3.equals(roleName)) {
282: collection.setWorkflowGroup(3, null);
283:
284: }
285:
286: // Second, remove all outhorizations for this role by searching for all policies that this
287: // group has on the collection and remove them otherwise the delete will fail because
288: // there are dependencies.
289: @SuppressWarnings("unchecked")
290: // the cast is correct
291: List<ResourcePolicy> policies = AuthorizeManager.getPolicies(
292: context, collection);
293: for (ResourcePolicy policy : policies) {
294: if (policy.getGroupID() == groupID)
295: policy.delete();
296: }
297:
298: // Finaly, Delete the role's actual group.
299: collection.update();
300: role.delete();
301: context.commit();
302:
303: result.setContinue(true);
304: result.setOutcome(true);
305: result.setMessage(new Message("default",
306: "The role was successfully deleted."));
307: return result;
308: }
309:
310: /**
311: * Look up the id of a group authorized for one of the given roles. If no group is currently
312: * authorized to preform this role then a new group will be created and assigned the role.
313: *
314: * @param context The current DSpace context.
315: * @param collectionID The collection id.
316: * @param roleName ADMIN, WF_STEP1, WF_STEP2, WF_STEP3, SUBMIT, DEFAULT_READ.
317: * @return The id of the group associated with that particular role.
318: */
319: public static int getCollectionDefaultRead(Context context,
320: int collectionID) throws SQLException, AuthorizeException {
321: Collection collection = Collection.find(context, collectionID);
322:
323: Group[] itemGroups = AuthorizeManager.getAuthorizedGroups(
324: context, collection, Constants.DEFAULT_ITEM_READ);
325: Group[] bitstreamGroups = AuthorizeManager.getAuthorizedGroups(
326: context, collection, Constants.DEFAULT_BITSTREAM_READ);
327:
328: if (itemGroups.length != 1 && bitstreamGroups.length != 1)
329: // If there are more than one groups assigned either of these privleges then this role based method will not work.
330: // The user will need to go to the authorization section to manualy straight this out.
331: return -1;
332:
333: Group itemGroup = itemGroups[0];
334: Group bitstreamGroup = bitstreamGroups[0];
335:
336: if (itemGroup.getID() != bitstreamGroup.getID())
337: // If the same group is not assigned both of these priveleges then this role based method will not work. The user
338: // will need to go to the authorization section to manualy straighten this out.
339: return -1;
340:
341: return itemGroup.getID();
342: }
343:
344: /**
345: * Change default privleges from the anonymous group to a new group that will be created and
346: * approrpate privleges assigned. The id of this new group will be returned.
347: *
348: * @param context The current DSpace context.
349: * @param collectionID The collection id.
350: * @return The group ID of the new group.
351: */
352: public static int createCollectionDefaultReadGroup(Context context,
353: int collectionID) throws SQLException, AuthorizeException,
354: UIException {
355: int roleID = getCollectionDefaultRead(context, collectionID);
356:
357: if (roleID != 0)
358: throw new UIException(
359: "Unable to create a new default read group because either the group allready exists or multiple groups are assigned the default privleges.");
360:
361: Collection collection = Collection.find(context, collectionID);
362: Group role = Group.create(context);
363: role.setName("COLLECTION_" + collection.getID()
364: + "_DEFAULT_READ");
365:
366: // Remove existing privleges from the anynomous group.
367: AuthorizeManager.removePoliciesActionFilter(context,
368: collection, Constants.DEFAULT_ITEM_READ);
369: AuthorizeManager.removePoliciesActionFilter(context,
370: collection, Constants.DEFAULT_BITSTREAM_READ);
371:
372: // Grant our new role the default privleges.
373: AuthorizeManager.addPolicy(context, collection,
374: Constants.DEFAULT_ITEM_READ, role);
375: AuthorizeManager.addPolicy(context, collection,
376: Constants.DEFAULT_BITSTREAM_READ, role);
377:
378: // Committ the changes
379: role.update();
380: context.commit();
381:
382: return role.getID();
383: }
384:
385: /**
386: * Change the default read priveleges to the anonymous group.
387: *
388: * If getCollectionDefaultRead() returns -1 or the anonymous group then nothing
389: * is done.
390: *
391: * @param context The current DSpace context.
392: * @param collectionID The collection id.
393: * @return A process result's object.
394: */
395: public static FlowResult changeCollectionDefaultReadToAnonymous(
396: Context context, int collectionID) throws SQLException,
397: AuthorizeException, UIException {
398: FlowResult result = new FlowResult();
399:
400: int roleID = getCollectionDefaultRead(context, collectionID);
401:
402: if (roleID < 1) {
403: throw new UIException(
404: "Unable to delete the default read role because the role is either allready assigned to the anonymous group or multiple groups are assigned the default priveleges.");
405: }
406:
407: Collection collection = Collection.find(context, collectionID);
408: Group role = Group.find(context, roleID);
409: Group anonymous = Group.find(context, 0);
410:
411: // Delete the old role, this will remove the default privleges.
412: role.delete();
413:
414: // Set anonymous as the default read group.
415: AuthorizeManager.addPolicy(context, collection,
416: Constants.DEFAULT_ITEM_READ, anonymous);
417: AuthorizeManager.addPolicy(context, collection,
418: Constants.DEFAULT_BITSTREAM_READ, anonymous);
419:
420: // Commit the changes
421: context.commit();
422:
423: result.setContinue(true);
424: result.setOutcome(true);
425: result
426: .setMessage(new Message("default",
427: "All new items submitted to this collection will default to anonymous read."));
428: return result;
429: }
430:
431: /**
432: * Delete collection itself
433: *
434: * @param context The current DSpace context.
435: * @param collectionID The collection id.
436: * @return A process result's object.
437: */
438: public static FlowResult processDeleteCollection(Context context,
439: int collectionID) throws SQLException, AuthorizeException,
440: IOException {
441: FlowResult result = new FlowResult();
442:
443: Collection collection = Collection.find(context, collectionID);
444:
445: Community[] parents = collection.getCommunities();
446:
447: for (Community parent : parents) {
448: parent.removeCollection(collection);
449: parent.update();
450: }
451:
452: context.commit();
453:
454: result.setContinue(true);
455: result.setOutcome(true);
456: result.setMessage(new Message("default",
457: "The collection was successfully deleted."));
458:
459: return result;
460: }
461:
462: /**
463: * Create a new collection
464: *
465: * @param context The current DSpace context.
466: * @param communityID The id of the parent community.
467: * @return A process result's object.
468: */
469: public static FlowResult processCreateCollection(Context context,
470: int communityID, Request request) throws SQLException,
471: AuthorizeException, IOException {
472: FlowResult result = new FlowResult();
473:
474: Community parent = Community.find(context, communityID);
475: Collection newCollection = parent.createCollection();
476:
477: // Get the metadata
478: String name = request.getParameter("name");
479: String shortDescription = request
480: .getParameter("short_description");
481: String introductoryText = request
482: .getParameter("introductory_text");
483: String copyrightText = request.getParameter("copyright_text");
484: String sideBarText = request.getParameter("side_bar_text");
485: String license = request.getParameter("license");
486: String provenanceDescription = request
487: .getParameter("provenance_description");
488:
489: // If they don't have a name then make it untitled.
490: if (name == null || name.length() == 0)
491: name = "Untitled";
492:
493: // If empty, make it null.
494: if (shortDescription != null && shortDescription.length() == 0)
495: shortDescription = null;
496: if (introductoryText != null && introductoryText.length() == 0)
497: introductoryText = null;
498: if (copyrightText != null && copyrightText.length() == 0)
499: copyrightText = null;
500: if (sideBarText != null && sideBarText.length() == 0)
501: sideBarText = null;
502: if (license != null && license.length() == 0)
503: license = null;
504: if (provenanceDescription != null
505: && provenanceDescription.length() == 0)
506: provenanceDescription = null;
507:
508: // Save the metadata
509: newCollection.setMetadata("name", name);
510: newCollection
511: .setMetadata("short_description", shortDescription);
512: newCollection
513: .setMetadata("introductory_text", introductoryText);
514: newCollection.setMetadata("copyright_text", copyrightText);
515: newCollection.setMetadata("side_bar_text", sideBarText);
516: newCollection.setMetadata("license", license);
517: newCollection.setMetadata("provenance_description",
518: provenanceDescription);
519:
520: // Set the logo
521: Object object = request.get("logo");
522: Part filePart = null;
523: if (object instanceof Part)
524: filePart = (Part) object;
525:
526: if (filePart != null && filePart.getSize() > 0) {
527: InputStream is = filePart.getInputStream();
528:
529: newCollection.setLogo(is);
530: }
531:
532: // Save everything
533: newCollection.update();
534: context.commit();
535: // success
536: result.setContinue(true);
537: result.setOutcome(true);
538: result.setMessage(new Message("default",
539: "The collection was successfully created."));
540: result.setParameter("collectionID", newCollection.getID());
541:
542: return result;
543: }
544:
545: // Community related functions
546:
547: /**
548: * Create a new community
549: *
550: * @param context The current DSpace context.
551: * @param communityID The id of the parent community (-1 for a top-level community).
552: * @return A process result's object.
553: */
554: public static FlowResult processCreateCommunity(Context context,
555: int communityID, Request request)
556: throws AuthorizeException, IOException, SQLException {
557: FlowResult result = new FlowResult();
558:
559: Community parent = Community.find(context, communityID);
560: Community newCommunity;
561:
562: if (parent != null)
563: newCommunity = parent.createSubcommunity();
564: else
565: newCommunity = Community.create(null, context);
566:
567: String name = request.getParameter("name");
568: String shortDescription = request
569: .getParameter("short_description");
570: String introductoryText = request
571: .getParameter("introductory_text");
572: String copyrightText = request.getParameter("copyright_text");
573: String sideBarText = request.getParameter("side_bar_text");
574:
575: // If they don't have a name then make it untitled.
576: if (name == null || name.length() == 0)
577: name = "Untitled";
578:
579: // If empty, make it null.
580: if (shortDescription != null && shortDescription.length() == 0)
581: shortDescription = null;
582: if (introductoryText != null && introductoryText.length() == 0)
583: introductoryText = null;
584: if (copyrightText != null && copyrightText.length() == 0)
585: copyrightText = null;
586: if (sideBarText != null && sideBarText.length() == 0)
587: sideBarText = null;
588:
589: newCommunity.setMetadata("name", name);
590: newCommunity.setMetadata("short_description", shortDescription);
591: newCommunity.setMetadata("introductory_text", introductoryText);
592: newCommunity.setMetadata("copyright_text", copyrightText);
593: newCommunity.setMetadata("side_bar_text", sideBarText);
594:
595: // Upload the logo
596: Object object = request.get("logo");
597: Part filePart = null;
598: if (object instanceof Part)
599: filePart = (Part) object;
600:
601: if (filePart != null && filePart.getSize() > 0) {
602: InputStream is = filePart.getInputStream();
603:
604: newCommunity.setLogo(is);
605: }
606:
607: // Save everything
608: newCommunity.update();
609: context.commit();
610: // success
611: result.setContinue(true);
612: result.setOutcome(true);
613: result.setMessage(new Message("default",
614: "The community was successfully created."));
615: result.setParameter("communityID", newCommunity.getID());
616:
617: return result;
618: }
619:
620: /**
621: * Process the community metadata edit form.
622: *
623: * @param context The current DSpace context.
624: * @param communityID The community id.
625: * @param deleteLogo Determines if the logo should be deleted along with the metadata editing action.
626: * @param request the Cocoon request object
627: * @return A process result's object.
628: */
629: public static FlowResult processEditCommunity(Context context,
630: int communityID, boolean deleteLogo, Request request)
631: throws AuthorizeException, IOException, SQLException {
632: FlowResult result = new FlowResult();
633:
634: Community community = Community.find(context, communityID);
635:
636: String name = request.getParameter("name");
637: String shortDescription = request
638: .getParameter("short_description");
639: String introductoryText = request
640: .getParameter("introductory_text");
641: String copyrightText = request.getParameter("copyright_text");
642: String sideBarText = request.getParameter("side_bar_text");
643:
644: // If they don't have a name then make it untitled.
645: if (name == null || name.length() == 0)
646: name = "Untitled";
647:
648: // If empty, make it null.
649: if (shortDescription != null && shortDescription.length() == 0)
650: shortDescription = null;
651: if (introductoryText != null && introductoryText.length() == 0)
652: introductoryText = null;
653: if (copyrightText != null && copyrightText.length() == 0)
654: copyrightText = null;
655: if (sideBarText != null && sideBarText.length() == 0)
656: sideBarText = null;
657:
658: // Save the data
659: community.setMetadata("name", name);
660: community.setMetadata("short_description", shortDescription);
661: community.setMetadata("introductory_text", introductoryText);
662: community.setMetadata("copyright_text", copyrightText);
663: community.setMetadata("side_bar_text", sideBarText);
664:
665: if (deleteLogo) {
666: // Remove the logo
667: community.setLogo(null);
668: } else {
669: // Update the logo
670: Object object = request.get("logo");
671: Part filePart = null;
672: if (object instanceof Part)
673: filePart = (Part) object;
674:
675: if (filePart != null && filePart.getSize() > 0) {
676: InputStream is = filePart.getInputStream();
677:
678: community.setLogo(is);
679: }
680: }
681:
682: // Save everything
683: community.update();
684: context.commit();
685:
686: // No notice...
687: result.setContinue(true);
688: return result;
689: }
690:
691: /**
692: * Delete community itself
693: *
694: * @param context The current DSpace context.
695: * @param communityID The community id.
696: * @return A process result's object.
697: */
698: public static FlowResult processDeleteCommunity(Context context,
699: int communityID) throws SQLException, AuthorizeException,
700: IOException {
701: FlowResult result = new FlowResult();
702:
703: Community community = Community.find(context, communityID);
704:
705: community.delete();
706: context.commit();
707:
708: result.setContinue(true);
709: result.setOutcome(true);
710: result.setMessage(new Message("default",
711: "The community was successfully deleted."));
712:
713: return result;
714: }
715:
716: /**
717: * Check whether this metadata value is a proper XML fragment. If the value is not
718: * then an error message will be returned that might (sometimes not) tell the user how
719: * to correct the problem.
720: *
721: * @param value The metadat's value
722: * @return An error string of the problem or null if there is no problem with the metadata's value.
723: */
724: public static String checkXMLFragment(String value) {
725: // escape the ampersand correctly;
726: value = escapeXMLEntities(value);
727:
728: // Try and parse the XML into a mini-dom
729: String xml = "<fragment>" + value + "</fragment>";
730:
731: ByteArrayInputStream inputStream = new ByteArrayInputStream(xml
732: .getBytes());
733:
734: SAXBuilder builder = new SAXBuilder();
735: try {
736: // This will generate an error if not valid XML.
737: builder.build(inputStream);
738: } catch (JDOMException jdome) {
739: // It's not XML
740: return jdome.getMessage();
741: } catch (IOException ioe) {
742: // This shouldn't ever occure because we are parsing
743: // an in-memory string, but in case it does we'll just return
744: // it as a normal error.
745: return ioe.getMessage();
746: }
747:
748: return null;
749: }
750:
751: /**
752: * Sanatize any XML that was inputed by the user, this will clean up
753: * any unescaped characters so that they can be stored as proper XML.
754: * These are errors that in general we want to take care of on behalf
755: * of the user.
756: *
757: * @param value The unsantized value
758: * @return A sanatized value
759: */
760: public static String escapeXMLEntities(String value) {
761: if (value == null)
762: return null;
763:
764: // Escape any XML entities
765: int amp = -1;
766: while ((amp = value.indexOf('&', amp + 1)) > -1) {
767: // Is it an xml entity named by number?
768: if (substringCompare(value, amp + 1, '#'))
769: continue;
770:
771: // &
772: if (substringCompare(value, amp + 1, 'a', 'm', 'p', ';'))
773: continue;
774:
775: // '
776: if (substringCompare(value, amp + 1, 'a', 'p', 'o', 's',
777: ';'))
778: continue;
779:
780: // "
781: if (substringCompare(value, amp + 1, 'q', 'u', 'o', 't',
782: ';'))
783: continue;
784:
785: // <
786: if (substringCompare(value, amp + 1, 'l', 't', ';'))
787: continue;
788:
789: // >
790: if (substringCompare(value, amp + 1, 'g', 't', ';'))
791: continue;
792:
793: // Replace the ampersand with an XML entity.
794: value = value.substring(0, amp) + "&"
795: + value.substring(amp + 1);
796: }
797:
798: return value;
799: }
800:
801: /**
802: * Check if the given character sequence is located in the given
803: * string at the specified index. If it is then return true, otherwise false.
804: *
805: * @param string The string to test against
806: * @param index The location within the string
807: * @param characters The character sequence to look for.
808: * @return true if the character sequence was found, otherwise false.
809: */
810: private static boolean substringCompare(String string, int index,
811: char... characters) {
812: // Is the string long enough?
813: if (string.length() <= index + characters.length)
814: return false;
815:
816: // Do all the characters match?
817: for (char character : characters) {
818: if (string.charAt(index) != character)
819: return false;
820: index++;
821: }
822:
823: return false;
824: }
825:
826: }
|