/* * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.platform.relations; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.IterableQueryResult; import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; import org.nuxeo.ecm.core.api.repository.RepositoryManager; import org.nuxeo.ecm.core.query.sql.NXQL; import org.nuxeo.ecm.core.schema.DocumentType; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder; import org.nuxeo.ecm.platform.relations.api.Blank; import org.nuxeo.ecm.platform.relations.api.Graph; import org.nuxeo.ecm.platform.relations.api.GraphDescription; import org.nuxeo.ecm.platform.relations.api.Literal; import org.nuxeo.ecm.platform.relations.api.Node; import org.nuxeo.ecm.platform.relations.api.NodeType; import org.nuxeo.ecm.platform.relations.api.QNameResource; import org.nuxeo.ecm.platform.relations.api.QueryResult; import org.nuxeo.ecm.platform.relations.api.Resource; import org.nuxeo.ecm.platform.relations.api.Statement; import org.nuxeo.ecm.platform.relations.api.Subject; import org.nuxeo.ecm.platform.relations.api.impl.AbstractNode; import org.nuxeo.ecm.platform.relations.api.impl.NodeFactory; import org.nuxeo.ecm.platform.relations.api.impl.QueryResultImpl; import org.nuxeo.ecm.platform.relations.api.impl.RelationDate; import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl; import org.nuxeo.ecm.platform.relations.api.util.RelationConstants; import org.nuxeo.runtime.api.Framework; /** * Relation graph implementation delegating to the core. */ public class CoreGraph implements Graph { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(CoreGraph.class); public static final String OPTION_DOCTYPE = "doctype"; public static final String REL_TYPE = "Relation"; public static final String REL_PREDICATE = "relation:predicate"; public static final String REL_SOURCE_ID = "relation:source"; public static final String REL_SOURCE_URI = "relation:sourceUri"; public static final String REL_TARGET_ID = "relation:target"; public static final String REL_TARGET_URI = "relation:targetUri"; public static final String REL_TARGET_STRING = "relation:targetString"; public static final String DC_CREATED = "dc:created"; public static final String DC_CREATOR = "dc:creator"; public static final String DC_MODIFIED = "dc:modified"; public static final String DC_TITLE = "dc:title"; public static final String DC_DESCRIPTION = "dc:description"; // avoid confusion with any legal uri public static String BLANK_NS = "-:"; public static String DOCUMENT_NAMESPACE = RelationConstants.DOCUMENT_NAMESPACE; /** Without final slash (compat). */ public static String DOCUMENT_NAMESPACE2 = DOCUMENT_NAMESPACE.substring(0, DOCUMENT_NAMESPACE.length() - 1); /** Has no final slash (compat). */ public static final String COMMENT_NAMESPACE = "http://www.nuxeo.org/comments/uid"; public static final String[] DOC_NAMESPACES = { DOCUMENT_NAMESPACE, DOCUMENT_NAMESPACE2, COMMENT_NAMESPACE }; protected static final List<Statement> EMPTY_STATEMENTS = Collections.emptyList(); protected static final Statement ALL = new StatementImpl(null, null, null); protected CoreSession session; protected String name; protected String docType = REL_TYPE; public Map<String, String> namespaces; public List<String> namespaceList = Collections.emptyList(); /** Only one of those is filled. */ protected static class NodeAsString { public String id; public String uri; public String string; } /** * A graph with this base session. An unrestricted session will be opened based on it. */ public CoreGraph(CoreSession session) { this.session = session; } @Override public void setDescription(GraphDescription graphDescription) { name = graphDescription.getName(); setOptions(graphDescription.getOptions()); namespaces = graphDescription.getNamespaces(); namespaceList = namespaces == null ? Collections.<String> emptyList() : new ArrayList<String>( new LinkedHashSet<String>(namespaces.values())); } protected void setOptions(Map<String, String> options) { for (Entry<String, String> option : options.entrySet()) { String key = option.getKey(); String type = option.getValue(); if (key.equals(OPTION_DOCTYPE)) { SchemaManager sm = Framework.getLocalService(SchemaManager.class); DocumentType documentType = sm.getDocumentType(type); if (documentType == null) { throw new IllegalArgumentException("Unknown type: " + type + " for graph: " + name); } Type[] th = documentType.getTypeHierarchy(); String baseType = th.length == 0 ? type : th[th.length - 1].getName(); if (!REL_TYPE.equals(baseType)) { throw new IllegalArgumentException("Not a Relation type: " + type + " for graph: " + name); } docType = type; } } } @Override public Map<String, String> getNamespaces() { return namespaces; } @Override public Long size() { SizeFinder sizeFinder = session == null ? new SizeFinder() : new SizeFinder(session); sizeFinder.runUnrestricted(); return Long.valueOf(sizeFinder.size); } protected class SizeFinder extends UnrestrictedSessionRunner { protected long size; protected SizeFinder() { super(getDefaultRepositoryName()); } protected SizeFinder(CoreSession session) { super(session); } @Override public void run() { // TODO could use a COUNT(*) query IterableQueryResult it = session.queryAndFetch("SELECT " + NXQL.ECM_UUID + " FROM " + docType, NXQL.NXQL); try { size = it.size(); } finally { it.close(); } } } @Override public void clear() { remove(Collections.singletonList(ALL)); } @Override public void add(Statement statement) { add(Collections.singletonList(statement)); } @Override public void add(List<Statement> statements) { StatementAdder statementAdder = session == null ? new StatementAdder(statements) : new StatementAdder( statements, session); statementAdder.runUnrestricted(); } protected class StatementAdder extends UnrestrictedSessionRunner { protected List<Statement> statements; protected Date now; protected StatementAdder(List<Statement> statements) { super(getDefaultRepositoryName(), "system"); this.statements = statements; } protected StatementAdder(List<Statement> statements, CoreSession session) { super(session); this.statements = statements; } @Override public void run() { now = new Date(); for (Statement statement : statements) { add(statement); } session.save(); } protected void add(Statement statement) { DocumentModel rel = session.createDocumentModel(null, "relation", docType); rel = setRelationProperties(rel, statement); session.createDocument(rel); } protected DocumentModel setRelationProperties(DocumentModel rel, Statement statement) { Resource pred = statement.getPredicate(); String predicateUri = pred.getUri(); if (predicateUri == null) { throw new IllegalArgumentException("Invalid predicate in statement: " + statement); } Subject subject = statement.getSubject(); if (subject.isLiteral()) { throw new IllegalArgumentException("Invalid literal subject in statement: " + statement); } NodeAsString source = getNodeAsString(subject); Node object = statement.getObject(); NodeAsString target = getNodeAsString(object); String author = getAuthor(statement); if (author == null) { author = getOriginatingUsername(); } Date created = getCreationDate(statement); if (created == null) { created = now; } Date modified = getModificationDate(statement); if (modified == null) { modified = now; } String comment = getComment(statement); String title = (source.id != null ? source.id : source.uri) + " " + predicateUri.substring(predicateUri.lastIndexOf('/') + 1) + " " + (target.id != null ? target.id : target.uri != null ? target.uri : target.string); int MAX_TITLE = 200; if (title.length() > MAX_TITLE) { title = title.substring(0, MAX_TITLE); } rel.setPropertyValue(REL_PREDICATE, predicateUri); if (source.id != null) { rel.setPropertyValue(REL_SOURCE_ID, source.id); } else { rel.setPropertyValue(REL_SOURCE_URI, source.uri); } if (target.id != null) { rel.setPropertyValue(REL_TARGET_ID, target.id); } else if (target.uri != null) { rel.setPropertyValue(REL_TARGET_URI, target.uri); } else { rel.setPropertyValue(REL_TARGET_STRING, target.string); } if (author != null) { // will usually get overwritten by DublinCoreListener // but not in tests rel.setPropertyValue(DC_CREATOR, author); } if (created != null) { // will usually get overwritten by DublinCoreListener // but not in tests rel.setPropertyValue(DC_CREATED, created); } if (modified != null) { // will usually get overwritten by DublinCoreListener // but not in tests rel.setPropertyValue(DC_MODIFIED, modified); } rel.setPropertyValue(DC_TITLE, title); // for debug if (comment != null) { rel.setPropertyValue(DC_DESCRIPTION, comment); } return rel; } } @Override public void remove(Statement statement) { remove(Collections.singletonList(statement)); } @Override public void remove(List<Statement> statements) { StatementRemover statementRemover = session == null ? new StatementRemover(statements) : new StatementRemover( statements, session); statementRemover.runUnrestricted(); } protected class StatementRemover extends UnrestrictedSessionRunner { protected List<Statement> statements; protected Date now; protected StatementRemover(List<Statement> statements) { super(getDefaultRepositoryName()); this.statements = statements; } protected StatementRemover(List<Statement> statements, CoreSession session) { super(session); this.statements = statements; } @Override public void run() { now = new Date(); for (Statement statement : statements) { remove(statement); } } protected void remove(Statement statement) { String query = "SELECT " + NXQL.ECM_UUID + " FROM " + docType; query = whereBuilder(query, statement); if (query == null) { return; } IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); try { for (Map<String, Serializable> map : it) { String id = (String) map.get(NXQL.ECM_UUID); session.removeDocument(new IdRef(id)); } } finally { it.close(); } } } protected class StatementFinder extends UnrestrictedSessionRunner { protected List<Statement> statements; protected Statement statement; protected StatementFinder(Statement statement) { super(getDefaultRepositoryName()); this.statement = statement; } protected StatementFinder(Statement statement, CoreSession session) { super(session); this.statement = statement; } @Override public void run() { String query = "SELECT " + REL_PREDICATE + ", " + REL_SOURCE_ID + ", " + REL_SOURCE_URI + ", " + REL_TARGET_ID + ", " + REL_TARGET_URI + ", " + REL_TARGET_STRING + ", " + DC_CREATED + ", " + DC_CREATOR + ", " + DC_MODIFIED + ", " + DC_DESCRIPTION + " FROM " + docType; query = whereBuilder(query, statement); if (query == null) { statements = EMPTY_STATEMENTS; return; } statements = new ArrayList<Statement>(); IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); try { for (Map<String, Serializable> map : it) { String pred = (String) map.get(REL_PREDICATE); String source = (String) map.get(REL_SOURCE_ID); String sourceUri = (String) map.get(REL_SOURCE_URI); String target = (String) map.get(REL_TARGET_ID); String targetUri = (String) map.get(REL_TARGET_URI); String targetString = (String) map.get(REL_TARGET_STRING); Calendar created = (Calendar) map.get(DC_CREATED); String creator = (String) map.get(DC_CREATOR); Calendar modified = (Calendar) map.get(DC_MODIFIED); String comment = (String) map.get(DC_DESCRIPTION); Resource predicate = NodeFactory.createResource(pred); Node subject; if (source != null) { subject = createId(source); } else { subject = createUri(sourceUri); } Node object; if (target != null) { object = createId(target); } else if (targetUri != null) { object = createUri(targetUri); } else { object = NodeFactory.createLiteral(targetString); } Statement statement = new StatementImpl(subject, predicate, object); setCreationDate(statement, created); setAuthor(statement, creator); setModificationDate(statement, modified); setComment(statement, comment); statements.add(statement); } } finally { it.close(); } } protected QNameResource createId(String id) { return NodeFactory.createQNameResource(DOCUMENT_NAMESPACE, session.getRepositoryName() + '/' + id); } protected Node createUri(String uri) { if (uri.startsWith(BLANK_NS)) { // skolemization String id = uri.substring(BLANK_NS.length()); return NodeFactory.createBlank(id.isEmpty() ? null : id); } else { for (String ns : namespaceList) { if (uri.startsWith(ns)) { String localName = uri.substring(ns.length()); return NodeFactory.createQNameResource(ns, localName); } } return NodeFactory.createResource(uri); } } } @Override public List<Statement> getStatements() { return getStatements(ALL); } @Override public List<Statement> getStatements(Node subject, Node predicate, Node object) { return getStatements(new StatementImpl(subject, predicate, object)); } @Override public List<Statement> getStatements(Statement statement) { StatementFinder statementFinder = session == null ? new StatementFinder(statement) : new StatementFinder( statement, session); statementFinder.runUnrestricted(); return statementFinder.statements; } @Override public List<Node> getSubjects(Node predicate, Node object) { List<Statement> statements = getStatements(new StatementImpl(null, predicate, object)); List<Node> nodes = new ArrayList<Node>(statements.size()); for (Statement statement : statements) { nodes.add(statement.getSubject()); } return nodes; } @Override public List<Node> getPredicates(Node subject, Node object) { List<Statement> statements = getStatements(new StatementImpl(subject, null, object)); List<Node> nodes = new ArrayList<Node>(statements.size()); for (Statement statement : statements) { nodes.add(statement.getPredicate()); } return nodes; } @Override public List<Node> getObjects(Node subject, Node predicate) { List<Statement> statements = getStatements(new StatementImpl(subject, predicate, null)); List<Node> nodes = new ArrayList<Node>(statements.size()); for (Statement statement : statements) { nodes.add(statement.getObject()); } return nodes; } @Override public boolean hasStatement(Statement statement) { if (statement == null) { return false; } // could be optimized in the null/blank case, but this method seems // unused return !getStatements(statement).isEmpty(); } @Override public boolean hasResource(Resource resource) { if (resource == null) { return false; } ResourceFinder resourceFinder = session == null ? new ResourceFinder(resource) : new ResourceFinder(resource, session); resourceFinder.runUnrestricted(); return resourceFinder.found; } protected class ResourceFinder extends UnrestrictedSessionRunner { protected boolean found; protected Resource resource; protected ResourceFinder(Resource resource) { super(getDefaultRepositoryName()); this.resource = resource; } protected ResourceFinder(Resource resource, CoreSession session) { super(session); this.resource = resource; } @Override public void run() { String query = "SELECT " + NXQL.ECM_UUID + " FROM " + docType; query = whereAnyBuilder(query, resource); IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); try { found = it.iterator().hasNext(); } finally { it.close(); } } protected String whereAnyBuilder(String query, Resource resource) { List<Object> params = new ArrayList<Object>(3); List<String> clauses = new ArrayList<String>(3); NodeAsString nas = getNodeAsString(resource); if (nas.id != null) { // don't allow predicates that are actually doc ids clauses.add(REL_SOURCE_ID + " = ?"); params.add(nas.id); clauses.add(REL_TARGET_URI + " = ?"); params.add(DOCUMENT_NAMESPACE + session.getRepositoryName() + '/' + nas.id); } else if (nas.uri != null) { for (String ns : DOC_NAMESPACES) { if (nas.uri.startsWith(ns)) { String id = localNameToId(nas.uri.substring(ns.length())); clauses.add(REL_SOURCE_ID + " = ?"); params.add(id); break; } } clauses.add(REL_SOURCE_URI + " = ?"); params.add(nas.uri); clauses.add(REL_TARGET_URI + " = ?"); params.add(nas.uri); clauses.add(REL_PREDICATE + " = ?"); params.add(nas.uri); } query += " WHERE " + StringUtils.join(clauses, " OR "); query = NXQLQueryBuilder.getQuery(query, params.toArray(), true, true, null); return query; } } public static final Pattern SPARQL_SPO_PO = Pattern.compile("SELECT \\?s \\?p \\?o WHERE \\{ \\?s \\?p \\?o . \\?s <(.*)> <(.*)> . \\}"); public static final Pattern SPARQL_PO_S = Pattern.compile("SELECT \\?p \\?o WHERE \\{ <(.*)> \\?p \\?o \\}"); @Override public QueryResult query(String queryString, String language, String baseURI) { // language is ignored, assume SPARQL Matcher matcher = SPARQL_SPO_PO.matcher(queryString); if (matcher.matches()) { Node predicate = NodeFactory.createResource(matcher.group(1)); Node object = NodeFactory.createResource(matcher.group(2)); // find subjects with this predicate and object List<Node> subjects = getSubjects(predicate, object); List<Map<String, Node>> results = new ArrayList<>(); if (!subjects.isEmpty()) { // find all statements with these subjects List<Statement> statements = getStatements(new Subjects(subjects), null, null); for (Statement st : statements) { Map<String, Node> map = new HashMap<>(); map.put("s", st.getSubject()); map.put("p", st.getPredicate()); map.put("o", st.getObject()); results.add(map); } } return new QueryResultImpl(Integer.valueOf(results.size()), Arrays.asList("s", "p", "o"), results); } matcher = SPARQL_PO_S.matcher(queryString); if (matcher.matches()) { Node subject = NodeFactory.createResource(matcher.group(1)); // find predicates and objects with this subject List<Statement> statements = getStatements(new StatementImpl(subject, null, null)); List<Map<String, Node>> results = new ArrayList<>(); for (Statement st : statements) { Map<String, Node> map = new HashMap<>(); map.put("p", st.getPredicate()); map.put("o", st.getObject()); results.add(map); } return new QueryResultImpl(Integer.valueOf(results.size()), Arrays.asList("p", "o"), results); } throw new UnsupportedOperationException("Cannot parse query: " + queryString); } public static final Pattern SPARQL_S_PO = Pattern.compile("SELECT \\?s WHERE \\{ \\?s <(.*)> <(.*)> \\}"); @Override public int queryCount(String queryString, String language, String baseURI) { // language is ignored, assume SPARQL Matcher matcher = SPARQL_S_PO.matcher(queryString); if (matcher.matches()) { Node predicate = NodeFactory.createResource(matcher.group(1)); Node object = NodeFactory.createResource(matcher.group(2)); List<Node> subjects = getSubjects(predicate, object); return subjects.size(); } throw new UnsupportedOperationException("Cannot parse query: " + queryString); } @Override public boolean read(String path, String lang, String base) { InputStream in = null; try { in = new FileInputStream(path); return read(in, lang, base); } catch (FileNotFoundException e) { throw new RuntimeException(e); } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.error(e); } } } } @Override public boolean write(String path, String lang, String base) { OutputStream out = null; try { out = new FileOutputStream(new File(path)); return write(out, lang, base); } catch (FileNotFoundException e) { throw new RuntimeException(e); } finally { if (out != null) { try { out.close(); } catch (IOException e) { log.error(e); } } } } @Override public boolean read(InputStream in, String lang, String base) { throw new UnsupportedOperationException(); } @Override public boolean write(OutputStream out, String lang, String base) { throw new UnsupportedOperationException(); } protected static String getDefaultRepositoryName() { return Framework.getService(RepositoryManager.class).getDefaultRepositoryName(); } /** Fake Node type used to pass down multiple nodes into whereBuilder. */ public static class Subjects extends AbstractNode implements Subject { private static final long serialVersionUID = 1L; protected List<Node> nodes; public Subjects(List<Node> nodes) { this.nodes = nodes; } public List<Node> getNodes() { return nodes; } @Override public NodeType getNodeType() { return null; } } protected String whereBuilder(String query, Statement statement) { List<Object> params = new ArrayList<Object>(3); List<String> clauses = new ArrayList<String>(3); Resource p = statement.getPredicate(); if (p != null) { NodeAsString pn = getNodeAsString(p); if (pn.uri == null) { return null; } clauses.add(REL_PREDICATE + " = ?"); params.add(pn.uri); } Node s = statement.getSubject(); if (s != null) { if (s instanceof Subjects) { List<Node> subjects = ((Subjects) s).getNodes(); if (subjects.isEmpty()) { throw new UnsupportedOperationException("empty subjects"); } StringBuilder buf = new StringBuilder(REL_SOURCE_URI); buf.append(" IN ("); for (Node sub : subjects) { NodeAsString sn = getNodeAsString(sub); if (sn.id != null) { throw new UnsupportedOperationException("subjects ListNode with id instead of uri" + subjects); } buf.append("?, "); params.add(sn.uri); } buf.setLength(buf.length() - 2); // remove last comma/space buf.append(")"); clauses.add(buf.toString()); } else { NodeAsString sn = getNodeAsString(s); if (sn.id != null) { clauses.add(REL_SOURCE_ID + " = ?"); params.add(sn.id); } else { clauses.add(REL_SOURCE_URI + " = ?"); params.add(sn.uri); } } } Node o = statement.getObject(); if (o != null) { NodeAsString on = getNodeAsString(o); if (on.id != null) { clauses.add(REL_TARGET_ID + " = ?"); params.add(on.id); } else if (on.uri != null) { clauses.add(REL_TARGET_URI + " = ?"); params.add(on.uri); } else { clauses.add(REL_TARGET_STRING + " = ?"); params.add(on.string); } } if (!clauses.isEmpty()) { query += " WHERE " + StringUtils.join(clauses, " AND "); query = NXQLQueryBuilder.getQuery(query, params.toArray(), true, true, null); } return query; } protected static NodeAsString getNodeAsString(Node node) { NodeAsString nas = new NodeAsString(); if (node.isBlank()) { // skolemization String id = ((Blank) node).getId(); nas.uri = BLANK_NS + (id == null ? "" : id); } else if (node.isLiteral()) { nas.string = ((Literal) node).getValue(); } else if (node.isQNameResource()) { String ns = ((QNameResource) node).getNamespace(); if (DOCUMENT_NAMESPACE.equals(ns) || DOCUMENT_NAMESPACE2.equals(ns) || COMMENT_NAMESPACE.equals(ns)) { nas.id = localNameToId(((QNameResource) node).getLocalName()); } else { nas.uri = ((Resource) node).getUri(); } } else { // node.isResource() String uri = ((Resource) node).getUri(); for (String ns : DOC_NAMESPACES) { if (uri.startsWith(ns)) { nas.id = localNameToId(uri.substring(ns.length())); break; } } if (nas.id == null) { nas.uri = uri; } } return nas; } protected static String localNameToId(String localName) { String[] split = localName.split("/"); if (split.length == 1) { return localName; // compat, no repository name } else { return split[1]; } } protected static String getAuthor(Statement statement) { return getStringProperty(statement, RelationConstants.AUTHOR); } protected static void setAuthor(Statement statement, String author) { setStringProperty(statement, RelationConstants.AUTHOR, author); } protected static Date getCreationDate(Statement statement) { return getDateProperty(statement, RelationConstants.CREATION_DATE); } protected static void setCreationDate(Statement statement, Calendar created) { setDateProperty(statement, RelationConstants.CREATION_DATE, created); } protected static Date getModificationDate(Statement statement) { return getDateProperty(statement, RelationConstants.MODIFICATION_DATE); } protected static void setModificationDate(Statement statement, Calendar modified) { setDateProperty(statement, RelationConstants.MODIFICATION_DATE, modified); } protected static String getComment(Statement statement) { return getStringProperty(statement, RelationConstants.COMMENT); } protected static void setComment(Statement statement, String comment) { setStringProperty(statement, RelationConstants.COMMENT, comment); } protected static String getStringProperty(Statement statement, Resource prop) { Node node = statement.getProperty(prop); if (node == null || !node.isLiteral()) { return null; } return ((Literal) node).getValue(); } protected static void setStringProperty(Statement statement, Resource prop, String string) { if (string == null) { return; } statement.setProperty(prop, NodeFactory.createLiteral(string)); } protected static Date getDateProperty(Statement statement, Resource prop) { Node node = statement.getProperty(prop); if (node == null || !node.isLiteral()) { return null; } return RelationDate.getDate((Literal) node); } protected static void setDateProperty(Statement statement, Resource prop, Calendar date) { if (date == null) { return; } statement.setProperty(prop, RelationDate.getLiteralDate(date)); } }