com.fluidops.iwb.wiki.Wikimedia.java Source code

Java tutorial

Introduction

Here is the source code for com.fluidops.iwb.wiki.Wikimedia.java

Source

/*
 * Copyright (C) 2008-2013, fluid Operations AG
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
    
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
    
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.fluidops.iwb.wiki;

import info.bliki.wiki.model.IWikiModel;
import info.bliki.wiki.model.SemanticAttribute;
import info.bliki.wiki.model.SemanticRelation;
import info.bliki.wiki.template.AbstractTemplateFunction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.Repository;

import com.fluidops.ajax.FClientUpdate;
import com.fluidops.ajax.components.FComponent;
import com.fluidops.iwb.api.EndpointImpl;
import com.fluidops.iwb.api.NamespaceService;
import com.fluidops.iwb.api.ReadDataManager;
import com.fluidops.iwb.api.ReadWriteDataManager;
import com.fluidops.iwb.api.ReadWriteDataManagerImpl;
import com.fluidops.iwb.api.RequestMapper;
import com.fluidops.iwb.api.valueresolver.ValueResolver;
import com.fluidops.iwb.page.PageContext;
import com.fluidops.iwb.util.Config;
import com.fluidops.iwb.widget.AbstractWidget;
import com.fluidops.iwb.wiki.FluidWikiModel.TemplateResolver;
import com.fluidops.iwb.wiki.parserfunction.IWBMagicWords;
import com.fluidops.iwb.wiki.parserfunction.PageContextAwareParserFunction;
import com.fluidops.iwb.wiki.parserfunction.ParserFunctionsFactory;
import com.fluidops.iwb.wiki.parserfunction.ShowParserFunction;
import com.fluidops.iwb.wiki.parserfunction.SparqlParserFunction;
import com.fluidops.iwb.wiki.parserfunction.UrlGetParamParserFunction;
import com.fluidops.iwb.wiki.parserfunction.WidgetParserFunction;
import com.fluidops.util.StringUtil;
import com.google.common.collect.Lists;

/**
 * This class parses Wikimedia page content and
 * extracts semantic statements.
 * 
 * @author Uli, pha
 */
public class Wikimedia {
    private static final Logger logger = Logger.getLogger(Wikimedia.class.getName());

    // the (one and only) wiki storage instance
    static WikiStorage wikiStorage = createWikiStorage();

    /**
     * Retrieve the wiki storage to be used depending on the configuration.
     * @return
     */
    static WikiStorage createWikiStorage() {
        if (Config.getConfig().getStoreWikiInDatabase()) {
            if (Config.getConfig().getUseMySQL())
                return new WikiMySQLStorage();
            return new WikiH2SQLStorage();
        }
        return new WikiFileStorage();
    }

    // html header
    static String htmlHeader;

    /**
     * Gets the HTML.
     * 
     * @return the HTML
     */
    public static String getHTML(String wikitext, final URI id, FComponent parent, Date version, PageContext pc) {
        if (wikitext == null || wikitext.length() == 0)
            return "(No text defined for this topic)";

        final FluidWikiModel wikiModel = new FluidWikiModel(id, parent);
        ParserFunctionsFactory.registerPageContextAwareParserFunctions(wikiModel, pc);
        final WidgetParserFunction widgetParser = new WidgetParserFunction(parent);
        ParserFunctionsFactory.registerParserFunction(wikiModel, pc, widgetParser);
        ParserFunctionsFactory.registerParserFunction(wikiModel, pc, new SparqlParserFunction());
        ParserFunctionsFactory.registerParserFunction(wikiModel, pc, new ShowParserFunction());
        ParserFunctionsFactory.registerParserFunction(wikiModel, pc, new UrlGetParamParserFunction());

        wikiModel.setPageName(EndpointImpl.api().getDataManager().getLabel(id));
        wikiModel.addTemplateResolver(new FluidTemplateResolver(wikiModel, version));
        wikiModel.addTemplateResolver(new LegacyWidgetTemplateResolver(widgetParser));

        String html;
        PageContext _oldPc = PageContext.getThreadPageContext();
        try {
            PageContext.setThreadPageContext(pc);
            html = wikiModel.render(wikitext);
        } finally {
            PageContext.setThreadPageContext(_oldPc);
        }

        // handle "#REDIRECT [[myUri]]" directive:
        // a) redirect link must be given
        // b) redirect=no is no given as request parameter
        if (wikiModel.getRedirectLink() != null && !("no".equals(pc.getRequestParameter("redirect")))) {

            URI redirectUri = EndpointImpl.api().getNamespaceService().guessURI(wikiModel.getRedirectLink());
            if (redirectUri == null)
                return "Error: requested redirect to " + StringEscapeUtils.escapeHtml(wikiModel.getRedirectLink())
                        + " cannot be performed. Not a valid resource.";
            RequestMapper rm = EndpointImpl.api().getRequestMapper();
            String location = rm.getRequestStringFromValue(redirectUri);
            // add the redirectSource (used as a redirect hint on the new page)
            location += (location.contains("?") ? "&" : "?") + "redirectedFrom="
                    + StringUtil.urlEncode(rm.getReconvertableUri(id, false));
            parent.addClientUpdate(new FClientUpdate("document.location = '" + location + "';"));
            return "Redirect to " + wikiModel.getRedirectLink();
        }

        // try to resolve template variables like $this.Host/cpuUsage
        html = replaceTemplateVariables(html, id, pc.repository);

        return html;
    }

