Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.

Commit 1b7fe15

Browse files
committed
Merge pull request #11 from dvdkruk/master
Added Support for Generating Protobuf Schema From POJO Definition
2 parents 862d86e + b4e1f8b commit 1b7fe15

21 files changed

+871
-199
lines changed

README.md

+72-4
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ Protobuf module requires Java 7, due to `protoparser` requiring Java 7.
2424

2525
With release 2.6.0 (Jul-2015), this module is considered stable, although number of production deployments is still limited.
2626

27-
## Functionality
27+
# Functionality
2828

29-
### Supported versions
29+
## Supported Versions
3030

3131
Version 2 of `protoc` supported. This is the official standard used in production as of Aug-2015.
3232
There is work underway by protobuf authors to specify a new version, called 'v3', but it has
@@ -41,11 +41,79 @@ When v3 specification is finalized we are likely to work on upgrading module to
4141
as option of this module, or via new v3-based module, depending on exact compatibility details
4242
between v2 and v3.
4343

44-
### Missing features
44+
# Usage
45+
46+
## Creating ObjectMapper
47+
Usage is as with basic ```JsonFactory```; most commonly you will just construct a standard ObjectMapper with ```com.fasterxml.jackson.dataformat.protobuf.ProtobufFactory```, like so:
48+
```java
49+
ObjectMapper mapper = new ObjectMapper(new ProtobufFactory());
50+
```
51+
Or the easy way, like:
52+
```java
53+
ObjectMapper mapper = new ProtobufMapper();
54+
```
55+
56+
## Reading Protobuf Data
57+
58+
Assuming you have the following protobuf definition:,
59+
```java
60+
String protobuf_str = "message Employee {\n"
61+
+" required string name = 1;\n"
62+
+" required int32 age = 2;\n"
63+
+" repeated string emails = 3;\n"
64+
+" optional Employee boss = 4;\n"
65+
+"}\n";
66+
67+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protobuf_str);
68+
```
69+
and a POJO definition like:
70+
```java
71+
public class Employee
72+
{
73+
public String name;
74+
public int age;
75+
public String[] emails;
76+
public Employee boss;
77+
}
78+
```
79+
you can actually use data-binding like so:
80+
```java
81+
byte[] protobufData = ... ; // or find an InputStream
82+
Employee empl = mapper.readerFor(Employee.class)
83+
.with(schema)
84+
.readValue(protobufData);
85+
```
86+
87+
## Writing Protobuf Data
88+
89+
Writing protobuf-encoded data follows similar pattern:
90+
```java
91+
byte[] protobufData = mapper.writer(schema)
92+
.writeValueAsBytes(empl);
93+
```
94+
and that's about it.
95+
96+
## Generating Protobuf Schema From POJO Definition
97+
You do not have to start with a protobuf Schema. This module can actually generate schemas for you, starting with POJO definitions! Here's how:
98+
```java
99+
public class POJO {
100+
// your typical, Jackson-compatible POJO (with or without annotations)
101+
}
102+
103+
ObjectMapper mapper = new ObjectMapper(new ProtobufFactory());
104+
ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator();
105+
mapper.acceptJsonFormatVisitor(POJO.class, gen);
106+
ProtobufSchema schemaWrapper = gen.getProtobufSchema();
107+
NativeProtobufSchema nativeProtobufSchema = schemaWrapper.getSource();
108+
109+
String asProtofile = nativeProtobufSchema.toString();
110+
```
111+
112+
# Missing features/Issues
45113

46114
Following features are not yet fully implemented as of version 2.6, but are planned to be evetually supported;
47115

48116
* Enforcing of mandatory values
49117
* Value defaulting
50-
* Construction of `protoc` schemas using alternatives to reading textual definition; for example, programmatic construction, or generation from Java classes.
118+
* Construction of `protoc` schemas using alternatives to reading textual definition; for example, programmatic construction.
51119

pom.xml

-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ abstractions.
6363
<groupId>com.fasterxml.jackson.core</groupId>
6464
<artifactId>jackson-annotations</artifactId>
6565
<version>${jackson.version.annotations}</version>
66-
<scope>test</scope>
6766
</dependency>
6867
</dependencies>
6968

src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/NativeProtobufSchema.java

