Skip to content

Commit 7a5ebd1

Browse files
author
James Leigh
authored
Merge pull request #845 from jamesrdf/issues/#712-validate-create
Fix #712: Add ValidatingValueFactory wrapper
2 parents 014eeee + 51e698c commit 7a5ebd1

5 files changed

Lines changed: 270 additions & 3 deletions

File tree

core/model/src/main/java/org/eclipse/rdf4j/model/datatypes/XMLDatatypeUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.rdf4j.common.net.ParsedIRI;
2323
import org.eclipse.rdf4j.common.text.ASCIIUtil;
2424
import org.eclipse.rdf4j.model.IRI;
25+
import org.eclipse.rdf4j.model.util.Literals;
2526
import org.eclipse.rdf4j.model.vocabulary.XMLSchema;
2627

2728
/**
@@ -274,6 +275,9 @@ else if (datatype.equals(XMLSchema.QNAME)) {
274275
else if (datatype.equals(XMLSchema.ANYURI)) {
275276
result = isValidAnyURI(value);
276277
}
278+
else if (datatype.equals(XMLSchema.LANGUAGE)) {
279+
result = Literals.isValidLanguageTag(value);
280+
}
277281

278282
return result;
279283
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Eclipse RDF4J contributors, Aduna, and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Distribution License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/org/documents/edl-v10.php.
7+
*******************************************************************************/
8+
package org.eclipse.rdf4j.model.impl;
9+
10+
import java.math.BigDecimal;
11+
import java.math.BigInteger;
12+
import java.net.URISyntaxException;
13+
import java.util.Date;
14+
15+
import javax.xml.datatype.XMLGregorianCalendar;
16+
17+
import org.eclipse.rdf4j.common.net.ParsedIRI;
18+
import org.eclipse.rdf4j.model.BNode;
19+
import org.eclipse.rdf4j.model.IRI;
20+
import org.eclipse.rdf4j.model.Literal;
21+
import org.eclipse.rdf4j.model.Resource;
22+
import org.eclipse.rdf4j.model.Statement;
23+
import org.eclipse.rdf4j.model.URI;
24+
import org.eclipse.rdf4j.model.Value;
25+
import org.eclipse.rdf4j.model.ValueFactory;
26+
import org.eclipse.rdf4j.model.datatypes.XMLDatatypeUtil;
27+
import org.eclipse.rdf4j.model.util.Literals;
28+
import org.eclipse.rdf4j.model.util.URIUtil;
29+
30+
/**
31+
* Validating wrapper to {@link ValueFactory}. Use this class in situations where a caller may be prone to
32+
* errors.
33+
*
34+
* @author James Leigh
35+
*/
36+
public class ValidatingValueFactory implements ValueFactory {
37+
38+
private static final int[][] PN_CHARS_U = {
39+
new int[] { '0', '9' },
40+
new int[] { '_', '_' },
41+
new int[] { 'A', 'Z' },
42+
new int[] { 'a', 'z' },
43+
new int[] { 0x00C0, 0x00D6 },
44+
new int[] { 0x00D8, 0x00F6 },
45+
new int[] { 0x00F8, 0x02FF },
46+
new int[] { 0x0370, 0x037D },
47+
new int[] { 0x037F, 0x1FFF },
48+
new int[] { 0x200C, 0x200D },
49+
new int[] { 0x2070, 0x218F },
50+
new int[] { 0x2C00, 0x2FEF },
51+
new int[] { 0x3001, 0xD7FF },
52+
new int[] { 0xF900, 0xFDCF },
53+
new int[] { 0xFDF0, 0xFFFD },
54+
new int[] { 0x10000, 0xEFFFF } };
55+
56+
private static final int[][] PN_CHARS = {
57+
new int[] { '-', '-' },
58+
new int[] { 0x00B7, 0x00B7 },
59+
new int[] { 0x0300, 0x036F },
60+
new int[] { 0x203F, 0x2040 },
61+
new int[] { '0', '9' },
62+
new int[] { '_', '_' },
63+
new int[] { 'A', 'Z' },
64+
new int[] { 'a', 'z' },
65+
new int[] { 0x00C0, 0x00D6 },
66+
new int[] { 0x00D8, 0x00F6 },
67+
new int[] { 0x00F8, 0x02FF },
68+
new int[] { 0x0370, 0x037D },
69+
new int[] { 0x037F, 0x1FFF },
70+
new int[] { 0x200C, 0x200D },
71+
new int[] { 0x2070, 0x218F },
72+
new int[] { 0x2C00, 0x2FEF },
73+
new int[] { 0x3001, 0xD7FF },
74+
new int[] { 0xF900, 0xFDCF },
75+
new int[] { 0xFDF0, 0xFFFD },
76+
new int[] { 0x10000, 0xEFFFF } };
77+
78+
private final ValueFactory delegate;
79+
80+
public ValidatingValueFactory() {
81+
this(SimpleValueFactory.getInstance());
82+
}
83+
84+
public ValidatingValueFactory(ValueFactory delegate) {
85+
this.delegate = delegate;
86+
}
87+
88+
@Override
89+
public IRI createIRI(String iri) {
90+
try {
91+
if (!new ParsedIRI(iri).isAbsolute()) {
92+
throw new IllegalArgumentException("IRI must be absolute");
93+
}
94+
return delegate.createIRI(iri);
95+
}
96+
catch (URISyntaxException e) {
97+
throw new IllegalArgumentException(e);
98+
}
99+
}
100+
101+
@Override
102+
public IRI createIRI(String namespace, String localName) {
103+
if (!URIUtil.isCorrectURISplit(namespace, localName)) {
104+
return createIRI(namespace + localName);
105+
}
106+
try {
107+
if (!new ParsedIRI(namespace + localName).isAbsolute()) {
108+
throw new IllegalArgumentException("Namespace must be absolute");
109+
}
110+
return delegate.createIRI(namespace, localName);
111+
}
112+
catch (URISyntaxException e) {
113+
throw new IllegalArgumentException(e);
114+
}
115+
}
116+
117+
@Override
118+
public BNode createBNode(String nodeID) {
119+
if (nodeID.length() < 1) {
120+
throw new IllegalArgumentException("Blank node ID cannot be empty");
121+
}
122+
if (!isMember(PN_CHARS_U, nodeID.codePointAt(0))) {
123+
throw new IllegalArgumentException("Blank node ID must start with alphanumber or underscore");
124+
}
125+
for (int i = 0, n = nodeID.codePointCount(0, nodeID.length()); i < n; i++) {
126+
if (!isMember(PN_CHARS, nodeID.codePointAt(nodeID.offsetByCodePoints(0, i)))) {
127+
throw new IllegalArgumentException("Illegal blank node ID character");
128+
}
129+
}
130+
return delegate.createBNode(nodeID);
131+
}
132+
133+
@Override
134+
public Literal createLiteral(String label, IRI datatype) {
135+
if (!XMLDatatypeUtil.isValidValue(label, datatype)) {
136+
throw new IllegalArgumentException("Not a valid literal value");
137+
}
138+
return delegate.createLiteral(label, datatype);
139+
}
140+
141+
public Literal createLiteral(String label, String language) {
142+
if (!Literals.isValidLanguageTag(language)) {
143+
throw new IllegalArgumentException("Not a valid language tag: " + language);
144+
}
145+
return delegate.createLiteral(label, language);
146+
}
147+
148+
@Override
149+
public URI createURI(String uri) {
150+
return createIRI(uri);
151+
}
152+
153+
@Override
154+
public URI createURI(String namespace, String localName) {
155+
return createIRI(namespace, localName);
156+
}
157+
158+
@Override
159+
public Literal createLiteral(String label, URI datatype) {
160+
if (datatype instanceof IRI) {
161+
return createLiteral(label, (IRI)datatype);
162+
}
163+
else {
164+
return createLiteral(label, createIRI(datatype.stringValue()));
165+
}
166+
}
167+
168+
public BNode createBNode() {
169+
return delegate.createBNode();
170+
}
171+
172+
public Literal createLiteral(String label) {
173+
return delegate.createLiteral(label);
174+
}
175+
176+
public Literal createLiteral(boolean value) {
177+
return delegate.createLiteral(value);
178+
}
179+
180+
public Literal createLiteral(byte value) {
181+
return delegate.createLiteral(value);
182+
}
183+
184+
public Literal createLiteral(short value) {
185+
return delegate.createLiteral(value);
186+
}
187+
188+
public Literal createLiteral(int value) {
189+
return delegate.createLiteral(value);
190+
}
191+
192+
public Literal createLiteral(long value) {
193+
return delegate.createLiteral(value);
194+
}
195+
196+
public Literal createLiteral(float value) {
197+
return delegate.createLiteral(value);
198+
}
199+
200+
public Literal createLiteral(double value) {
201+
return delegate.createLiteral(value);
202+
}
203+
204+
public Literal createLiteral(BigDecimal bigDecimal) {
205+
return delegate.createLiteral(bigDecimal);
206+
}
207+
208+
public Literal createLiteral(BigInteger bigInteger) {
209+
return delegate.createLiteral(bigInteger);
210+
}
211+
212+
public Literal createLiteral(XMLGregorianCalendar calendar) {
213+
return delegate.createLiteral(calendar);
214+
}
215+
216+
public Literal createLiteral(Date date) {
217+
return delegate.createLiteral(date);
218+
}
219+
220+
public Statement createStatement(Resource subject, IRI predicate, Value object) {
221+
return delegate.createStatement(subject, predicate, object);
222+
}
223+
224+
public Statement createStatement(Resource subject, IRI predicate, Value object, Resource context) {
225+
return delegate.createStatement(subject, predicate, object, context);
226+
}
227+
228+
public Statement createStatement(Resource subject, URI predicate, Value object) {
229+
return delegate.createStatement(subject, predicate, object);
230+
}
231+
232+
public Statement createStatement(Resource subject, URI predicate, Value object, Resource context) {
233+
return delegate.createStatement(subject, predicate, object, context);
234+
}
235+
236+
private boolean isMember(int[][] set, int cp) {
237+
for (int i = 0; i < set.length; i++) {
238+
if (set[i][0] <= cp && cp <= set[i][1]) {
239+
return true;
240+
}
241+
}
242+
return false;
243+
}
244+
}