    /**
     * Parses the given wikitext for occurrences of #widget definitions
     * and registers the widget class to {@link FluidWikiModel}. This 
     * method is a preprocessing step allowing to have access to all
     * widget classes prior to actually rendering.
     * 
     * @param wikitext
     * @param id
     * @param included
     * @return list of widget classes
     */
    public static List<Class<? extends AbstractWidget<?>>> parseWidgets(String wikitext, final URI id) {

        // TODO think if we can avoid the additional parsing step as this is only
        // used for jsUrls mechanism in SemWiki. In Bug 10060

        if (wikitext == null || wikitext.length() == 0)
            return FluidWikiModel.getParsedWidgets();

        final FluidWikiModel wikiModel = new FluidWikiModel(id);
        wikiModel.addTemplateResolver(new TemplateResolver() {

            @SuppressWarnings("unchecked")
            @Override
            public String resolveTemplate(String namespace, String templateName,
                    Map<String, String> templateParameters, URI page, FComponent parent) {

                if (templateName.startsWith("#widget")) {
                    try {
                        String widgetName = templateName.substring(templateName.lastIndexOf(":") + 1).trim();
                        String widgetClassName = EndpointImpl.api().getWidgetService().getWidgetClass(widgetName);

                        if (widgetClassName == null)
                            return null;

                        FluidWikiModel.addParsedWidget(
                                (Class<? extends AbstractWidget<?>>) Class.forName(widgetClassName));
                        return "";
                    } catch (Exception e) {
                        logger.warn("Error while parsing widgets: " + e.getMessage());
                        logger.debug("Details: ", e);
                    }
                }
                return null;
            }
        });
        FluidTemplateResolver tplResolver = new FluidTemplateResolver(wikiModel, null);
        tplResolver.setIgnoreErrors(true);
        wikiModel.addTemplateResolver(tplResolver);
        wikiModel.setUp();
        wikiModel.parseTemplates(wikitext);

        return FluidWikiModel.getParsedWidgets();
    }

    /**
     * Parses the given wikiText for the occurrences of included page templates and resolves them as URIs. 
     * Filters out all widget definitions and magic words. 
     * 
     * @param wikiText
     * @param id
     * @return List of URIs corresponding to included page templates.
     */
    public static List<URI> parseIncludedTemplates(String wikiText, final URI id) {
        // Take the templates included in the Wiki text in '{{}}' brackets.
        if (wikiText == null || wikiText.length() == 0)
            return Collections.emptyList();

        final List<URI> res = Lists.newArrayList();

        FluidWikiModel wikiModel = new FluidWikiModel(id);

        ParserFunctionsFactory.registerParserFunction(wikiModel, null,
                new IfIncludedTemplateFinder(res, id, "#ifexpr"));
        ParserFunctionsFactory.registerParserFunction(wikiModel, null,
                new IfIncludedTemplateFinder(res, id, "#if"));

        wikiModel.addTemplateResolver(new DefaultIncludedTemplateResolver(res));
        wikiModel.addTemplateResolver(new SparqlIncludedTemplateResolver(res));

        wikiModel.setUp();
        wikiModel.parseTemplates(wikiText);

        return res;
    }