+24-4
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,29 @@
1313
public class NativeProtobufSchema
1414
{
1515
protected final String _name;
16-
protected final List<TypeElement> _nativeTypes;
16+
protected final Collection<TypeElement> _nativeTypes;
1717

1818
protected volatile String[] _messageNames;
1919

2020
protected NativeProtobufSchema(ProtoFile input)
2121
{
22-
_name = input.filePath();
23-
_nativeTypes = input.typeElements();
22+
this(input.filePath(), input.typeElements());
2423
}
25-
24+
25+
protected NativeProtobufSchema(String name, Collection<TypeElement> types)
26+
{
27+
_name = name;
28+
_nativeTypes = types;
29+
}
30+
2631
public static NativeProtobufSchema construct(ProtoFile input) {
2732
return new NativeProtobufSchema(input);
2833
}
2934

35+
public static NativeProtobufSchema construct(String name, Collection<TypeElement> types) {
36+
return new NativeProtobufSchema(name, types);
37+
}
38+
3039
/**
3140
* Method for checking whether specified message type is defined by
3241
* the native schema
@@ -78,6 +87,17 @@ public List<String> getMessageNames() {
7887
}
7988
return Arrays.asList(_messageNames);
8089
}
90+
91+
@Override
92+
public String toString() {
93+
return toString(_name);
94+
}
95+
96+
public String toString(String name) {
97+
ProtoFile.Builder builder = ProtoFile.builder(name);
98+
builder.addTypes(_nativeTypes);
99+
return builder.build().toSchema();
100+
}
81101

82102
/*
83103
/**********************************************************

src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufMessage.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,4 @@ public String fieldsAsString() {
134134
public Iterable<ProtobufField> fields() {
135135
return Arrays.asList(_fields);
136136
}
137-
}
137+
}

src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufSchema.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public ProtobufMessage getRootType() {
8686
public List<String> getMessageTypes() {
8787
return _source.getMessageNames();
8888
}
89-
89+
9090
/**
9191
* Accessor to get type id for this {@link FormatSchema}, used by code Jackson
9292
* databinding functionality. Not usually needed by application developers.

src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufSchemaLoader.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public ProtobufSchemaLoader() { }
3838
/* Public API
3939
/**********************************************************
4040
*/
41-
41+
4242
public ProtobufSchema load(URL url) throws IOException {
4343
return loadNative(url).forFirstType();
4444
}

src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/TypeResolver.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ protected TypeResolver(TypeResolver p, Map<String,MessageElement> nativeMsgs,
3535
_resolvedMessageTypes = Collections.emptyMap();
3636
}
3737

38-
public static TypeResolver construct(List<TypeElement> nativeTypes) {
38+
public static TypeResolver construct(Collection<TypeElement> nativeTypes) {
3939
return construct(null, nativeTypes);
4040
}
4141

42-
protected static TypeResolver construct(TypeResolver parent, List<TypeElement> nativeTypes)
42+
protected static TypeResolver construct(TypeResolver parent, Collection<TypeElement> nativeTypes)
4343
{
4444
Map<String,MessageElement> nativeMessages = null;
4545
Map<String,ProtobufEnum> enumTypes = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.fasterxml.jackson.dataformat.protobuf.schemagen;
2+
3+
import com.fasterxml.jackson.databind.BeanProperty;
4+
5+
public class AnnotationBasedTagGenerator implements TagGenerator {
6+
7+
@Override
8+
public int nextTag(BeanProperty writer) {
9+
if (ProtobuffSchemaHelper.hasIndexAnnotation(writer)) {
10+
return ProtobuffSchemaHelper.getJsonProperty(writer).index();
11+
}
12+
throw new IllegalStateException("No 'JsonProperty.index' annotation found for " + writer.getFullName()
13+
+ ", either annotate all properties of type " + writer.getWrapperName().getSimpleName() + " with indexes or none at all");
14+
}
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.fasterxml.jackson.dataformat.protobuf.schemagen;
2+
3+
import com.fasterxml.jackson.databind.BeanProperty;
4+
5+
public class DefaultTagGenerator implements TagGenerator {
6+
7+
protected int _tagCounter;
8+
9+
public DefaultTagGenerator() {
10+
this(1);
11+
}
12+
13+
public DefaultTagGenerator(int startingTag) {
14+
_tagCounter = startingTag;
15+
}
16+
17+
@Override
18+
public int nextTag(BeanProperty writer) {
19+
if (ProtobuffSchemaHelper.hasIndexAnnotation(writer)) {
20+
throw new IllegalStateException(writer.getFullName()
21+
+ " is annotated with 'JsonProperty.index', however not all properties of type "
22+
+ writer.getWrapperName().getSimpleName()
23+
+ " are annotated. Either annotate all properties or none at all.");
24+
}
25+
26+
return nextTag();
27+
}
28+
29+
public int nextTag() {
30+
return _tagCounter++;
31+
}
32+
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.fasterxml.jackson.dataformat.protobuf.schemagen;
2+
3+
import java.util.Collection;
4+
import java.util.HashSet;
5+
import java.util.LinkedHashMap;
6+
import java.util.Map;
7+
import java.util.Set;
8+
9+
import com.fasterxml.jackson.databind.JavaType;
10+
11+
public class DefinedTypeElementBuilders {
12+
13+
protected Map<JavaType, TypeElementBuilder> _definedTypeElementBuilders = new LinkedHashMap<>();
14+
15+
protected Set<JavaType> _isNestedType = new HashSet<>();
16+
17+
public DefinedTypeElementBuilders() {
18+
}
19+
20+
public void AddTypeElement(JavaType type, TypeElementBuilder builder, boolean isNested) {
21+
if (_definedTypeElementBuilders.containsKey(type)) { //Type element builder already defined
22+
if (_definedTypeElementBuilders.get(type) != builder) { //Not expect this.
23+
throw new IllegalStateException("Trying to redefine TypeElementBuilder for type " + type);
24+
}
25+
} else { //new builder
26+
_definedTypeElementBuilders.put(type, builder);
27+
}
28+
29+
if(isNested) {
30+
_isNestedType.add(type);
31+
}
32+
}
33+
34+
public boolean containsBuilderFor(JavaType type) {
35+
return _definedTypeElementBuilders.containsKey(type);
36+
}
37+
38+
public TypeElementBuilder getBuilderFor(JavaType type) {
39+
return _definedTypeElementBuilders.get(type);
40+
}
41+
42+
public Set<TypeElementBuilder> getAllBuilders() {
43+
return new HashSet<TypeElementBuilder>(_definedTypeElementBuilders.values());
44+
}
45+
46+
public Set<TypeElementBuilder> getAllNestedBuilders() {
47+
return getAllBuildersFor(_isNestedType);
48+
}
49+
50+
public Set<TypeElementBuilder> getDependencyBuilders() {
51+
return getNonNestedBuilders(true);
52+
}
53+
54+
public Set<TypeElementBuilder> getNonNestedBuilders() {
55+
return getNonNestedBuilders(false);
56+
}
57+
58+
public Set<TypeElementBuilder> getNonNestedBuilders(boolean excludeRoot) {
59+
Set<JavaType> types = _definedTypeElementBuilders.keySet(); //all keys
60+
types.removeAll(_isNestedType); //exclude nested type
61+
62+
if(excludeRoot) { //exclude root
63+
if(_definedTypeElementBuilders.isEmpty()) {
64+
throw new IllegalStateException("DefinedTypeElementBuilders._definedTypeElementBuilders is empty");
65+
}
66+
types.remove(_definedTypeElementBuilders.keySet().iterator().next()); //expect the first element is root
67+
}
68+
69+
return getAllBuildersFor(types);
70+
}
71+
72+
protected HashSet<TypeElementBuilder> getAllBuildersFor(Collection<JavaType> types) {
73+
HashSet<TypeElementBuilder> nestedBuilder = new HashSet<>();
74+
for (JavaType type : types) {
75+
nestedBuilder.add(getBuilderFor(type));
76+
}
77+
return nestedBuilder;
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.fasterxml.jackson.dataformat.protobuf.schemagen;
2+
3+
import java.util.Set;
4+
5+
import com.fasterxml.jackson.databind.JavaType;
6+
import com.fasterxml.jackson.databind.SerializerProvider;
7+
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor.Base;
8+
import com.squareup.protoparser.EnumConstantElement;
9+
import com.squareup.protoparser.EnumElement;
10+
import com.squareup.protoparser.TypeElement;
11+
12+
public class EnumElementVisitor extends Base implements TypeElementBuilder {
13+
14+
EnumElement.Builder _builder;
15+
16+
DefaultTagGenerator _tagGenerator = new DefaultTagGenerator(0);
17+
18+
public EnumElementVisitor(SerializerProvider provider, JavaType type,
19+
DefinedTypeElementBuilders definedTypeElementBuilders, boolean isNested) {
20+
21+
if (!type.isEnumType()) {
22+
throw new IllegalArgumentException("Expected an enum, however given type is " + type);
23+
}
24+
25+
_builder = EnumElement.builder();
26+
_builder.name(type.getRawClass().getSimpleName());
27+
_builder.documentation("Enum for " + type.toCanonical());
28+
29+
definedTypeElementBuilders.AddTypeElement(type, this, isNested);
30+
}
31+
32+
@Override
33+
public TypeElement build() {
34+
return _builder.build();
35+
}
36+
37+
@Override
38+
public void enumTypes(Set<String> enums) {
39+
for (String eName : enums) {
40+
_builder.addConstant(buildEnumConstant(eName));
41+
}
42+
}
43+
44+
protected EnumConstantElement buildEnumConstant(String name) {
45+
EnumConstantElement.Builder builder = EnumConstantElement.builder();
46+
builder.name(name);
47+
builder.tag(_tagGenerator.nextTag());
48+
return builder.build();
49+
}
50+
}

0 commit comments

Comments
 (0)