001/**
002 * Copyright (C) 2007 - 2016, Jens Lehmann
003 *
004 * This file is part of DL-Learner.
005 *
006 * DL-Learner is free software; you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation; either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * DL-Learner is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
018 */
019package org.dllearner.kb.sparql;
020
021import org.aksw.jena_sparql_api.cache.core.QueryExecutionFactoryCacheEx;
022import org.aksw.jena_sparql_api.cache.extra.CacheFrontend;
023import org.aksw.jena_sparql_api.core.FluentQueryExecutionFactory;
024import org.aksw.jena_sparql_api.core.QueryExecutionFactory;
025import org.aksw.jena_sparql_api.http.QueryExecutionHttpWrapper;
026import org.aksw.jena_sparql_api.model.QueryExecutionFactoryModel;
027import org.aksw.jena_sparql_api.pagination.core.QueryExecutionFactoryPaginated;
028import org.apache.jena.query.QueryExecution;
029import org.apache.jena.rdf.model.*;
030import org.apache.jena.riot.WebContent;
031import org.apache.jena.sparql.engine.http.QueryEngineHTTP;
032import org.apache.log4j.Level;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import java.util.Set;
037
038/**
039 * According to the definition at http://www.w3.org/Submission/CBD/ ...
040 * <p>
041 * An alternative form of description, which includes all statements expressed along both
042 * outbound and inbound arc paths, terminated in like fashion as a concise bounded description but extending from the
043 * starting node in both directions; thus enabling the requesting agent to potentially infer itself any implicit
044 * statements based on symmetric property pairs. We can call this derivative of a concise bounded description a
045 * <em>symmetric concise bounded description</em>.
046 * <p>
047 * Specifically, given a particular node (the starting node) in a particular RDF graph (the source graph), a subgraph of
048 * that particular graph, taken to comprise a symmetric concise bounded description of the resource denoted by the
049 * starting node, can be identified as follows:
050 *
051 * <ol>
052 *     <li>
053 *                      Include in the subgraph all statements in the source graph where the object of the statement is the starting node;
054 *     </li>
055 *     <li>
056 *                      Recursively, for all statements identified in the subgraph thus far having a blank node subject not equal
057 *                      to the starting node, include in the subgraph all statements in the source graph where the object of the
058 *                      statement is the blank node in question and which are not already included in the subgraph.
059 *     </li>
060 *     <li>
061 *                      Recursively, for all statements included in the subgraph thus far, for all reifications of each statement
062 *                      in the source graph, include the symmetric concise bounded description beginning from the rdf:Statement
063 *                      node of each reification.
064 *     </li>
065 *     <li>
066 *                      Include in the subgraph the concise bounded description beginning from the starting node.
067 *     </li>
068 * </ol>
069 * <p>
070 *
071 * @author Lorenz Buehmann
072 */
073public class SymmetricConciseBoundedDescriptionGeneratorImpl implements ConciseBoundedDescriptionGenerator{
074        
075        private static final Logger logger = LoggerFactory.getLogger(SymmetricConciseBoundedDescriptionGeneratorImpl.class);
076        
077        private QueryExecutionFactory qef;
078        
079        private Set<String> namespaces;
080
081        public SymmetricConciseBoundedDescriptionGeneratorImpl(SparqlEndpoint endpoint, CacheFrontend cache) {
082                qef = FluentQueryExecutionFactory
083                                .http(endpoint.getURL().toString(), endpoint.getDefaultGraphURIs())
084                                .config().withPostProcessor(qe -> ((QueryEngineHTTP) ((QueryExecutionHttpWrapper) qe).getDecoratee())
085                                                                                                        .setModelContentType(WebContent.contentTypeRDFXML))
086                                .end()
087                                .create();
088
089                if(cache != null){
090                        qef = new QueryExecutionFactoryCacheEx(qef, cache);
091                }
092                qef = new QueryExecutionFactoryPaginated(qef, 10000);
093        }
094        
095        public SymmetricConciseBoundedDescriptionGeneratorImpl(SparqlEndpoint endpoint) {
096                this(endpoint, null);
097        }
098
099        public SymmetricConciseBoundedDescriptionGeneratorImpl(QueryExecutionFactory qef) {
100                this.qef = qef;
101        }
102
103        public SymmetricConciseBoundedDescriptionGeneratorImpl(Model model) {
104                this(new QueryExecutionFactoryModel(model));
105        }
106
107        /* (non-Javadoc)
108         * @see org.dllearner.kb.sparql.ConciseBoundedDescriptionGenerator#getConciseBoundedDescription(java.lang.String, int, boolean)
109         */
110        @Override
111        public Model getConciseBoundedDescription(String resource, int depth, boolean withTypesForLeafs) {
112                logger.debug("computing CBD of depth {} for {} ...", resource, depth);
113                Model cbd = ModelFactory.createDefaultModel();
114                cbd.add(getIncomingModel(resource, depth));
115                cbd.add(getOutgoingModel(resource, depth));
116                logger.debug("CBD size: {}", cbd.size());
117                return cbd;
118        }
119
120        @Override
121        public void setAllowedPropertyNamespaces(Set<String> namespaces) {
122                this.namespaces = namespaces;
123        }
124        
125        private Model getIncomingModel(String resource, int depth){
126                String query = makeConstructQueryObject2(resource, depth);
127                logger.debug("computing incoming triples for {}\n{}", resource, query);
128                try(QueryExecution qe = qef.createQueryExecution(query)) {
129                        return qe.execConstruct();
130                } catch (Exception e) {
131                        logger.error("Failed to retrieve incoming CBD for " + resource + ".\nQuery:\n" + query, e);
132                }
133                return null;
134        }
135        
136        private Model getOutgoingModel(String resource, int depth){
137                String query = makeConstructQuerySubject(resource, depth);
138                logger.debug("computing outgoing triples for {}\n{}", resource, query);
139                try(QueryExecution qe = qef.createQueryExecution(query)) {
140                        return qe.execConstruct();
141                } catch (Exception e) {
142                        logger.error("Failed to retrieve outgoing CBD for " + resource + ".\nQuery:\n" + query, e);
143                }
144                return null;
145        }
146        
147        /**
148         * A SPARQL CONSTRUCT query is created, to get a RDF graph for the given resource and recursion depth.
149         * @param resource The resource for which a CONSTRUCT query is created.
150         * @return The CONSTRUCT query
151         */
152        private String makeConstructQuerySubject(String resource, int depth){
153                StringBuilder sb = new StringBuilder();
154                sb.append("CONSTRUCT {\n");
155                sb.append("<").append(resource).append("> ").append("?p0 ").append("?o0").append(".\n");
156                for(int i = 1; i < depth; i++){
157                        sb.append("?o").append(i-1).append(" ").append("?p").append(i).append(" ").append("?o").append(i).append(".\n");
158                }
159                sb.append("}\n");
160                sb.append("WHERE {\n");
161                sb.append("<").append(resource).append("> ").append("?p0 ").append("?o0").append(".\n");
162                for(int i = 1; i < depth; i++){
163                        sb.append("OPTIONAL{\n");
164                        sb.append("?o").append(i-1).append(" ").append("?p").append(i).append(" ").append("?o").append(i).append(".\n");
165                }
166                for(int i = 1; i < depth; i++){
167                        sb.append("}");
168                }
169                sb.append("}\n");
170
171                return sb.toString();
172        }
173        
174        /**
175         * A SPARQL CONSTRUCT query is created, to get a RDF graph for the given resource and recursion depth.
176         * @param resource The resource for which a CONSTRUCT query is created.
177         * @return The CONSTRUCT query
178         */
179        private String makeConstructQueryObject(String resource, int depth){
180                StringBuilder sb = new StringBuilder();
181                sb.append("CONSTRUCT {\n");
182                sb.append("?s0 ").append("?p0 ").append("<").append(resource).append(">").append(".\n");
183                for(int i = 1; i < depth; i++){
184                        sb.append("?o").append(i).append(" ").append("?p").append(i).append(" ").append("?s").append(i-1).append(".\n");
185                }
186                sb.append("}\n");
187                sb.append("WHERE {\n");
188                sb.append("?s0 ").append("?p0 ").append("<").append(resource).append(">").append(".\n");
189                for(int i = 1; i < depth; i++){
190                        sb.append("OPTIONAL{\n");
191                        sb.append("?o").append(i).append(" ").append("?p").append(i).append(" ").append("?s").append(i-1).append(".\n");
192                }
193                for(int i = 1; i < depth; i++){
194                        sb.append("}");
195                }
196                sb.append("}\n");
197
198                return sb.toString();
199        }
200
201        /**
202         * A SPARQL CONSTRUCT query is created, to get a RDF graph for the given resource and recursion depth.
203         * @param resource The resource for which a CONSTRUCT query is created.
204         * @return The CONSTRUCT query
205         */
206        private String makeConstructQueryObject2(String resource, int depth){
207                StringBuilder sb = new StringBuilder();
208                sb.append("CONSTRUCT {\n");
209                sb.append("?s0 ").append("?p0 ").append("<").append(resource).append(">").append(".\n");
210                if(depth > 1) {
211                        sb.append("?s0 ").append("?p0_out ").append("?o0_out").append(".\n");
212                }
213                for(int i = 1; i < depth; i++){
214                        sb.append("?o").append(i).append(" ").append("?p").append(i).append(" ").append("?s").append(i-1).append(".\n");
215                }
216                sb.append("}\n");
217                sb.append("WHERE {\n");
218                sb.append("?s0 ").append("?p0 ").append("<").append(resource).append(">").append(".\n");
219                if(depth > 1) {
220                        sb.append("OPTIONAL{\n");
221                        sb.append("?s0 ").append("?p0_out ").append("?o0_out").append(".\n");
222                        sb.append("}\n");
223                }
224                for(int i = 1; i < depth; i++){
225                        sb.append("OPTIONAL{\n");
226                        sb.append("?o").append(i).append(" ").append("?p").append(i).append(" ").append("?s").append(i-1).append(".\n");
227
228                }
229                for(int i = 1; i < depth; i++){
230                        sb.append("}");
231                }
232                sb.append("}\n");
233
234                return sb.toString();
235        }
236
237        @Override
238        public void setIgnoredProperties(Set<String> properties) {
239        }
240
241        /* (non-Javadoc)
242         * @see org.dllearner.kb.sparql.ConciseBoundedDescriptionGenerator#setAllowedObjectNamespaces(java.util.Set)
243         */
244        @Override
245        public void setAllowedObjectNamespaces(Set<String> namespaces) {
246        }
247
248        public static void main(String[] args) throws Exception{
249                org.apache.log4j.Logger.getRootLogger().setLevel(Level.DEBUG);
250                SparqlEndpoint endpoint = SparqlEndpoint.create("http://sake.informatik.uni-leipzig.de:8890/sparql", "http://dbpedia.org");
251//              endpoint = SparqlEndpoint.getEndpointDBpedia();
252                ConciseBoundedDescriptionGenerator cbdGen = new SymmetricConciseBoundedDescriptionGeneratorImpl(endpoint);
253
254                Resource res = ResourceFactory.createResource("http://dbpedia.org/resource/Santa_Clara,_California");
255
256                Model cbd = cbdGen.getConciseBoundedDescription(res.getURI(), 2);
257                System.out.println("#triples =\t" + cbd.size());
258
259                System.out.println("#triples_out =\t" + cbd.listStatements(res, null, (RDFNode) null).toSet().size());
260                cbd.listStatements(res, null, (RDFNode) null).toList().forEach(System.out::println);
261
262                System.out.println("#triples_in =\t" + cbd.listStatements(null, null, res).toSet().size());
263                cbd.listStatements(null, null, res).toList().forEach(System.out::println);
264
265        }
266
267}