    /**
     * Detects the templates defined as parts of the '#ifexpr' function
     *
     */
    private static class IfIncludedTemplateFinder extends AbstractTemplateFunction
            implements PageContextAwareParserFunction {

        private List<URI> res;
        private URI id;
        private String functionName;

        /**
         * IfExprTemplateFinder has a list of templates URIs where all detected templates will be added
         *  and the current resource
         * @param res list of templates URIs
         * @param id the current resource
         */
        public IfIncludedTemplateFinder(List<URI> res, URI id, String functionName) {
            super();
            this.res = res;
            this.id = id;
            this.functionName = functionName;
        }

        @Override
        public void setPageContext(PageContext pc) {
            //is not used here
        }

        @Override
        public String getFunctionName() {
            return functionName;
        }

        /**
         * Detects the templates defined as parts of the '#ifexpr' function.
         * use {@link Wikimedia#parseIncludedTemplates(String, URI)} to add 
         * the detected templates to the list of all templates
         */
        @Override
        public String parseFunction(List<String> parts, IWikiModel model, char[] src, int beginIndex, int endIndex,
                boolean isSubst) throws IOException {

            if (parts.size() == 0)
                return null;

            //concatenate all parts into a string and parse it to find the templates
            StringBuilder sb = new StringBuilder();
            for (String part : parts) {
                sb.append(part);
            }
            res.addAll(parseIncludedTemplates(sb.toString(), id));

            return "";
        }

    }

    /**
     * Resolves templates defined as a parameter of the '#sparql' function
     *
     */
    private static class SparqlIncludedTemplateResolver implements TemplateResolver {

        private List<URI> res;

        public SparqlIncludedTemplateResolver(List<URI> res) {
            super();
            this.res = res;
        }

        /**
         * Resolve templates defined as a parameter 'template' of the '#sparql' function
         *
         */
        @Override
        public String resolveTemplate(String namespace, String templateName, Map<String, String> templateParameters,
                URI page, FComponent parent) {

            if (templateName.startsWith("#sparql")) {
                templateName = templateParameters.get("template");
                if (templateName != null) {
                    URI templateURI = FluidWikiModel.resolveTemplateURI(templateName, namespace);
                    if (templateURI != null)
                        res.add(templateURI);
                }
            }
            return null;
        }
    }

    /**
     * Resolves standard templates found directly in the wiki text.
     * Does not handle the cases where the templates are defined within custom functions 
     * configurations.
     *
     */
    private static class DefaultIncludedTemplateResolver implements TemplateResolver {

        private List<URI> res;

        public DefaultIncludedTemplateResolver(List<URI> res) {
            super();
            this.res = res;
        }

        @Override
        public String resolveTemplate(String namespace, String templateName, Map<String, String> templateParameters,
                URI page, FComponent parent) {

            if (templateName.startsWith("#"))
                return null;
            if (IWBMagicWords.isMagicWord(templateName))
                return null;
            URI templateURI = FluidWikiModel.resolveTemplateURI(templateName, namespace);
            if (templateURI != null)
                res.add(templateURI);
            return null;
        }
    }

    /**
     * Extracts the semantic relations from the wiki and
     * returns them in form of (context-free) statements.
     *  
     * @param wikitext the wikitext
     * @param id the URI of the resource/page
     *
     */
    public static List<Statement> getSemanticRelations(String wikitext, final URI id) {
        List<Statement> semanticRelations = new ArrayList<Statement>();

        if (wikitext == null || wikitext.isEmpty())
            return semanticRelations;

        FluidWikiModel wikiModel = new FluidWikiModel(id);
        wikiModel.render(wikitext);

        List<SemanticRelation> rels = wikiModel.getSemanticRelations();
        List<SemanticAttribute> atts = wikiModel.getSemanticAttributes();

        ReadDataManager dm = EndpointImpl.api().getDataManager();

        //make sure all statements are valid (check for nulls in predicate or object)
        //if at least one statement contains null as predicate or object 
        //none of the statements will be added in the repository (check bug [5922])

        if (rels != null) {
            // objects of semantic relations [[a::b]] are mapped to either literals or URIs, preferably to URIs
            for (SemanticRelation sr : rels) {
                NamespaceService ns = EndpointImpl.api().getNamespaceService();
                URI predicate = ns.guessURI(sr.getRelation());

                Value object = dm.guessValueForPredicate(sr.getValue(), predicate, true);

                if (predicate != null && object != null)
                    semanticRelations.add(ValueFactoryImpl.getInstance().createStatement(id, predicate, object));
            }
        }
        if (atts != null) {
            // objects of semantic attributes [[a:=b]] are always mapped to literals
            for (SemanticAttribute sa : atts) {
                NamespaceService ns = EndpointImpl.api().getNamespaceService();
                URI predicate = ns.guessURI(sa.getAttribute());

                Value object = dm.createLiteralForPredicate(sa.getValue(), predicate);

                if (predicate != null && object != null)
                    semanticRelations.add(ValueFactoryImpl.getInstance().createStatement(id, predicate, object));
            }
        }

        return semanticRelations;
    }

