/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/help/trunk/help-component/src/java/org/sakaiproject/component/app/help/HelpManagerImpl.java $ * $Id: HelpManagerImpl.java 128310 2013-08-08 18:23:41Z matthew@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 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.sakaiproject.component.app.help; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.Searcher; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import org.hibernate.HibernateException; import org.hibernate.Session; import org.sakaiproject.api.app.help.Category; import org.sakaiproject.api.app.help.Context; import org.sakaiproject.api.app.help.Glossary; import org.sakaiproject.api.app.help.GlossaryEntry; import org.sakaiproject.api.app.help.HelpManager; import org.sakaiproject.api.app.help.Resource; import org.sakaiproject.api.app.help.RestConfiguration; import org.sakaiproject.api.app.help.Source; import org.sakaiproject.api.app.help.TableOfContents; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.app.help.model.CategoryBean; import org.sakaiproject.component.app.help.model.ContextBean; import org.sakaiproject.component.app.help.model.ResourceBean; import org.sakaiproject.component.app.help.model.SourceBean; import org.sakaiproject.component.app.help.model.TableOfContentsBean; import org.sakaiproject.tool.api.Tool; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.user.api.PreferencesService; import org.sakaiproject.user.api.UserDirectoryService; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.UrlResource; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException; import org.springframework.orm.hibernate3.HibernateTransactionManager; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * HelpManager provides database and search capabilitites for the Sakai help tool. * @author <a href="mailto:jlannan.iupui.edu">Jarrod Lannan</a> * @version $Id: HelpManagerImpl.java 128310 2013-08-08 18:23:41Z matthew@longsight.com $ * */ public class HelpManagerImpl extends HibernateDaoSupport implements HelpManager { private static final String QUERY_GETRESOURCEBYDOCID = "query.getResourceByDocId"; private static final String QUERY_GETCATEGORYBYNAME = "query.getCategoryByName"; private static final String QUERY_GET_WELCOME_PAGE = "query.getWelcomePage"; private static final String DOCID = "docId"; private static final String WELCOME_PAGE = "welcomePage"; private static final String NAME = "name"; private static final String LUCENE_INDEX_PATH = System .getProperty("java.io.tmpdir") + File.separator + "sakai.help"; private static final String TOC_API = "org.sakaiproject.api.app.help.TableOfContents"; private static String EXTERNAL_URL; private static String DEFAULT_HELP_FILE = "help.xml"; private static String HELP_BASENAME = "help"; private static String DEFAULT_LOCALE = "default"; private Map<String, List> helpContextConfig = new HashMap<String, List>(); private int contextSize; private RestConfiguration restConfiguration; private ServerConfigurationService serverConfigurationService; // Map which contains all localized help toc private Map<String, TableOfContentsBean> toc; // All supported locales private List<String> locales; private Boolean initialized = Boolean.FALSE; private Object initializedLock = new Object(); private Glossary glossary; private String supportEmailAddress; private ToolManager toolManager; private HibernateTransactionManager txManager; private static final Log LOG = LogFactory.getLog(HelpManagerImpl.class); /** * @see org.sakaiproject.api.app.help.HelpManager#getServerConfigurationService() */ public ServerConfigurationService getServerConfigurationService() { return serverConfigurationService; } /** * @see org.sakaiproject.api.app.help.HelpManager#setServerConfigurationService(org.sakaiproject.service.framework.config.ServerConfigurationService) */ public void setServerConfigurationService(ServerConfigurationService s) { serverConfigurationService = s; } private PreferencesService preferencesService; public void setPreferencesService(PreferencesService preferencesService) { this.preferencesService = preferencesService; } private UserDirectoryService userDirectoryService; public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } public List getContexts(String mappedView) { return (List) helpContextConfig.get(mappedView); } public List getActiveContexts(Map session) { List contexts = (List) session.get("help_contexts"); if (contexts == null) { contexts = new SizedList(getContextSize()); session.put("help_contexts", contexts); } return contexts; } public void addContexts(Map session, String mappedView) { List newContexts = getContexts(mappedView); List contexts = getActiveContexts(session); if (newContexts != null) { contexts.addAll(newContexts); } } /** * return list of resources matching context id * * @param contextId * @return */ public Set<Resource> getResources(Long contextId) { return searchResources(new TermQuery(new Term("context", "\"" + contextId + "\""))); } /** * Store resource * @see org.sakaiproject.api.app.help.HelpManager#storeResource(org.sakaiproject.api.help.Entity) */ public void storeResource(Resource resource) { getHibernateTemplate().saveOrUpdate(resource); } /** * @see org.sakaiproject.api.app.help.HelpManager#getResource(java.lang.Long) */ public Resource getResource(Long id) { return (ResourceBean) getHibernateTemplate().get(ResourceBean.class, id); } /** * @see org.sakaiproject.api.app.help.HelpManager#deleteResource(java.lang.Long) */ public void deleteResource(Long resourceId) { Resource resource = getResource(resourceId); if (resource == null) return; getHibernateTemplate().delete(resource); } /** * @see org.sakaiproject.api.app.help.HelpManager#getSource(java.lang.Long) */ public Source getSource(Long id) { try { return (SourceBean) getHibernateTemplate().load(SourceBean.class, id); } catch (HibernateObjectRetrievalFailureException e) { return null; } } /** * @see org.sakaiproject.api.app.help.HelpManager#storeSource(org.sakaiproject.api.help.Source) */ public void storeSource(Source source) { getHibernateTemplate().saveOrUpdate(source); } /** * @see org.sakaiproject.api.app.help.HelpManager#deleteSource(java.lang.Long) */ public void deleteSource(Long sourceId) { Source source = getSource(sourceId); if (source == null) return; getHibernateTemplate().delete(source); } /** * @see org.sakaiproject.api.app.help.HelpManager#getContext(java.lang.Long) */ public Context getContext(Long id) { try { return (ContextBean) getHibernateTemplate().load(ContextBean.class, id); } catch (HibernateObjectRetrievalFailureException e) { return null; } } /** * @see org.sakaiproject.api.app.help.HelpManager#storeContext(org.sakaiproject.api.help.Context) */ public void storeContext(Context context) { getHibernateTemplate().saveOrUpdate(context); } /** * @see org.sakaiproject.api.app.help.HelpManager#deleteContext(java.lang.Long) */ public void deleteContext(Long contextId) { Context context = getContext(contextId); if (context == null) return; getHibernateTemplate().delete(context); } /** * @see org.sakaiproject.api.app.help.HelpManager#getResourcesForActiveContexts(java.util.Map) */ public Map getResourcesForActiveContexts(Map session) { Map<String, Set<Resource>> resourceMap = new HashMap<String, Set<Resource>>(); List<String> activeContexts = getActiveContexts(session); for(String context : activeContexts) { try { Set<Resource> resources = searchResources(new TermQuery(new Term("context", "\"" + context + "\""))); if (resources != null && resources.size() > 0) { resourceMap.put(context, resources); } } catch (Exception e) { LOG.error(e); } } return resourceMap; } /** * @see org.sakaiproject.api.app.help.HelpManager#searchResources(java.lang.String) */ public Set<Resource> searchResources(String queryStr) { initialize(); try { return searchResources(queryStr, "content"); } catch (ParseException e) { LOG.debug("ParseException parsing Help search query " + queryStr, e); return null; } } /** * @see org.sakaiproject.api.app.help.HelpManager#getTableOfContents() */ public TableOfContents getTableOfContents() { initialize(); return getToc(); } /** * @see org.sakaiproject.api.app.help.HelpManager#setTableOfContents(org.sakaiproject.api.help.TableOfContents) */ public void setTableOfContents(TableOfContents toc) { setToc((TableOfContentsBean) toc); } /** * @see org.sakaiproject.api.app.help.HelpManager#searchGlossary(java.lang.String) */ public GlossaryEntry searchGlossary(String keyword) { return getGlossary().find(keyword); } /** * Search Resources * @param query * @return Set of matching results. */ protected Set<Resource> searchResources(Query query) { Set<Resource> results = new HashSet<Resource>(); String locale = getSelectedLocale().toString(); if (!toc.containsKey(locale)) { locale = DEFAULT_LOCALE; } String luceneFolder = LUCENE_INDEX_PATH + File.separator + locale; Searcher searcher = null; FSDirectory dir = null; try { dir = FSDirectory.open(new File(luceneFolder)); searcher = new IndexSearcher(dir, false); LOG.debug("Searching for: " + query.toString()); //Hits hits = searcher.search(query); TopDocs topDocs = searcher.search(query, 1000); ScoreDoc[] hits = topDocs.scoreDocs; LOG.debug(hits.length + " total matching documents"); for (int i = 0; i < hits.length; i++) { ScoreDoc scoreDoc = hits[i]; Document doc = searcher.doc(scoreDoc.doc); ResourceBean resource = getResourceFromDocument(doc); resource.setScore(scoreDoc.score * 100); results.add(resource); } } catch (Exception e) { LOG.error(e); } finally { if (searcher != null) { try { searcher.close(); } catch (IOException e) { //nothing to do } } if (dir != null) { dir.close(); } } return results; } /** * Search Lucene * * @param queryStr * @param defaultField * @return * @throws ParseException */ protected Set<Resource> searchResources(String queryStr, String defaultField) throws ParseException { Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_29); QueryParser parser = new QueryParser(Version.LUCENE_29, defaultField, analyzer); Query query = parser.parse(queryStr); return searchResources(query); } /** * Get Resource From Document. * @param document * @return resource bean */ protected ResourceBean getResourceFromDocument(Document document) { Long id = new Long(document.getField("id").stringValue()); return (ResourceBean) getResource(id); } /** * Get entire Collection of Resources. * @return collection of resources */ protected Collection<? extends Resource> getResources() { return getHibernateTemplate().loadAll(ResourceBean.class); } /** * Get ContextSize. * @return size of Context. */ public int getContextSize() { return contextSize; } /** * Set ContextSize * @param contextSize */ public void setContextSize(int contextSize) { this.contextSize = contextSize; } /** * Get Document. * @param resource * @return document * @throws IOException * @throws MalformedURLException */ protected Document getDocument(ResourceBean resource) throws IOException, MalformedURLException { Document doc = new Document(); if (resource.getContexts() != null) { for (String context : resource.getContexts()) { doc.add(new Field("context", "\"" + context + "\"", Field.Store.YES, Field.Index.NOT_ANALYZED)); } } URL urlResource; URLConnection urlConnection = null; //For local file override String sakaiHomePath = serverConfigurationService.getSakaiHomePath(); String localHelpPath = sakaiHomePath+serverConfigurationService.getString("help.localpath","/help/"); File localFile = new File(localHelpPath+resource.getLocation()); boolean localFileIsFile = false; if(localFile.isFile()) { LOG.debug("Local help file overrides: "+resource.getLocation()); localFileIsFile = true; } StringBuilder sb = new StringBuilder(); if (resource.getLocation() == null || resource.getLocation().startsWith("/")) { // handle REST content if (!getRestConfiguration().getOrganization().equals("sakai")) { urlResource = new URL(getRestConfiguration().getRestUrlInDomain() + resource.getDocId() + "?domain=" + getRestConfiguration().getRestDomain()); urlConnection = urlResource.openConnection(); String basicAuthUserPass = getRestConfiguration().getRestCredentials(); String encoding = Base64.encodeBase64(basicAuthUserPass.getBytes("utf-8")).toString(); urlConnection.setRequestProperty("Authorization", "Basic " + encoding); BufferedReader br = new BufferedReader(new InputStreamReader( urlConnection.getInputStream()), 512); try { int readReturn = 0; char[] cbuf = new char[512]; while ((readReturn = br.read(cbuf, 0, 512)) != -1) { sb.append(cbuf, 0, readReturn); } } finally { br.close(); } // if document is coming from corpus then get document name from xml and assign to resource String resourceName = getRestConfiguration().getResourceNameFromCorpusDoc(sb.toString()); resource.setName(resourceName); storeResource(resource); } else if (!"".equals(EXTERNAL_URL)) { // handle external help location urlResource = new URL(EXTERNAL_URL + resource.getLocation()); } else { // Add the home folder file reading here if(localFileIsFile) { urlResource = localFile.toURI().toURL(); } else { // handle classpath location urlResource = getClass().getResource(resource.getLocation()); } } } else { // handle external location specified in reg file urlResource = new URL(resource.getLocation()); } if (urlResource == null) { return null; } if (resource.getLocation() != null){ String resLocation = resource.getLocation(); if(localFileIsFile) { resLocation = localFile.getPath(); } doc.add(new Field("location", resLocation, Field.Store.YES, Field.Index.NOT_ANALYZED)); } //doc.add(Field.Keyword("id", resource.getId().toString())); doc.add(new Field("id", resource.getId().toString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); if (getRestConfiguration().getOrganization().equals("sakai")) { Reader reader = new BufferedReader(new InputStreamReader(urlResource.openStream())); try { int readReturn = 0; char[] cbuf = new char[512]; while ((readReturn = reader.read(cbuf, 0, 512)) != -1) { sb.append(cbuf, 0, readReturn); } } finally { reader.close(); } } //doc.add(Field.Text("content", sb.toString())); doc.add(new Field("content", sb.toString(), Field.Store.YES, Field.Index.ANALYZED)); return doc; } /** * Get Table Of Contents Bean. * @return table of contents bean */ public TableOfContentsBean getToc() { if (toc == null) { return null; } String locale = getSelectedLocale().toString(); if (toc.containsKey(locale)) { return toc.get(locale); } else { return toc.get(DEFAULT_LOCALE); } } /** * Set Table Of Contents Bean. * @param toc */ public void setToc(TableOfContentsBean toc) { this.toc.put(DEFAULT_LOCALE, toc); } /** * @see org.sakaiproject.api.app.help.HelpManager#getGlossary() */ public Glossary getGlossary() { return glossary; } /** * Set Glossary. * @param glossary */ public void setGlossary(Glossary glossary) { this.glossary = glossary; } /** * @see org.sakaiproject.api.app.help.HelpManager#storeCategory(org.sakaiproject.api.help.Category) */ public void storeCategory(Category category) { getHibernateTemplate().saveOrUpdate(category); } /** * @see org.sakaiproject.api.app.help.HelpManager#createCategory() */ public Category createCategory() { return new CategoryBean(); } /** * @see org.sakaiproject.api.app.help.HelpManager#createResource() */ public Resource createResource() { return new ResourceBean(); } /** * @see org.sakaiproject.api.app.help.HelpManager#getResourceByDocId(java.lang.String) */ public Resource getResourceByDocId(final String docId) { HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { org.hibernate.Query q = session .getNamedQuery(QUERY_GETRESOURCEBYDOCID); q.setString(DOCID, (docId == null) ? null : docId.toLowerCase()); if (q.list().size() == 0){ return null; } else{ return (Resource) q.list().get(0); } } }; Resource resource = (Resource) getHibernateTemplate().execute(hcb); return resource; } /** * @see org.sakaiproject.api.app.help.HelpManager#getWelcomePage() */ public String getWelcomePage() { initialize(); HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { org.hibernate.Query q = session .getNamedQuery(QUERY_GET_WELCOME_PAGE); q.setString(WELCOME_PAGE, "true"); if (q.list().size() == 0){ return null; } else{ return ((Resource) q.list().get(0)).getDocId(); } } }; return (String) getHibernateTemplate().execute(hcb); } /** * Find a Category by name * @param name * @return Category */ public Category getCategoryByName(final String name) { HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { org.hibernate.Query q = session .getNamedQuery(QUERY_GETCATEGORYBYNAME); q.setString(NAME, (name == null) ? name : name.toLowerCase()); return q.uniqueResult(); } }; return (Category) getHibernateTemplate().execute(hcb); } /** * Index Categories and Resources * @param categories */ private void indexRecursive(IndexWriter indexWriter, Set<Category> categories) { for (Category category: categories) { Set<Resource> resourcesList = category.getResources(); for (Resource resource : resourcesList) { try { Document doc = getDocument((ResourceBean)resource); if (doc != null) { indexWriter.addDocument(doc); LOG.debug("added resource '" + resource.getName() + "', doc count=" + indexWriter.maxDoc()); } else { LOG.debug("failed to add resource '" + "' (" + resource.getName()); } } catch (IOException e) { LOG.error("I/O error while adding resource '" + "' (" + resource.getName() + "): " + e.getMessage(), e); } } Set<Category> subCategories = category.getCategories(); indexRecursive(indexWriter, subCategories); } } /** * Store the mapping of Categories and Resources * @param categories */ private void storeRecursive(Set<Category> categories) { for(Category category: categories) { Set<Resource> resourcesList = category.getResources(); category.setResources(null); for (Resource resource: resourcesList) { resource.setDocId(resource.getDocId().toLowerCase()); resource.setCategory(category); } category.setResources(resourcesList); this.storeCategory(category); Set<Category> subCategories = category.getCategories(); storeRecursive(subCategories); } } /** * Get Support Email Address. * @see org.sakaiproject.api.app.help.HelpManager#getSupportEmailAddress() */ public String getSupportEmailAddress() { return supportEmailAddress; } /** * set Support Email Address. * @param email */ public void setSupportEmailAddress(String email) { this.supportEmailAddress = email; } /** * get tool manager * @return Returns the toolManager. */ public ToolManager getToolManager() { return toolManager; } /** * set tool manager * @param toolManager The toolManager to set. */ public void setToolManager(ToolManager toolManager) { this.toolManager = toolManager; } /** * @param txManager The txManager to set. */ public void setTxManager(HibernateTransactionManager txManager) { this.txManager = txManager; } /** * @see org.sakaiproject.api.app.help.HelpManager#getRestConfiguration() */ public RestConfiguration getRestConfiguration() { return restConfiguration; } /** * set REST configuration * @param restConfiguration */ public void setRestConfiguration(RestConfiguration restConfiguration) { this.restConfiguration = restConfiguration; } /** * Reinitialize help content from UI */ public void reInitialize(){ synchronized (initializedLock) { initialized = Boolean.FALSE; } initialize(); } /** * Synchronize first access to tool. * @see org.sakaiproject.api.app.help.HelpManager#initialize() */ public void initialize() { if (initialized.booleanValue()) { return; } else { synchronized (initializedLock) { if (!initialized.booleanValue()) { dropExistingContent(); // handle external help content EXTERNAL_URL = getServerConfigurationService().getString( "help.location"); if (!"".equals(EXTERNAL_URL)) { if (EXTERNAL_URL.endsWith("/")) { // remove trailing forward slash EXTERNAL_URL = EXTERNAL_URL.substring(0, EXTERNAL_URL.length() - 1); } } // Get all supported locales locales = new ArrayList<String>(); Locale[] sl = serverConfigurationService.getSakaiLocales(); for (int i = 0; i < sl.length; i++) { locales.add(sl[i].toString()); // Locale toString should generate en_GB type identifiers } // Add default locale locales.add(DEFAULT_LOCALE); toc = new HashMap<String, TableOfContentsBean>(); registerHelpContent(); initialized = Boolean.TRUE; } } } } /** * @see org.sakaiproject.api.app.help.HelpManager#getExternalLocation() */ public String getExternalLocation() { return EXTERNAL_URL; } private void dropExistingContent() { if (LOG.isDebugEnabled()) { LOG.debug("dropExistingContent()"); } TransactionTemplate tt = new TransactionTemplate(txManager); tt.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { getHibernateTemplate().bulkUpdate("delete CategoryBean"); getHibernateTemplate().flush(); return null; } }); } /** * Returns the user locale * @param prefLocales * The prefLocales to set. */ private Locale getSelectedLocale() { Locale loc = preferencesService.getLocale(userDirectoryService.getCurrentUser().getId()); if (loc != null) { return loc; } else { return Locale.getDefault(); } } /** * Register help content either locally or externally * Index resources in Lucene */ private void registerHelpContent() { if (LOG.isDebugEnabled()) { LOG.debug("registerHelpContent()"); } // register external help docs if (!"".equals(EXTERNAL_URL)) { registerExternalHelpContent(EXTERNAL_URL + "/" + DEFAULT_HELP_FILE); } else { registerStaticContent(); } // Create lucene indexes for each toc (which key is either a locale or 'default') for (String key : toc.keySet()) { String luceneIndexPath = LUCENE_INDEX_PATH + File.separator + key; TableOfContentsBean currentToc = toc.get(key); // create index in lucene IndexWriter writer = null; Date start = new Date(); try { //writer = new IndexWriter(luceneIndexPath, new StandardAnalyzer(Version.LUCENE_29), true); FSDirectory directory = FSDirectory.open(new File(luceneIndexPath)); writer = new IndexWriter(directory, new StandardAnalyzer(Version.LUCENE_29), true, IndexWriter.MaxFieldLength.UNLIMITED); } catch (IOException e) { LOG.error("failed to create IndexWriter " + e.getMessage(), e); return; } // Index categories and resources indexRecursive(writer, currentToc.getCategories()); try { writer.optimize(); writer.commit(); writer.close(); } catch (IOException e) { LOG.error("failed to close writer " + e.getMessage(), e); } Date end = new Date(); LOG.info("finished initializing lucene for '" + key + "' in " + (end.getTime() - start.getTime()) + " total milliseconds"); } } /** * register external help content * build document from external reg file * @param externalHelpReg */ public void registerExternalHelpContent(String helpFile) { Set<Category> categories = new TreeSet<Category>(); URL urlResource = null; InputStream ism = null; BufferedInputStream bis = null; try { try { urlResource = new URL(EXTERNAL_URL + "/" + helpFile); ism = urlResource.openStream(); } catch (IOException e) { // Try default help file helpFile = DEFAULT_HELP_FILE; urlResource = new URL(EXTERNAL_URL + "/" + helpFile); ism = urlResource.openStream(); } bis = new BufferedInputStream(ism); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder builder = dbf.newDocumentBuilder(); InputSource is = new org.xml.sax.InputSource(bis); org.w3c.dom.Document xmlDocument = builder.parse(is); Node helpRegNode = (Node) xmlDocument.getDocumentElement(); recursiveExternalReg(helpRegNode, null, categories); // handle corpus docs if (!getRestConfiguration().getOrganization().equals("sakai")){ // get corpus document String corpusXml = getRestConfiguration().getCorpusDocument(); DocumentBuilderFactory dbfCorpus = DocumentBuilderFactory.newInstance(); dbfCorpus.setNamespaceAware(true); DocumentBuilder builderCorpus = dbfCorpus.newDocumentBuilder(); StringReader sReader = new StringReader(corpusXml); InputSource isCorpus = new org.xml.sax.InputSource(sReader); org.w3c.dom.Document xmlDocumentCorpus = builderCorpus.parse(isCorpus); registerCorpusDocs(xmlDocumentCorpus); sReader.close(); } } catch (MalformedURLException e) { LOG.warn("Unable to load external URL: " + EXTERNAL_URL + "/" + helpFile, e); } catch (IOException e) { LOG.warn("I/O error opening external URL: " + EXTERNAL_URL + "/" + helpFile, e); } catch (ParserConfigurationException e) { LOG.error(e.getMessage(), e); } catch (SAXException e) { LOG.error(e.getMessage(), e); } finally { try{ if (bis != null){ bis.close(); } } catch (IOException e){ LOG.error("error closing stream", e); } } // Add to toc map TableOfContentsBean externalToc = new TableOfContentsBean(); externalToc.setCategories(categories); setTableOfContents(externalToc); } /** ** @return Locale based on its string representation (language_region) **/ private Locale getLocaleFromString(String localeString) { return serverConfigurationService.getLocaleFromString(localeString); } /** * Adds help for a specific locale * @param path * @param locale */ private void addToolHelp(String path, String locale) { URL urlResource = null; String classpathUrl = null; String sakaiHomePath = serverConfigurationService.getSakaiHomePath(); String localHelpPath = sakaiHomePath+serverConfigurationService.getString("help.localpath","/help/"); File localFile = null; // find default help file if ( locale.equals(DEFAULT_LOCALE) ) { classpathUrl = path + "/" + HELP_BASENAME + ".xml"; localFile = new File(localHelpPath+classpathUrl); if(localFile.isFile()) try { urlResource = localFile.toURI().toURL(); } catch (MalformedURLException e) { urlResource = getClass().getResource(classpathUrl); } else urlResource = getClass().getResource(classpathUrl); } // find localized help file else { classpathUrl = path + "/" + HELP_BASENAME + "_" + locale + ".xml"; localFile = new File(localHelpPath+classpathUrl); if(localFile.isFile()) try { urlResource = localFile.toURI().toURL(); } catch (MalformedURLException e) { urlResource = getClass().getResource(classpathUrl); } else urlResource = getClass().getResource(classpathUrl); // If language/region help file not found, look for language-only help file if ( urlResource == null ) { Locale nextLocale = getLocaleFromString(locale); classpathUrl = path + "/" + HELP_BASENAME + "_" + nextLocale.getLanguage() + ".xml"; localFile = new File(localHelpPath+classpathUrl); if(localFile.isFile()) try { urlResource = localFile.toURI().toURL(); } catch (MalformedURLException e) { urlResource = getClass().getResource(classpathUrl); } else urlResource = getClass().getResource(classpathUrl); } // If language-only help file not found, look for default help file if ( urlResource == null ) { classpathUrl = path + "/" + HELP_BASENAME + ".xml"; localFile = new File(localHelpPath+classpathUrl); if(localFile.isFile()) try { urlResource = localFile.toURI().toURL(); } catch (MalformedURLException e) { urlResource = getClass().getResource(classpathUrl); } else urlResource = getClass().getResource(classpathUrl); } } // Url exists? if (urlResource != null) { TableOfContentsBean localizedToc; // Add this tool categories to this tool toc try { org.springframework.core.io.Resource resource = new UrlResource(urlResource); BeanFactory beanFactory = new XmlBeanFactory(resource); TableOfContents tocTemp = (TableOfContents) beanFactory.getBean(TOC_API); Set<Category> categories = tocTemp.getCategories(); storeRecursive(categories); // Get localized toc if (toc.containsKey(locale)) { localizedToc = toc.get(locale); } else { // Create and add localized toc localizedToc = new TableOfContentsBean(); toc.put(locale, localizedToc); } // Update localized toc categories localizedToc.getCategories().addAll(categories); } catch (Exception e) { LOG.warn("Unable to load help index from " + classpathUrl + " : " + e.getMessage()); } } } /** * register local content */ public void registerStaticContent() { // register static content Set<Tool> toolSet = toolManager.findTools(null, null); // find out what we want to ignore List<String> hideHelp = Arrays.asList(StringUtils.split(serverConfigurationService.getString("help.hide"), ",")); if (hideHelp == null) { hideHelp = new ArrayList<String> (); } for (Tool tool : toolSet) { if (tool != null && tool.getId() != null && !hideHelp.contains(tool.getId())) { String[] extraCollections = {}; String toolHelpCollections = tool.getRegisteredConfig().getProperty(TOOLCONFIG_HELP_COLLECTIONS); if (toolHelpCollections != null) extraCollections = StringUtils.split(toolHelpCollections, ","); // Loop throughout the locales list for (String locale : locales) { // Add localized tool helps addToolHelp("/" + tool.getId().toLowerCase().replaceAll("\\.", "_"), locale); // Add any other optional collections for (int k = 0; k < extraCollections.length; k++) { addToolHelp("/" + extraCollections[k], locale); } } } } // Sort the help topics for each locale for (String locale : locales) { TableOfContentsBean localizedToc = toc.get(locale); // Sort this localized toc categories with a TreeSet if (localizedToc != null) { Set<Category> sortedCategories = new TreeSet<Category>(); Set<Category> categories = localizedToc.getCategories(); sortedCategories.addAll(categories); localizedToc.setCategories(sortedCategories); } } } private static int cnt = 0; /** * Parse external help reg doc recursively * @param n * @param category */ public void recursiveExternalReg(Node n, Category category, Set<Category> categories) { if (n == null) { return; } NodeList nodeList = n.getChildNodes(); int nodeListLength = nodeList.getLength(); for (int i = 0; i < nodeListLength; i++) { if (nodeList.item(i).getNodeType() != Node.ELEMENT_NODE) { continue; } Node currentNode = nodeList.item(i); if ("category".equals(currentNode.getNodeName())) { Category childCategory = new CategoryBean(); childCategory.setName(currentNode.getAttributes().getNamedItem("name") .getNodeValue()); if (category != null) { childCategory.setParent(category); category.getCategories().add(childCategory); } storeCategory(childCategory); categories.add(childCategory); LOG.info("adding help category: " + childCategory.getName()); recursiveExternalReg(currentNode, childCategory, categories); } else if ("resource".equals(currentNode.getNodeName())) { Resource resource = new ResourceBean(); NamedNodeMap nnm = currentNode.getAttributes(); if (nnm != null) { // name required resource.setName(nnm.getNamedItem("name").getNodeValue()); if (nnm.getNamedItem("location") != null) { resource.setLocation(nnm.getNamedItem("location").getNodeValue()); } if (nnm.getNamedItem("docId") != null) { resource.setDocId(nnm.getNamedItem("docId").getNodeValue()); } else { resource.setDocId(Integer.valueOf(cnt).toString()); cnt++; } //defaultForTool is an optional attribute if (nnm.getNamedItem("defaultForTool") != null) { resource.setDefaultForTool(nnm.getNamedItem("defaultForTool") .getNodeValue()); } // welcomePage is an optional attribute if (nnm.getNamedItem("welcomePage") != null) { resource.setWelcomePage(nnm.getNamedItem("welcomePage") .getNodeValue().toLowerCase()); } } resource.setCategory(category); category.getResources().add(resource); storeResource(resource); LOG.info("adding help resource: " + resource + " to category: " + category.getName()); recursiveExternalReg(currentNode, category, categories); } } } /** * Parse corpus document * @param doc document */ public void registerCorpusDocs(org.w3c.dom.Document doc) { if (doc == null) return; List<String> arrayCorpus = new ArrayList<String>(); NodeList nodeList = doc.getElementsByTagName("id"); int nodeListLength = nodeList.getLength(); for (int i = 0; i < nodeListLength; i++) { Node currentNode = nodeList.item(i); NodeList nlChildren = currentNode.getChildNodes(); for (int j = 0; j < nlChildren.getLength(); j++){ if (nlChildren.item(j).getNodeType() == Node.TEXT_NODE){ arrayCorpus.add(nlChildren.item(j).getNodeValue()); } } } // iterate through corpus docs and add to home category if not already // added by help.xml external registration // if Home category does not exist, then create it if (getCategoryByName("Home") == null){ Category cat = new CategoryBean(); cat.setName("Home"); storeCategory(cat); } for (int i = 0; i < arrayCorpus.size(); i++){ String currentDocId = (String) arrayCorpus.get(i); // if the corpus doc does not already exist from help.xml, then add it to the Home category if (this.getResourceByDocId(currentDocId) == null){ Resource resource = new ResourceBean(); resource.setDocId(currentDocId); resource.setName(currentDocId); Category homeCategory = getCategoryByName("Home"); resource.setCategory(homeCategory); homeCategory.getResources().add(resource); storeResource(resource); } } } }