/**********************************************************************************
* $URL:https://source.sakaiproject.org/svn/osp/trunk/glossary/api-impl/src/java/org/theospi/portfolio/help/HelpManagerImpl.java $
* $Id:HelpManagerImpl.java 9134 2006-05-08 20:28:42Z chmaurer@iupui.edu $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.theospi.portfolio.help;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.jdom.CDATA;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.content.api.ContentResource;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.exception.TypeException;
import org.sakaiproject.exception.UnsupportedFileTypeException;
import org.sakaiproject.metaobj.shared.DownloadableManager;
import org.sakaiproject.metaobj.shared.mgt.AgentManager;
import org.sakaiproject.metaobj.shared.mgt.IdManager;
import org.sakaiproject.metaobj.shared.model.Agent;
import org.sakaiproject.metaobj.shared.model.Id;
import org.sakaiproject.metaobj.shared.model.MimeType;
import org.sakaiproject.metaobj.shared.model.PersistenceException;
import org.sakaiproject.metaobj.worksite.mgt.WorksiteManager;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.tool.api.Placement;
import org.sakaiproject.tool.api.ToolManager;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.theospi.portfolio.help.model.Glossary;
import org.theospi.portfolio.help.model.GlossaryDescription;
import org.theospi.portfolio.help.model.GlossaryEntry;
import org.theospi.portfolio.help.model.HelpFunctionConstants;
import org.theospi.portfolio.help.model.HelpManager;
import org.theospi.portfolio.security.AuthorizationFacade;
import org.theospi.portfolio.security.AuthorizationFailedException;
import org.theospi.portfolio.shared.model.Node;
import org.theospi.utils.zip.UncloseableZipInputStream;
/**
* This implementation uses the spring config to configure the system, and
* uses as a database for indexing resources, and configuring which contexts
* are associated with what resources. Lucene is also responsible for
* performing help searches.
*
* <br/><br/>
*
* Contexts are mapped to views in the spring config. To do this, define
* a bean of type, org.theospi.portfolio.help.model.HelpContextConfig.
* Create a map of contexts which are keyed by the view name. Contexts are
* just string ids. An example:
* <br/><br/>
* <bean id="presentationHelpContexts" class="org.theospi.portfolio.help.model.HelpContextConfig"><br/>
* <constructor-arg><br/>
* <map><br/>
* <entry key="addPresentation1"><br/>
* <list> <br/>
* <value>Creating a Presentation</value><br/>
* </list> <br/>
* </entry> <br/>
* ...
* <br/><br/>
* An explanation: what this means is that when a user navigates to the
* addPresentation1 view a context called "Creating a Presentation" is created.
* This context is just an identifier for possible actions the user might perform
* from this page.
* <br/><br/>
* To create resources define a bean of type, org.theospi.portfolio.help.model.Resource.
* The name is the display name that is shown on jsp pages. The location is
* the url of the resource. Configure all contexts associated with this resource.
* An example,
* <br/><br/>
* <bean id="pres_resource_2" class="org.theospi.portfolio.help.model.Resource"> <br/>
* <property name="name"><value>Creating a Presentation</value></property> <br/>
* <property name="location"><value>${system.baseUrl}/help/creatingPresentations.html</value></property><br/>
* <property name="contexts"><br/>
* <list><br/>
* <value>Creating a Presentation</value><br/>
* </list><br/>
* </property><br/>
* </bean><br/>
* <br/><br/>
* If all this is configured correctly, when a user navigates to the addPresentation1
* view a context of "Creating a Presentation" is created. If the user navigates
* to help, the user will be presented with links to all the resources associated with
* this context.
* <br/><br/>
*
* @see org.theospi.portfolio.help.model.Resource
* @see org.theospi.portfolio.help.model.Source
*
*/
public class HelpManagerImpl extends HibernateDaoSupport
implements HelpManager, HelpFunctionConstants, DownloadableManager {
protected final Log logger = LogFactory.getLog(getClass());
private Glossary glossary;
private IdManager idManager;
private AuthorizationFacade authzManager;
private WorksiteManager worksiteManager;
private ToolManager toolManager;
private ContentHostingService contentHosting;
private AgentManager agentManager;
private List globalSites;
private List globalSiteTypes;
public GlossaryEntry searchGlossary(String keyword) {
return getGlossary().find(keyword, toolManager.getCurrentPlacement().getContext());
}
public boolean isPhraseStart(String phraseFragment) {
return getGlossary().isPhraseStart(phraseFragment, toolManager.getCurrentPlacement().getContext());
}
public void setIdManager(IdManager idManager){
this.idManager = idManager;
}
public Glossary getGlossary() {
return glossary;
}
public void setGlossary(Glossary glossary) {
this.glossary = glossary;
}
public ContentHostingService getContentHosting() {
return contentHosting;
}
public void setContentHosting(ContentHostingService contentHosting) {
this.contentHosting = contentHosting;
}
public AgentManager getAgentManager() {
return agentManager;
}
public void setAgentManager(AgentManager agentManager) {
this.agentManager = agentManager;
}
public GlossaryEntry addEntry(GlossaryEntry newEntry) {
getAuthzManager().checkPermission(ADD_TERM,
getToolId());
if (isGlobal()) {
//Prepare for Global add
newEntry.setWorksiteId(null);
} else {
//Prepare for Local add
newEntry.setWorksiteId(getWorksiteManager().getCurrentWorksiteId().getValue());
}
if (entryExists(newEntry)) {
throw new PersistenceException("Glossary term {0} already defined.",
new Object[]{newEntry.getTerm()}, "term");
}
return getGlossary().addEntry(newEntry);
}
public void removeEntry(GlossaryEntry entry) {
getAuthzManager().checkPermission(DELETE_TERM,
getToolId());
if (isGlobal()) {
getGlossary().removeEntry(entry);
}
else {
if (entry.getWorksiteId().equals(getWorksiteManager().getCurrentWorksiteId().getValue())) {
getGlossary().removeEntry(entry);
}
else {
throw new AuthorizationFailedException("Unable to update from another worksite");
}
}
}
public void updateEntry(GlossaryEntry entry) {
getAuthzManager().checkPermission(EDIT_TERM,
getToolId());
if (isGlobal()) {
entry.setWorksiteId(null);
}
else {
if (!entry.getWorksiteId().equals(getWorksiteManager().getCurrentWorksiteId().getValue())) {
throw new AuthorizationFailedException("Unable to update from another worksite");
}
}
if (entryExists(entry)) {
throw new PersistenceException("Glossary term {0} already defined.",
new Object[]{entry.getTerm()}, "term");
}
getGlossary().updateEntry(entry);
}
public boolean isMaintainer(){
return getAuthzManager().isAuthorized(WorksiteManager.WORKSITE_MAINTAIN,
idManager.getId(toolManager.getCurrentPlacement().getContext()));
}
public Collection getWorksiteTerms() {
return getWorksiteTerms(getWorksiteManager().getCurrentWorksiteId().getValue());
}
public Collection getWorksiteTerms(String workSite) {
if (isGlobal()) {
return getGlossary().findAllGlobal();
}
else {
return getGlossary().findAll(workSite);
}
}
public boolean isGlobal() {
String siteId = getWorksiteManager().getCurrentWorksiteId().getValue();
if (getGlobalSites().contains(siteId)) {
return true;
}
Site site = getWorksiteManager().getSite(siteId);
if (site.getType() != null && getGlobalSiteTypes().contains(site.getType())) {
return true;
}
return false;
}
protected boolean entryExists(GlossaryEntry entry){
return entryExists(entry, toolManager.getCurrentPlacement().getContext());
}
protected boolean entryExists(GlossaryEntry entry, String worksite){
Collection entryFound = getGlossary().findAll(entry.getTerm(), worksite);
for (Iterator i = entryFound.iterator();i.hasNext();){
GlossaryEntry entryIter = (GlossaryEntry)i.next();
String entryWID = entryIter.getWorksiteId();
if (entryIter.getId().equals(entry.getId())) {
continue;
}
else if (entryWID == null && isGlobal()) {
return true;
}
else if (entryWID != null) {
return true;
}
}
return false;
}
public String packageForDownload(Map params, OutputStream out) throws IOException {
packageGlossaryForExport(getWorksiteManager().getCurrentWorksiteId(), out);
//Blank filename for now -- no more dangerous, since the request is in the form of a filename
return "";
}
public void packageGlossaryForExport(Id worksiteId, OutputStream os)
throws IOException {
getAuthzManager().checkPermission(HelpFunctionConstants.EXPORT_TERMS,
getToolId());
CheckedOutputStream checksum = new CheckedOutputStream(os,
new Adler32());
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(
checksum));
putWorksiteTermsIntoZip(worksiteId, zos);
zos.finish();
zos.flush();
}
public void putWorksiteTermsIntoZip(Id worksiteId, ZipOutputStream zout) throws IOException
{
Collection terms = getWorksiteTerms(worksiteId.getValue());
Element rootNode = new Element("ospiGlossary");
rootNode.setAttribute("formatVersion", "2.1");
for (Iterator iter = terms.iterator(); iter.hasNext();) {
GlossaryEntry ge = (GlossaryEntry) iter.next();
//loads the long description
ge = getGlossary().load(ge.getId());
Element termNode = new Element("ospiTerm");
Element attrNode = new Element("term");
attrNode.addContent(new CDATA(ge.getTerm()));
termNode.addContent(attrNode);
attrNode = new Element("description");
attrNode.addContent(new CDATA(ge.getDescription()));
termNode.addContent(attrNode);
attrNode = new Element("longDescription");
attrNode.addContent(new CDATA(ge.getLongDescription()));
termNode.addContent(attrNode);
rootNode.addContent(termNode);
}
storeFileInZip(zout, new java.io.StringReader(
(new XMLOutputter()).outputString(new Document(rootNode))), "glossaryTerms.xml");
}
protected void storeFileInZip(ZipOutputStream zos, Reader in,
String entryName) throws IOException {
char data[] = new char[1024 * 10];
if (File.separatorChar == '\\') {
entryName = entryName.replace('\\', '/');
}
ZipEntry newfileEntry = new ZipEntry(entryName);
zos.putNextEntry(newfileEntry);
BufferedReader origin = new BufferedReader(in, data.length);
OutputStreamWriter osw = new OutputStreamWriter(zos);
int count;
while ((count = origin.read(data, 0, data.length)) != -1) {
osw.write(data, 0, count);
}
origin.close();
osw.flush();
zos.closeEntry();
in.close();
}
public Node getNode(Id artifactId) {
String id = getContentHosting().resolveUuid(artifactId.getValue());
if (id == null) {
return null;
}
try {
ContentResource resource = getContentHosting().getResource(id);
String ownerId = resource.getProperties().getProperty(
resource.getProperties().getNamePropCreator());
Agent owner = getAgentManager().getAgent(
idManager.getId(ownerId));
return new Node(artifactId, resource, owner);
} catch (PermissionException e) {
logger.error("", e);
throw new RuntimeException(e);
} catch (IdUnusedException e) {
logger.error("", e);
throw new RuntimeException(e);
} catch (TypeException e) {
logger.error("", e);
throw new RuntimeException(e);
}
}
/**
* Given a resource id, this parses out the GlossaryEntries from its input stream.
* Once the enties are found, they are inserted into the users current worksite. If a term exists
* in the worksite, then execute based on the last parameter.
* @param worksiteId Id
* @param resourceId an String
* @param replaceExisting boolean
*/
public void importTermsResource(String resourceId, boolean replaceExisting) throws IOException, UnsupportedFileTypeException, JDOMException
{
importTermsResource(getWorksiteManager().getCurrentWorksiteId(), resourceId, replaceExisting);
}
/**
* Given a resource id, this parses out the GlossaryEntries from its input stream.
* Once the enties are found, they are inserted into the given worksite. If a term exists
* in the worksite, then execute based on the last parameter.
* @param worksiteId Id
* @param resourceId an String
* @param replaceExisting boolean
*/
public void importTermsResource(Id worksiteId, String resourceId, boolean replaceExisting)
throws IOException, UnsupportedFileTypeException, JDOMException
{
Node node = getNode(idManager.getId(resourceId));
if(node.getMimeType().equals(new MimeType("text/xml")) ||
node.getMimeType().equals(new MimeType("application/x-osp")) ||
node.getMimeType().equals(new MimeType("application/xml"))) {
importTermsStream(worksiteId, node.getInputStream(), replaceExisting);
} else if(node.getMimeType().equals(new MimeType("application/zip")) ||
node.getMimeType().equals(new MimeType("application/x-zip-compressed"))) {
ZipInputStream zis = new UncloseableZipInputStream(node.getInputStream());
ZipEntry currentEntry = zis.getNextEntry();
boolean found = false;
while(currentEntry != null) {
if(currentEntry.getName().endsWith("xml")) {
importTermsStream(worksiteId, zis, replaceExisting);
found = true;
}
zis.closeEntry();
currentEntry = zis.getNextEntry();
}
if(!found)
throw new UnsupportedFileTypeException("No glossary xml files were found");
} else {
throw new UnsupportedFileTypeException("Unsupported file type");
}
}
/**
* Given an xml File stream, this parses out the GlossaryEntries from the input stream.
* Once the enties are found, they are inserted into the given worksite. If a term exists
* in the worksite, then execute based on the last parameter.
* @param worksiteId Id
* @param inStream an xml InputStream
* @param replaceExisting boolean
*/
public void importTermsStream(Id worksiteId, InputStream inStream, boolean replaceExisting)
throws UnsupportedFileTypeException, JDOMException, IOException
{
SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // SAK-23131
try {
Document document = builder.build(new InputStreamReader(inStream));
List entries = extractEntries(document);
String worksiteStr = worksiteId.getValue();
for(Iterator iter = entries.iterator(); iter.hasNext(); ) {
GlossaryEntry entry = (GlossaryEntry)iter.next();
entry.setWorksiteId(worksiteStr);
boolean exists = entryExists(entry, worksiteStr);
if(!exists || (exists && replaceExisting)) {
if(!exists) {
addEntry(entry);
} else {
GlossaryEntry existingEntry = getGlossary().find(entry.getTerm(), worksiteStr);
existingEntry.setDescription(entry.getDescription());
existingEntry.setLongDescription(entry.getLongDescription());
updateEntry(existingEntry);
}
}
}
} catch(UnsupportedFileTypeException ufte) {
throw ufte;
} catch(JDOMException jdome) {
logger.error(jdome);
throw jdome;
}
}
/**
* Given an xml document this reads out the glossary entries.
* @param document XML Dom Document
* @return List of GlossaryEntry
*/
public List extractEntries(Document document) throws UnsupportedFileTypeException
{
Element topNode = document.getRootElement();
List ospiTerms = topNode.getChildren("ospiTerm");
if(ospiTerms.size() == 0)
throw new UnsupportedFileTypeException("No glossary term node found");
List entries = new ArrayList();
for (Iterator iter = ospiTerms.iterator(); iter.hasNext();) {
Element ospiTerm = (Element) iter.next();
GlossaryEntry entry = new GlossaryEntry();
entry.setLongDescriptionObject(new GlossaryDescription());
entry.setTerm(ospiTerm.getChildTextTrim("term"));
entry.setDescription(ospiTerm.getChildTextTrim("description"));
entry.setLongDescription(ospiTerm.getChildTextTrim("longDescription"));
entries.add(entry);
}
return entries;
}
protected Id getToolId() {
Placement placement = toolManager.getCurrentPlacement();
return idManager.getId(placement.getId());
}
public AuthorizationFacade getAuthzManager() {
return authzManager;
}
public void setAuthzManager(AuthorizationFacade authzManager) {
this.authzManager = authzManager;
}
public WorksiteManager getWorksiteManager() {
return worksiteManager;
}
public void setWorksiteManager(WorksiteManager worksiteManager) {
this.worksiteManager = worksiteManager;
}
public void removeFromSession(Object obj) {
this.getHibernateTemplate().evict(obj);
// Check whether it is a Hibernate-mapping class
Class<? extends Object> clazz;
if (obj instanceof GlossaryEntry) {
clazz = GlossaryEntry.class;
} else if (obj instanceof GlossaryDescription) {
clazz = GlossaryDescription.class;
} else {
clazz = obj.getClass();
}
try {
getHibernateTemplate().getSessionFactory().evict(clazz);
} catch (HibernateException e) {
logger.error(e);
}
}
public Set getSortedWorksiteTerms() {
return getGlossary().getSortedWorksiteTerms(toolManager.getCurrentPlacement().getContext());
}
public ToolManager getToolManager() {
return toolManager;
}
public void setToolManager(ToolManager toolManager) {
this.toolManager = toolManager;
}
public List getGlobalSites() {
return globalSites;
}
public void setGlobalSites(List globalSites) {
this.globalSites = globalSites;
}
public List getGlobalSiteTypes() {
return globalSiteTypes;
}
public void setGlobalSiteTypes(List globalSiteTypes) {
this.globalSiteTypes = globalSiteTypes;
}
}