    /**
     * Returns Wiki content for the given URI (topic).
     * @param topic
     * @return
     */
    public static String getWikiContent(URI topic, Date version) {
        return wikiStorage.getRawWikiContent(topic, version);
    }

    /**
     * Returns Wiki content for the given URI (topic).
     * @param topic
     * @return
     */
    public static String getRawWikiContent(URI topic, Date version) {
        return wikiStorage.getRawWikiContent(topic, version);
    }

    /**
     * Returns the WikiStorage instance.
     * 
     * @return
     */
    public static WikiStorage getWikiStorage() {
        return wikiStorage;
    }

    /**
     * Initializes the thread-local renderer context.
     */
    public static void initializeHTMLRenderer() {
        FluidWikiModel.initModel();
    }

    /**
     * Returns the rendered components from the renderer context.
     * @return
     */
    public static List<FComponent> getRenderedComponents() {
        return FluidWikiModel.getRenderedComponents();
    }

    /**
     * We replace special variables like $this.Host/cpuUsage$ by the value
     * that we obtain a for property Host/cpuUsage w.r.t to val. If
     * a variable has no value w.r.t. val, the value <i>(undefined)</i> is
     * replaced instead. There's a special handling for thumbnail vocabulary:
     * there, we try to embed the picture.
     * 
     * @param content The String which includes the variables
     * @param val The variable value
     * @return Returns the finished String
     */
    private static String replaceTemplateVariables(String content, Value val, Repository repository) {
        String ret = content.replaceAll("\\$this\\$", StringEscapeUtils.escapeHtml(val.stringValue()));

        // Support for variables like this: $this[IMAGE]$
        Pattern pat = Pattern.compile("\\$this(\\[([a-zA-Z_0-9]+)\\])\\$");
        Matcher matcher = pat.matcher(content);
        while (matcher.find()) {
            String rt = matcher.group(2) == null ? ValueResolver.DEFAULT : matcher.group(2);
            String valueStr = ValueResolver.resolveValue(rt, val);
            ret = ret.replace(matcher.group(0), valueStr);
        }

        ArrayList<LookupConfig> properties = new ArrayList<LookupConfig>();

        // collect outgoing special variables
        extractProperties(properties, ret, "\\$this\\.([^\\[\\$]+)(\\[([a-zA-Z_0-9]+)\\])?\\$", true);

        // collect incoming special variables
        extractProperties(properties, ret, "\\$(\\S+)\\.this(\\[([a-zA-Z_0-9]+)\\])?\\$", false);

        // if no replacement necessary, we're done
        if (properties.isEmpty())
            return ret;

        // Build the query
        String query = buildQueryUnion(properties);

        // execute the SPARQL query and replace the content
        ReadWriteDataManager dm = null;
        TupleQueryResult res = null;
        try {
            dm = ReadWriteDataManagerImpl.openDataManager(repository);
            res = dm.sparqlSelect(query, true, val, false);

            while (res.hasNext()) {
                BindingSet bs = res.next();

                Iterator<Binding> iter = bs.iterator();
                while (iter.hasNext()) {

                    // name is v0, v1, .. => add into corresponding properties results
                    Binding b = iter.next();
                    int index = Integer.parseInt(b.getName().substring(1));
                    // implicit indexing using array list, add to properties result list
                    properties.get(index).results.add(b.getValue());
                }
            }

            for (LookupConfig cfg : properties)
                ret = replaceVariables(ret, cfg);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            ReadWriteDataManagerImpl.closeQuietly(res);
            ReadWriteDataManagerImpl.closeQuietly(dm);
        }

        return ret;
    }