core/model/src/main/java/org/eclipse/rdf4j/model/util/Literals.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import java.util.Objects;
1616
import java.util.Optional;
1717

18-
import javax.xml.datatype.XMLGregorianCalendar;
1918
import javax.xml.datatype.Duration;
19+
import javax.xml.datatype.XMLGregorianCalendar;
2020

2121
import org.eclipse.rdf4j.model.Literal;
2222
import org.eclipse.rdf4j.model.Value;
@@ -600,6 +600,25 @@ public static String normalizeLanguageTag(String languageTag)
600600
return new Locale.Builder().setLanguageTag(languageTag).build().toLanguageTag().intern();
601601
}
602602

603+
/**
604+
* Checks if the given string is a <a href="https://tools.ietf.org/html/bcp47">BCP47</a> language tag
605+
* according to the rules defined by the JDK in the {@link Locale} API.
606+
*
607+
* @param languageTag
608+
* A language tag
609+
* @return <code>true</code> if the given language tag is well-formed according to the rules specified in
610+
* BCP47.
611+
*/
612+
public static boolean isValidLanguageTag(String languageTag) {
613+
try {
614+
new Locale.Builder().setLanguageTag(languageTag);
615+
return true;
616+
}
617+
catch (IllformedLocaleException e) {
618+
return false;
619+
}
620+
}
621+
603622
protected Literals() {
604623
// Protected default constructor to prevent instantiation
605624
}

core/model/src/main/java/org/eclipse/rdf4j/model/util/URIUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static boolean isCorrectURISplit(String namespace, String localName) {
103103

104104
if (lastNsChar == '#') {
105105
// correct split if namespace has no other '#'
106-
return namespace.lastIndexOf('#', nsLength - 2) == -1;
106+
return namespace.lastIndexOf('#', nsLength - 2) == -1 && localName.indexOf('#') == -1;
107107
}
108108
else if (lastNsChar == '/') {
109109
// correct split if local name has no '/' and URI contains no '#'

core/model/src/test/java/org/eclipse/rdf4j/model/util/URIUtilTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void testIsCorrectURISplit()
2525
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page#", "1"));
2626
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page#", "1/2"));
2727
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page#", "1:2"));
28-
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page#", "1#2"));
28+
assertFalse(URIUtil.isCorrectURISplit("http://www.example.org/page#", "1#2"));
2929
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page/", ""));
3030
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page/", "1"));
3131
assertTrue(URIUtil.isCorrectURISplit("http://www.example.org/page/", "1:2"));

0 commit comments

Comments
 (0)