/*
Copyright (c) 2012 LinkedIn Corp.
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.
*/
package com.linkedin.data.schema.resolver;
import com.linkedin.data.Data;
import com.linkedin.data.DataMap;
import com.linkedin.data.TestUtil;
import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.DataSchemaLocation;
import com.linkedin.data.schema.DataSchemaResolver;
import com.linkedin.data.schema.NamedDataSchema;
import com.linkedin.data.schema.RecordDataSchema;
import com.linkedin.data.schema.SchemaParser;
import com.linkedin.data.schema.SchemaParserFactory;
import com.linkedin.data.schema.PegasusSchemaParser;
import com.linkedin.data.template.DataTemplateUtil;
import com.linkedin.data.template.RecordTemplate;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static com.linkedin.data.TestUtil.asMap;
import static com.linkedin.data.TestUtil.out;
import static com.linkedin.util.FileUtil.buildSystemIndependentPath;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertSame;
public class TestDataSchemaResolver
{
static final String ERROR = "error";
static final String FOUND = "found";
static final String NOT_FOUND = "not found";
@BeforeTest
public void setup()
{
}
public static class MapDataSchemaResolver extends AbstractDataSchemaResolver
{
public MapDataSchemaResolver(SchemaParserFactory parserFactory,
List<String> paths, String extension, Map<String, String> map)
{
super(parserFactory);
_paths = paths;
_extension = extension;
_map = map;
}
@Override
protected Iterator<DataSchemaLocation> possibleLocations(String name)
{
final String transformedName = name.replace('.', File.separatorChar) + _extension;
return new AbstractIterator(_paths)
{
@Override
protected DataSchemaLocation transform(String path)
{
return new MapResolverLocation(path + File.separator + transformedName);
}
};
}
@Override
protected InputStream locationToInputStream(DataSchemaLocation location,
StringBuilder errorMessageBuilder)
{
String input = _map.get(((MapResolverLocation) location).getMapKey());
return input == null ? null : new ByteArrayInputStream(input.getBytes(Data.UTF_8_CHARSET));
}
public static class MapResolverLocation implements DataSchemaLocation
{
private final String _mapKey;
public MapResolverLocation(String mapKey)
{
_mapKey = mapKey;
}
public String getMapKey()
{
return _mapKey;
}
@Override
public String toString()
{
return _mapKey;
}
@Override
public boolean equals(Object o)
{
return _mapKey.equals(((MapResolverLocation)o)._mapKey);
}
@Override
public int hashCode()
{
return _mapKey.hashCode();
}
@Override
public File getSourceFile()
{
return null;
}
}
private List<String> _paths;
private String _extension;
private Map<String,String> _map;
};
List<String> _testPaths = Arrays.asList
(
buildSystemIndependentPath("a1"),
buildSystemIndependentPath("a2", "b"),
buildSystemIndependentPath("a3", "b", "c")
);
Map<String,String> _testSchemas = asMap
(
buildSystemIndependentPath("a1", "foo.pdsc"), "{ \"name\" : \"foo\", \"type\" : \"fixed\", \"size\" : 4 }",
buildSystemIndependentPath("a1", "x", "y", "z.pdsc"), "{ \"name\" : \"x.y.z\", \"type\" : \"fixed\", \"size\" : 7 }",
buildSystemIndependentPath("a2", "b", "bar.pdsc"), "{ \"name\" : \"bar\", \"type\" : \"fixed\", \"size\" : 5 }",
buildSystemIndependentPath("a3", "b", "c", "baz.pdsc"), "{ \"name\" : \"baz\", \"type\" : \"fixed\", \"size\" : 6 }",
buildSystemIndependentPath("a3", "b", "c", "error.pdsc"), "{ \"name\" : \"error\", \"type\" : \"fixed\", \"size\" : -1 }",
buildSystemIndependentPath("a3", "b", "c", "referrer.pdsc"), "{ \"name\" : \"referrer\", \"type\" : \"record\", \"fields\" : [ { \"name\" : \"referree\", \"type\" : \"referree\" } ] }",
buildSystemIndependentPath("a3", "b", "c", "referree.pdsc"), "{ \"name\" : \"referree\", \"type\" : \"enum\", \"symbols\" : [ \"good\", \"bad\", \"ugly\" ] }",
buildSystemIndependentPath("a3", "b", "c", "circular1.pdsc"), "{ \"name\" : \"circular1\", \"type\" : \"record\", \"fields\" : [ { \"name\" : \"member\", \"type\" : \"circular2\" } ] }",
buildSystemIndependentPath("a3", "b", "c", "circular2.pdsc"), "{ \"name\" : \"circular2\", \"type\" : \"record\", \"fields\" : [ { \"name\" : \"member\", \"type\" : \"circular1\" } ] }",
buildSystemIndependentPath("a3", "b", "c", "redefine1.pdsc"), "{ \"name\" : \"redefine1\", \"type\" : \"record\", \"fields\" : [ { \"name\" : \"member\", \"type\" : \"redefine2\" } ] }",
buildSystemIndependentPath("a3", "b", "c", "redefine2.pdsc"), "{ \"name\" : \"redefine2\", \"type\" : \"record\", \"fields\" : [ { \"name\" : \"member\", \"type\" : { \"type\" : \"fixed\", \"name\" : \"redefine1\", \"size\" : 8 } } ] }"
);
String[][] _testLookupAndExpectedResults = {
{
"referrer",
FOUND,
"\"name\" : \"referrer\"",
buildSystemIndependentPath("referrer.pdsc").toString()
},
{
"x.y.z",
FOUND,
"\"size\" : 7",
buildSystemIndependentPath("x", "y", "z.pdsc").toString()
},
{
"foo",
FOUND,
"\"size\" : 4",
buildSystemIndependentPath("foo.pdsc").toString()
},
{
"bar",
FOUND,
"\"size\" : 5",
buildSystemIndependentPath("bar.pdsc").toString()
},
{
"baz",
FOUND,
"\"size\" : 6",
buildSystemIndependentPath("baz.pdsc").toString()
},
{
"circular1",
FOUND,
"\"name\" : \"circular1\"",
buildSystemIndependentPath("circular1.pdsc").toString()
},
{
"apple",
NOT_FOUND,
null
},
{
"error",
ERROR,
"is negative"
},
{
"redefine1",
ERROR,
"already defined as"
},
};
@Test
public void testMapDataSchemaResolver()
{
boolean debug = false;
DataSchemaResolver resolver = new MapDataSchemaResolver(SchemaParserFactory.instance(), _testPaths, ".pdsc", _testSchemas);
lookup(resolver, _testLookupAndExpectedResults, File.separatorChar, debug);
}
@Test
public void testFileDataSchemaResolver() throws IOException
{
boolean debug = false;
File testDir = TestUtil.testDir("testFileDataSchemaResolver", debug);
Map<File, Map.Entry<String,String>> files = TestUtil.createSchemaFiles(testDir, _testSchemas, debug);
List<String> testPaths = new ArrayList<String>();
for (String testPath : _testPaths)
{
String dirname = (testDir.getCanonicalPath() + "/" + testPath).replace('/', File.separatorChar);
testPaths.add((new File(dirname)).getCanonicalPath());
}
FileDataSchemaResolver resolver = new FileDataSchemaResolver(SchemaParserFactory.instance());
resolver.setPaths(testPaths);
resolver.setExtension(".pdsc");
lookup(resolver, _testLookupAndExpectedResults, File.separatorChar, debug);
// create jar files
testPaths.clear();
for (String testPath : _testPaths)
{
String jarFileName = (testDir.getCanonicalPath() + testPath + ".jar").replace('/', File.separatorChar);
Map<String,String> jarFileContents = new HashMap<String, String>();
for (Map.Entry<String,String> entry : _testSchemas.entrySet())
{
if (entry.getKey().startsWith(testPath))
{
String key = entry.getKey();
jarFileContents.put(key.substring(testPath.length()), entry.getValue());
}
}
TestUtil.createSchemaJar(jarFileName, jarFileContents, debug);
testPaths.add(jarFileName);
}
FileDataSchemaResolver resolver2 = new FileDataSchemaResolver(SchemaParserFactory.instance());
resolver2.setPaths(testPaths);
resolver2.setExtension(".pdsc");
lookup(resolver2, _testLookupAndExpectedResults, File.separatorChar, debug);
// cleanup
TestUtil.deleteRecursive(testDir, debug);
}
public static class ClassNameFooRecord extends RecordTemplate
{
public static final RecordDataSchema SCHEMA = (RecordDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"record\", \"name\" : \"ClassNameFooRecord\", \"namespace\" : \"com.linkedin.data.schema.resolver.TestDataSchemaResolver\", \"fields\" : [ { \"name\" : \"foo\", \"type\" : \"string\" } ] }");
public ClassNameFooRecord()
{
super(new DataMap(), SCHEMA);
}
}
@Test
public void testClassNameDataSchemaResolver()
{
@SuppressWarnings("deprecation")
final ClassNameDataSchemaResolver resolver = new ClassNameDataSchemaResolver();
final PegasusSchemaParser parser = new SchemaParser(resolver);
final Class<? extends RecordTemplate> testClass = ClassNameFooRecord.class;
final String nonExistSchemaName = "Non-Existing Schema";
final DataSchema existSchema = parser.lookupName(testClass.getName());
assertNotNull(existSchema);
assertTrue(existSchema instanceof RecordDataSchema);
assertEquals(((RecordDataSchema) existSchema).getFullName(), testClass.getCanonicalName());
assertFalse(resolver.isBadLocation(new ClassNameDataSchemaLocation(testClass.getName())));
final DataSchema nonExistSchema = parser.lookupName(nonExistSchemaName);
assertNull(nonExistSchema);
assertTrue(parser.errorMessage().contains(nonExistSchemaName));
assertTrue(resolver.isBadLocation(new ClassNameDataSchemaLocation(nonExistSchemaName)));
}
@Test
public void testClasspathResourceDataSchemaResolver()
{
final ClasspathResourceDataSchemaResolver resolver = new ClasspathResourceDataSchemaResolver(SchemaParserFactory.instance());
final PegasusSchemaParser parser = new SchemaParser(resolver);
final String existingSchemaName = "com.linkedin.data.schema.ValidationDemo";
final String nonExistSchemaName = "Non-Existing Schema";
final DataSchema existSchema = parser.lookupName(existingSchemaName);
assertNotNull(existSchema);
assertTrue(existSchema instanceof RecordDataSchema);
assertEquals(((RecordDataSchema) existSchema).getFullName(), existingSchemaName);
final DataSchema nonExistSchema = parser.lookupName(nonExistSchemaName);
assertNull(nonExistSchema);
assertTrue(parser.errorMessage().contains(nonExistSchemaName));
}
public void lookup(DataSchemaResolver resolver, String[][] lookups, char separator, boolean debug)
{
PegasusSchemaParser parser = new SchemaParser(resolver);
for (String[] entry : lookups)
{
String name = entry[0];
String expectFound = entry[1];
String expected = entry[2];
DataSchema schema = parser.lookupName(name);
if (debug) { out.println("----" + name + "-----"); }
String errorMessage = parser.errorMessage();
if (debug && errorMessage.isEmpty() == false) { out.println(errorMessage); }
if (expectFound == ERROR)
{
assertTrue(parser.hasError());
assertTrue(expected == null || errorMessage.contains(expected));
}
else if (expectFound == FOUND)
{
assertTrue(schema != null);
String schemaText = schema.toString();
if (debug) { out.println(schemaText); }
assertFalse(parser.hasError());
assertTrue(schema instanceof NamedDataSchema);
NamedDataSchema namedSchema = (NamedDataSchema) schema;
assertEquals(namedSchema.getFullName(), name);
assertTrue(schemaText.contains(expected));
assertTrue(resolver.bindings().containsKey(name));
assertSame(resolver.bindings().get(name), namedSchema);
String location = entry[3];
DataSchemaLocation namedSchemalocation = resolver.nameToDataSchemaLocations().get(name);
String locationNorm;
if (namedSchemalocation.toString().contains(".jar"))
{
locationNorm = location.replace(separator, '/');
}
else
{
locationNorm = location.replace('/', separator);
}
assertNotNull(namedSchemalocation);
assertEquals(namedSchemalocation.toString().indexOf(locationNorm),
namedSchemalocation.toString().length() - locationNorm.length());
assertTrue(resolver.locationResolved(namedSchemalocation));
}
else if (expectFound == NOT_FOUND)
{
assertTrue(schema == null);
assertFalse(parser.hasError());
assertTrue(expected == null || errorMessage.contains(expected));
assertFalse(resolver.bindings().containsKey(name));
}
else
{
assertTrue(false);
}
}
}
}