    /**
     * All properties are extracted of a String, i.e. a template
     * 
     * @param properties An ArrayList that is filled with (uniquely) extracted configurations, which match the specified pattern
     * @param ret The content String, i.e. the template
     * @param pattern The pattern to be matched
     * @param outgoing if true=>outgoing edge, if false=>incoming edge
     * 
     * @return 
     */
    public static void extractProperties(ArrayList<LookupConfig> properties, String ret, String pattern,
            boolean outgoing) {

        //        extractProperties(properties, ret, "\\$(\\S+)\\.this(\\[([a-zA-Z_0-9]+)\\])?\\$",false);
        Pattern pat = Pattern.compile(pattern);
        Matcher matcher = pat.matcher(ret);
        while (matcher.find()) {
            LookupConfig cfg = new LookupConfig();
            cfg.match = matcher.group(0); // full string
            cfg.property = matcher.group(1); // predicate name
            cfg.resolver = matcher.group(3); // clearer
            cfg.outgoing = outgoing; // true if x.this

            // we make sure the namespace is defined, otherwise the query
            // would fail later on and not extract any result at all
            Map<String, String> namespaces = EndpointImpl.api().getNamespaceService()
                    .getRegisteredNamespacePrefixes();
            String[] spl = cfg.property.split(":"); // ... if there is a real namespace
            if (spl.length > 1 && !namespaces.containsKey(spl[0]))
                continue; // if we do not know about this namespace prefix, we ignore this variable. TODO log error

            // check if we have this match already in list ("match" is the key)
            boolean contains = false;
            for (LookupConfig cfg2 : properties) {
                if (cfg2.match.equals(cfg.match)) {
                    contains = true;
                    break;
                }
            }

            if (!contains)
                properties.add(cfg);
        }
    }

    /**
     * Build SPARQL query that extracts all values using UNION
     * 
     * @param propertiesIn
     * @param propertiesOut
     * @return Returns the query as a String
     */
    public static String buildQueryUnion(ArrayList<LookupConfig> properties) {
        StringBuilder query = new StringBuilder("SELECT ");
        for (int i = 0; i < properties.size(); i++)
            query.append(" ?v").append(i); // ?v0, ?v1, ...
        query.append(" WHERE {  \n");

        for (int i = 0; i < properties.size(); i++) {
            LookupConfig cfg = properties.get(i);
            // make the default namespace explicit if the user did not
            String property = cfg.property;
            URI propertyURI = EndpointImpl.api().getNamespaceService().matchStandardURI(property);
            if (propertyURI != null)
                property = "<" + propertyURI.stringValue() + ">";
            else if (!property.contains(":"))
                property = ":" + property;

            cfg.bindingName = "v" + i;

            if (i > 0)
                query.append("UNION ");
            if (cfg.outgoing)
                query.append("{ ?? " + property + " ?" + cfg.bindingName + " }\n");
            else
                query.append("{ ?" + cfg.bindingName + " " + property + " ?? }\n");
        }
        query.append("}");

        return query.toString();
    }

    /**
     * Replace variables specified by cfg.match within ret using the cfg.resolver and the obtained results
     */
    private static String replaceVariables(String ret, LookupConfig cfg) {
        String rt = cfg.resolver == null ? ValueResolver.DEFAULT : cfg.resolver;
        String valueStr = ValueResolver.resolveValues(rt, cfg.results);
        if (valueStr == null)
            ret = ret.replace(cfg.match, "");
        else
            ret = ret.replace(cfg.match, valueStr);
        return ret;
    }

    /**
     * Configuration for replacing variables in templates, for each $this.property[resolver]$
     * there is exactly one config.
     *  
     * @author as
     *
     */
    // TODO: move logic to public place
    public static class LookupConfig {
        public String match;
        public String property;
        public String resolver;
        public boolean outgoing = true; // marker if outgoing or ingoing edge
        public String bindingName; // the variable name for results (i.e v0,v1,..)
        public List<Value> results = new LinkedList<Value>();
    }
}