001: /**********************************************************************************
002: * $URL:https://source.sakaiproject.org/svn/osp/trunk/glossary/api-impl/src/java/org/theospi/portfolio/help/HelpManagerImpl.java $
003: * $Id:HelpManagerImpl.java 9134 2006-05-08 20:28:42Z chmaurer@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.theospi.portfolio.help;
021:
022: import java.io.BufferedOutputStream;
023: import java.io.BufferedReader;
024: import java.io.File;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.InputStreamReader;
028: import java.io.OutputStream;
029: import java.io.OutputStreamWriter;
030: import java.io.Reader;
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.Set;
037: import java.util.zip.Adler32;
038: import java.util.zip.CheckedOutputStream;
039: import java.util.zip.ZipEntry;
040: import java.util.zip.ZipInputStream;
041: import java.util.zip.ZipOutputStream;
042:
043: import org.apache.commons.logging.Log;
044: import org.apache.commons.logging.LogFactory;
045: import org.hibernate.HibernateException;
046: import org.jdom.CDATA;
047: import org.jdom.Document;
048: import org.jdom.Element;
049: import org.jdom.JDOMException;
050: import org.jdom.input.SAXBuilder;
051: import org.jdom.output.XMLOutputter;
052: import org.sakaiproject.content.api.ContentHostingService;
053: import org.sakaiproject.content.api.ContentResource;
054: import org.sakaiproject.exception.IdUnusedException;
055: import org.sakaiproject.exception.PermissionException;
056: import org.sakaiproject.exception.TypeException;
057: import org.sakaiproject.exception.UnsupportedFileTypeException;
058: import org.sakaiproject.metaobj.shared.DownloadableManager;
059: import org.sakaiproject.metaobj.shared.mgt.AgentManager;
060: import org.sakaiproject.metaobj.shared.mgt.IdManager;
061: import org.sakaiproject.metaobj.shared.model.Agent;
062: import org.sakaiproject.metaobj.shared.model.Id;
063: import org.sakaiproject.metaobj.shared.model.MimeType;
064: import org.sakaiproject.metaobj.shared.model.PersistenceException;
065: import org.sakaiproject.metaobj.worksite.mgt.WorksiteManager;
066: import org.sakaiproject.site.api.Site;
067: import org.sakaiproject.tool.api.Placement;
068: import org.sakaiproject.tool.api.ToolManager;
069: import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
070: import org.theospi.portfolio.help.model.Glossary;
071: import org.theospi.portfolio.help.model.GlossaryDescription;
072: import org.theospi.portfolio.help.model.GlossaryEntry;
073: import org.theospi.portfolio.help.model.HelpFunctionConstants;
074: import org.theospi.portfolio.help.model.HelpManager;
075: import org.theospi.portfolio.security.AuthorizationFacade;
076: import org.theospi.portfolio.security.AuthorizationFailedException;
077: import org.theospi.portfolio.shared.model.Node;
078: import org.theospi.utils.zip.UncloseableZipInputStream;
079:
080: /**
081: * This implementation uses the spring config to configure the system, and
082: * uses as a database for indexing resources, and configuring which contexts
083: * are associated with what resources. Lucene is also responsible for
084: * performing help searches.
085: *
086: * <br/><br/>
087: *
088: * Contexts are mapped to views in the spring config. To do this, define
089: * a bean of type, org.theospi.portfolio.help.model.HelpContextConfig.
090: * Create a map of contexts which are keyed by the view name. Contexts are
091: * just string ids. An example:
092: * <br/><br/>
093: * <bean id="presentationHelpContexts" class="org.theospi.portfolio.help.model.HelpContextConfig"><br/>
094: * <constructor-arg><br/>
095: * <map><br/>
096: * <entry key="addPresentation1"><br/>
097: * <list> <br/>
098: * <value>Creating a Presentation</value><br/>
099: * </list> <br/>
100: * </entry> <br/>
101: * ...
102: * <br/><br/>
103: * An explanation: what this means is that when a user navigates to the
104: * addPresentation1 view a context called "Creating a Presentation" is created.
105: * This context is just an identifier for possible actions the user might perform
106: * from this page.
107: * <br/><br/>
108: * To create resources define a bean of type, org.theospi.portfolio.help.model.Resource.
109: * The name is the display name that is shown on jsp pages. The location is
110: * the url of the resource. Configure all contexts associated with this resource.
111: * An example,
112: * <br/><br/>
113: * <bean id="pres_resource_2" class="org.theospi.portfolio.help.model.Resource"> <br/>
114: * <property name="name"><value>Creating a Presentation</value></property> <br/>
115: * <property name="location"><value>${system.baseUrl}/help/creatingPresentations.html</value></property><br/>
116: * <property name="contexts"><br/>
117: * <list><br/>
118: * <value>Creating a Presentation</value><br/>
119: * </list><br/>
120: * </property><br/>
121: * </bean><br/>
122: * <br/><br/>
123: * If all this is configured correctly, when a user navigates to the addPresentation1
124: * view a context of "Creating a Presentation" is created. If the user navigates
125: * to help, the user will be presented with links to all the resources associated with
126: * this context.
127: * <br/><br/>
128: *
129: * @see org.theospi.portfolio.help.model.Resource
130: * @see org.theospi.portfolio.help.model.Source
131: *
132: */
133: public class HelpManagerImpl extends HibernateDaoSupport implements
134: HelpManager, HelpFunctionConstants, DownloadableManager {
135:
136: protected final Log logger = LogFactory.getLog(getClass());
137: private Glossary glossary;
138: private IdManager idManager;
139: private AuthorizationFacade authzManager;
140: private WorksiteManager worksiteManager;
141: private ToolManager toolManager;
142: private ContentHostingService contentHosting;
143: private AgentManager agentManager;
144: private List globalSites;
145: private List globalSiteTypes;
146:
147: public GlossaryEntry searchGlossary(String keyword) {
148: return getGlossary().find(keyword,
149: toolManager.getCurrentPlacement().getContext());
150: }
151:
152: public boolean isPhraseStart(String phraseFragment) {
153: return getGlossary().isPhraseStart(phraseFragment,
154: toolManager.getCurrentPlacement().getContext());
155: }
156:
157: public void setIdManager(IdManager idManager) {
158: this .idManager = idManager;
159: }
160:
161: public Glossary getGlossary() {
162: return glossary;
163: }
164:
165: public void setGlossary(Glossary glossary) {
166: this .glossary = glossary;
167: }
168:
169: public ContentHostingService getContentHosting() {
170: return contentHosting;
171: }
172:
173: public void setContentHosting(ContentHostingService contentHosting) {
174: this .contentHosting = contentHosting;
175: }
176:
177: public AgentManager getAgentManager() {
178: return agentManager;
179: }
180:
181: public void setAgentManager(AgentManager agentManager) {
182: this .agentManager = agentManager;
183: }
184:
185: public GlossaryEntry addEntry(GlossaryEntry newEntry) {
186: if (isGlobal()) {
187: //Prepare for Global add
188: getAuthzManager().checkPermission(ADD_TERM,
189: idManager.getId(GLOBAL_GLOSSARY_QUALIFIER));
190: newEntry.setWorksiteId(null);
191: } else {
192: //Prepare for Local add
193: getAuthzManager().checkPermission(ADD_TERM, getToolId());
194:
195: newEntry.setWorksiteId(getWorksiteManager()
196: .getCurrentWorksiteId().getValue());
197: }
198:
199: if (entryExists(newEntry)) {
200: throw new PersistenceException(
201: "Glossary term {0} already defined.",
202: new Object[] { newEntry.getTerm() }, "term");
203: }
204:
205: return getGlossary().addEntry(newEntry);
206: }
207:
208: public void removeEntry(GlossaryEntry entry) {
209: if (isGlobal()) {
210: getAuthzManager().checkPermission(DELETE_TERM,
211: idManager.getId(GLOBAL_GLOSSARY_QUALIFIER));
212: getGlossary().removeEntry(entry);
213: } else {
214: getAuthzManager().checkPermission(DELETE_TERM, getToolId());
215:
216: if (entry.getWorksiteId().equals(
217: getWorksiteManager().getCurrentWorksiteId()
218: .getValue())) {
219: getGlossary().removeEntry(entry);
220: } else {
221: throw new AuthorizationFailedException(
222: "Unable to update from another worksite");
223: }
224: }
225: }
226:
227: public void updateEntry(GlossaryEntry entry) {
228: if (isGlobal()) {
229: getAuthzManager().checkPermission(EDIT_TERM,
230: idManager.getId(GLOBAL_GLOSSARY_QUALIFIER));
231: entry.setWorksiteId(null);
232: } else {
233: getAuthzManager().checkPermission(EDIT_TERM, getToolId());
234:
235: if (!entry.getWorksiteId().equals(
236: getWorksiteManager().getCurrentWorksiteId()
237: .getValue())) {
238: throw new AuthorizationFailedException(
239: "Unable to update from another worksite");
240: }
241: }
242: if (entryExists(entry)) {
243: throw new PersistenceException(
244: "Glossary term {0} already defined.",
245: new Object[] { entry.getTerm() }, "term");
246: }
247: getGlossary().updateEntry(entry);
248: }
249:
250: public boolean isMaintainer() {
251: return getAuthzManager().isAuthorized(
252: WorksiteManager.WORKSITE_MAINTAIN,
253: idManager.getId(toolManager.getCurrentPlacement()
254: .getContext()));
255: }
256:
257: public Collection getWorksiteTerms() {
258: return getWorksiteTerms(getWorksiteManager()
259: .getCurrentWorksiteId().getValue());
260: }
261:
262: public Collection getWorksiteTerms(String workSite) {
263: if (isGlobal()) {
264: return getGlossary().findAllGlobal();
265: } else {
266: return getGlossary().findAll(workSite);
267: }
268: }
269:
270: public boolean isGlobal() {
271: String siteId = getWorksiteManager().getCurrentWorksiteId()
272: .getValue();
273:
274: if (getGlobalSites().contains(siteId)) {
275: return true;
276: }
277:
278: Site site = getWorksiteManager().getSite(siteId);
279: if (site.getType() != null
280: && getGlobalSiteTypes().contains(site.getType())) {
281: return true;
282: }
283:
284: return false;
285: }
286:
287: protected boolean entryExists(GlossaryEntry entry) {
288: return entryExists(entry, toolManager.getCurrentPlacement()
289: .getContext());
290: }
291:
292: protected boolean entryExists(GlossaryEntry entry, String worksite) {
293: Collection entryFound = getGlossary().findAll(entry.getTerm(),
294: worksite);
295: for (Iterator i = entryFound.iterator(); i.hasNext();) {
296: GlossaryEntry entryIter = (GlossaryEntry) i.next();
297: String entryWID = entryIter.getWorksiteId();
298:
299: if (entryIter.getId().equals(entry.getId())) {
300: continue;
301: } else if (entryWID == null && isGlobal()) {
302: return true;
303: } else if (entryWID != null) {
304: return true;
305: }
306: }
307:
308: return false;
309: }
310:
311: public String packageForDownload(Map params, OutputStream out)
312: throws IOException {
313:
314: packageGlossaryForExport(getWorksiteManager()
315: .getCurrentWorksiteId(), out);
316:
317: //Blank filename for now -- no more dangerous, since the request is in the form of a filename
318: return "";
319: }
320:
321: public void packageGlossaryForExport(Id worksiteId, OutputStream os)
322: throws IOException {
323: if (isGlobal()) {
324: getAuthzManager().checkPermission(
325: HelpFunctionConstants.EXPORT_TERMS,
326: idManager.getId(GLOBAL_GLOSSARY_QUALIFIER));
327: } else {
328: getAuthzManager().checkPermission(
329: HelpFunctionConstants.EXPORT_TERMS, getToolId());
330: }
331: CheckedOutputStream checksum = new CheckedOutputStream(os,
332: new Adler32());
333: ZipOutputStream zos = new ZipOutputStream(
334: new BufferedOutputStream(checksum));
335:
336: putWorksiteTermsIntoZip(worksiteId, zos);
337:
338: zos.finish();
339: zos.flush();
340: }
341:
342: public void putWorksiteTermsIntoZip(Id worksiteId,
343: ZipOutputStream zout) throws IOException {
344: Collection terms = getWorksiteTerms(worksiteId.getValue());
345:
346: Element rootNode = new Element("ospiGlossary");
347:
348: rootNode.setAttribute("formatVersion", "2.1");
349:
350: for (Iterator iter = terms.iterator(); iter.hasNext();) {
351: GlossaryEntry ge = (GlossaryEntry) iter.next();
352:
353: //loads the long description
354: ge = getGlossary().load(ge.getId());
355:
356: Element termNode = new Element("ospiTerm");
357:
358: Element attrNode = new Element("term");
359: attrNode.addContent(new CDATA(ge.getTerm()));
360: termNode.addContent(attrNode);
361:
362: attrNode = new Element("description");
363: attrNode.addContent(new CDATA(ge.getDescription()));
364: termNode.addContent(attrNode);
365:
366: attrNode = new Element("longDescription");
367: attrNode.addContent(new CDATA(ge.getLongDescription()));
368: termNode.addContent(attrNode);
369:
370: rootNode.addContent(termNode);
371: }
372: storeFileInZip(zout, new java.io.StringReader(
373: (new XMLOutputter())
374: .outputString(new Document(rootNode))),
375: "glossaryTerms.xml");
376: }
377:
378: protected void storeFileInZip(ZipOutputStream zos, Reader in,
379: String entryName) throws IOException {
380:
381: char data[] = new char[1024 * 10];
382:
383: if (File.separatorChar == '\\') {
384: entryName = entryName.replace('\\', '/');
385: }
386:
387: ZipEntry newfileEntry = new ZipEntry(entryName);
388:
389: zos.putNextEntry(newfileEntry);
390:
391: BufferedReader origin = new BufferedReader(in, data.length);
392: OutputStreamWriter osw = new OutputStreamWriter(zos);
393: int count;
394: while ((count = origin.read(data, 0, data.length)) != -1) {
395: osw.write(data, 0, count);
396: }
397: origin.close();
398: osw.flush();
399: zos.closeEntry();
400: in.close();
401: }
402:
403: public Node getNode(Id artifactId) {
404: String id = getContentHosting().resolveUuid(
405: artifactId.getValue());
406: if (id == null) {
407: return null;
408: }
409:
410: try {
411: ContentResource resource = getContentHosting().getResource(
412: id);
413: String ownerId = resource.getProperties().getProperty(
414: resource.getProperties().getNamePropCreator());
415: Agent owner = getAgentManager().getAgent(
416: idManager.getId(ownerId));
417: return new Node(artifactId, resource, owner);
418: } catch (PermissionException e) {
419: logger.error("", e);
420: throw new RuntimeException(e);
421: } catch (IdUnusedException e) {
422: logger.error("", e);
423: throw new RuntimeException(e);
424: } catch (TypeException e) {
425: logger.error("", e);
426: throw new RuntimeException(e);
427: }
428: }
429:
430: /**
431: * Given a resource id, this parses out the GlossaryEntries from its input stream.
432: * Once the enties are found, they are inserted into the users current worksite. If a term exists
433: * in the worksite, then execute based on the last parameter.
434: * @param worksiteId Id
435: * @param resourceId an String
436: * @param replaceExisting boolean
437: */
438: public void importTermsResource(String resourceId,
439: boolean replaceExisting) throws IOException,
440: UnsupportedFileTypeException, JDOMException {
441: importTermsResource(
442: getWorksiteManager().getCurrentWorksiteId(),
443: resourceId, replaceExisting);
444: }
445:
446: /**
447: * Given a resource id, this parses out the GlossaryEntries from its input stream.
448: * Once the enties are found, they are inserted into the given worksite. If a term exists
449: * in the worksite, then execute based on the last parameter.
450: * @param worksiteId Id
451: * @param resourceId an String
452: * @param replaceExisting boolean
453: */
454: public void importTermsResource(Id worksiteId, String resourceId,
455: boolean replaceExisting) throws IOException,
456: UnsupportedFileTypeException, JDOMException {
457: Node node = getNode(idManager.getId(resourceId));
458: if (node.getMimeType().equals(new MimeType("text/xml"))
459: || node.getMimeType().equals(
460: new MimeType("application/x-osp"))
461: || node.getMimeType().equals(
462: new MimeType("application/xml"))) {
463: importTermsStream(worksiteId, node.getInputStream(),
464: replaceExisting);
465: } else if (node.getMimeType().equals(
466: new MimeType("application/zip"))
467: || node.getMimeType().equals(
468: new MimeType("application/x-zip-compressed"))) {
469: ZipInputStream zis = new UncloseableZipInputStream(node
470: .getInputStream());
471:
472: ZipEntry currentEntry = zis.getNextEntry();
473: boolean found = false;
474: while (currentEntry != null) {
475: if (currentEntry.getName().endsWith("xml")) {
476: importTermsStream(worksiteId, zis, replaceExisting);
477: found = true;
478: }
479: zis.closeEntry();
480: currentEntry = zis.getNextEntry();
481: }
482:
483: if (!found)
484: throw new UnsupportedFileTypeException(
485: "No glossary xml files were found");
486:
487: } else {
488: throw new UnsupportedFileTypeException(
489: "Unsupported file type");
490: }
491: }
492:
493: /**
494: * Given an xml File stream, this parses out the GlossaryEntries from the input stream.
495: * Once the enties are found, they are inserted into the given worksite. If a term exists
496: * in the worksite, then execute based on the last parameter.
497: * @param worksiteId Id
498: * @param inStream an xml InputStream
499: * @param replaceExisting boolean
500: */
501: public void importTermsStream(Id worksiteId, InputStream inStream,
502: boolean replaceExisting)
503: throws UnsupportedFileTypeException, JDOMException,
504: IOException {
505:
506: SAXBuilder builder = new SAXBuilder();
507:
508: try {
509: Document document = builder.build(new InputStreamReader(
510: inStream));
511: List entries = extractEntries(document);
512: String worksiteStr = worksiteId.getValue();
513:
514: for (Iterator iter = entries.iterator(); iter.hasNext();) {
515: GlossaryEntry entry = (GlossaryEntry) iter.next();
516: entry.setWorksiteId(worksiteStr);
517: boolean exists = entryExists(entry, worksiteStr);
518: if (!exists || (exists && replaceExisting)) {
519: if (!exists) {
520: addEntry(entry);
521: } else {
522: GlossaryEntry existingEntry = getGlossary()
523: .find(entry.getTerm(), worksiteStr);
524: existingEntry.setDescription(entry
525: .getDescription());
526: existingEntry.setLongDescription(entry
527: .getLongDescription());
528: updateEntry(existingEntry);
529: }
530: }
531: }
532:
533: } catch (UnsupportedFileTypeException ufte) {
534: throw ufte;
535: } catch (JDOMException jdome) {
536: logger.error(jdome);
537: throw jdome;
538: }
539: }
540:
541: /**
542: * Given an xml document this reads out the glossary entries.
543: * @param document XML Dom Document
544: * @return List of GlossaryEntry
545: */
546: public List extractEntries(Document document)
547: throws UnsupportedFileTypeException {
548: Element topNode = document.getRootElement();
549:
550: List ospiTerms = topNode.getChildren("ospiTerm");
551:
552: if (ospiTerms.size() == 0)
553: throw new UnsupportedFileTypeException(
554: "No glossary term node found");
555:
556: List entries = new ArrayList();
557:
558: for (Iterator iter = ospiTerms.iterator(); iter.hasNext();) {
559: Element ospiTerm = (Element) iter.next();
560:
561: GlossaryEntry entry = new GlossaryEntry();
562: entry.setLongDescriptionObject(new GlossaryDescription());
563: entry.setTerm(ospiTerm.getChildTextTrim("term"));
564: entry.setDescription(ospiTerm
565: .getChildTextTrim("description"));
566: entry.setLongDescription(ospiTerm
567: .getChildTextTrim("longDescription"));
568: entries.add(entry);
569: }
570: return entries;
571: }
572:
573: protected Id getToolId() {
574: Placement placement = toolManager.getCurrentPlacement();
575: return idManager.getId(placement.getId());
576: }
577:
578: public AuthorizationFacade getAuthzManager() {
579: return authzManager;
580: }
581:
582: public void setAuthzManager(AuthorizationFacade authzManager) {
583: this .authzManager = authzManager;
584: }
585:
586: public WorksiteManager getWorksiteManager() {
587: return worksiteManager;
588: }
589:
590: public void setWorksiteManager(WorksiteManager worksiteManager) {
591: this .worksiteManager = worksiteManager;
592: }
593:
594: public void removeFromSession(Object obj) {
595: this .getHibernateTemplate().evict(obj);
596: try {
597: getHibernateTemplate().getSessionFactory().evict(
598: obj.getClass());
599: } catch (HibernateException e) {
600: logger.error(e);
601: }
602: }
603:
604: public Set getSortedWorksiteTerms() {
605: return getGlossary().getSortedWorksiteTerms(
606: toolManager.getCurrentPlacement().getContext());
607: }
608:
609: public ToolManager getToolManager() {
610: return toolManager;
611: }
612:
613: public void setToolManager(ToolManager toolManager) {
614: this .toolManager = toolManager;
615: }
616:
617: public List getGlobalSites() {
618: return globalSites;
619: }
620:
621: public void setGlobalSites(List globalSites) {
622: this .globalSites = globalSites;
623: }
624:
625: public List getGlobalSiteTypes() {
626: return globalSiteTypes;
627: }
628:
629: public void setGlobalSiteTypes(List globalSiteTypes) {
630: this.globalSiteTypes = globalSiteTypes;
631: }
632:
633: }
|