Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 72 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ Protobuf module requires Java 7, due to `protoparser` requiring Java 7.

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

## Functionality
# Functionality

### Supported versions
## Supported Versions

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

### Missing features
# Usage

## Creating ObjectMapper
Usage is as with basic ```JsonFactory```; most commonly you will just construct a standard ObjectMapper with ```com.fasterxml.jackson.dataformat.protobuf.ProtobufFactory```, like so:
```java
ObjectMapper mapper = new ObjectMapper(new ProtobufFactory());
```
Or the easy way, like:
```java
ObjectMapper mapper = new ProtobufMapper();
```

## Reading Protobuf Data

Assuming you have the following protobuf definition:,
```java
String protobuf_str = "message Employee {\n"
+" required string name = 1;\n"
+" required int32 age = 2;\n"
+" repeated string emails = 3;\n"
+" optional Employee boss = 4;\n"
+"}\n";

ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protobuf_str);
```
and a POJO definition like:
```java
public class Employee
{
public String name;
public int age;
public String[] emails;
public Employee boss;
}
```
you can actually use data-binding like so:
```java
byte[] protobufData = ... ; // or find an InputStream
Employee empl = mapper.readerFor(Employee.class)
.with(schema)
.readValue(protobufData);
```

## Writing Protobuf Data

Writing protobuf-encoded data follows similar pattern:
```java
byte[] protobufData = mapper.writer(schema)
.writeValueAsBytes(empl);
```
and that's about it.

## Generating Protobuf Schema From POJO Definition
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:
```java
public class POJO {
// your typical, Jackson-compatible POJO (with or without annotations)
}

ObjectMapper mapper = new ObjectMapper(new ProtobufFactory());
ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator();
mapper.acceptJsonFormatVisitor(POJO.class, gen);
ProtobufSchema schemaWrapper = gen.getProtobufSchema();
NativeProtobufSchema nativeProtobufSchema = schemaWrapper.getSource();

String asProtofile = nativeProtobufSchema.toString();
```

# Missing features/Issues

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

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

1 change: 0 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ abstractions.
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version.annotations}</version>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,29 @@
public class NativeProtobufSchema
{
protected final String _name;
protected final List<TypeElement> _nativeTypes;
protected final Collection<TypeElement> _nativeTypes;

protected volatile String[] _messageNames;

protected NativeProtobufSchema(ProtoFile input)
{
_name = input.filePath();
_nativeTypes = input.typeElements();
this(input.filePath(), input.typeElements());
}


protected NativeProtobufSchema(String name, Collection<TypeElement> types)
{
_name = name;
_nativeTypes = types;
}

public static NativeProtobufSchema construct(ProtoFile input) {
return new NativeProtobufSchema(input);
}

public static NativeProtobufSchema construct(String name, Collection<TypeElement> types) {
return new NativeProtobufSchema(name, types);
}

/**
* Method for checking whether specified message type is defined by
* the native schema
Expand Down Expand Up @@ -78,6 +87,17 @@ public List<String> getMessageNames() {
}
return Arrays.asList(_messageNames);
}

@Override
public String toString() {
return toString(_name);
}

public String toString(String name) {
ProtoFile.Builder builder = ProtoFile.builder(name);
builder.addTypes(_nativeTypes);
return builder.build().toSchema();
}

/*
/**********************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,4 @@ public String fieldsAsString() {
public Iterable<ProtobufField> fields() {
return Arrays.asList(_fields);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public ProtobufMessage getRootType() {
public List<String> getMessageTypes() {
return _source.getMessageNames();
}

/**
* Accessor to get type id for this {@link FormatSchema}, used by code Jackson
* databinding functionality. Not usually needed by application developers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public ProtobufSchemaLoader() { }
/* Public API
/**********************************************************
*/

public ProtobufSchema load(URL url) throws IOException {
return loadNative(url).forFirstType();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ protected TypeResolver(TypeResolver p, Map<String,MessageElement> nativeMsgs,
_resolvedMessageTypes = Collections.emptyMap();
}

public static TypeResolver construct(List<TypeElement> nativeTypes) {
public static TypeResolver construct(Collection<TypeElement> nativeTypes) {
return construct(null, nativeTypes);
}

protected static TypeResolver construct(TypeResolver parent, List<TypeElement> nativeTypes)
protected static TypeResolver construct(TypeResolver parent, Collection<TypeElement> nativeTypes)
{
Map<String,MessageElement> nativeMessages = null;
Map<String,ProtobufEnum> enumTypes = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.fasterxml.jackson.dataformat.protobuf.schemagen;

import com.fasterxml.jackson.databind.BeanProperty;

public class AnnotationBasedTagGenerator implements TagGenerator {

@Override
public int nextTag(BeanProperty writer) {
if (ProtobuffSchemaHelper.hasIndexAnnotation(writer)) {
return ProtobuffSchemaHelper.getJsonProperty(writer).index();
}
throw new IllegalStateException("No 'JsonProperty.index' annotation found for " + writer.getFullName()
+ ", either annotate all properties of type " + writer.getWrapperName().getSimpleName() + " with indexes or none at all");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.fasterxml.jackson.dataformat.protobuf.schemagen;

import com.fasterxml.jackson.databind.BeanProperty;

public class DefaultTagGenerator implements TagGenerator {

protected int _tagCounter;

public DefaultTagGenerator() {
this(1);
}

public DefaultTagGenerator(int startingTag) {
_tagCounter = startingTag;
}

@Override
public int nextTag(BeanProperty writer) {
if (ProtobuffSchemaHelper.hasIndexAnnotation(writer)) {
throw new IllegalStateException(writer.getFullName()
+ " is annotated with 'JsonProperty.index', however not all properties of type "
+ writer.getWrapperName().getSimpleName()
+ " are annotated. Either annotate all properties or none at all.");
}

return nextTag();
}

public int nextTag() {
return _tagCounter++;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.fasterxml.jackson.dataformat.protobuf.schemagen;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.databind.JavaType;

public class DefinedTypeElementBuilders {

protected Map<JavaType, TypeElementBuilder> _definedTypeElementBuilders = new LinkedHashMap<>();

protected Set<JavaType> _isNestedType = new HashSet<>();

public DefinedTypeElementBuilders() {
}

public void AddTypeElement(JavaType type, TypeElementBuilder builder, boolean isNested) {
if (_definedTypeElementBuilders.containsKey(type)) { //Type element builder already defined
if (_definedTypeElementBuilders.get(type) != builder) { //Not expect this.
throw new IllegalStateException("Trying to redefine TypeElementBuilder for type " + type);
}
} else { //new builder
_definedTypeElementBuilders.put(type, builder);
}

if(isNested) {
_isNestedType.add(type);
}
}

public boolean containsBuilderFor(JavaType type) {
return _definedTypeElementBuilders.containsKey(type);
}

public TypeElementBuilder getBuilderFor(JavaType type) {
return _definedTypeElementBuilders.get(type);
}

public Set<TypeElementBuilder> getAllBuilders() {
return new HashSet<TypeElementBuilder>(_definedTypeElementBuilders.values());
}

public Set<TypeElementBuilder> getAllNestedBuilders() {
return getAllBuildersFor(_isNestedType);
}

public Set<TypeElementBuilder> getDependencyBuilders() {
return getNonNestedBuilders(true);
}

public Set<TypeElementBuilder> getNonNestedBuilders() {
return getNonNestedBuilders(false);
}

public Set<TypeElementBuilder> getNonNestedBuilders(boolean excludeRoot) {
Set<JavaType> types = _definedTypeElementBuilders.keySet(); //all keys
types.removeAll(_isNestedType); //exclude nested type

if(excludeRoot) { //exclude root
if(_definedTypeElementBuilders.isEmpty()) {
throw new IllegalStateException("DefinedTypeElementBuilders._definedTypeElementBuilders is empty");
}
types.remove(_definedTypeElementBuilders.keySet().iterator().next()); //expect the first element is root
}

return getAllBuildersFor(types);
}

protected HashSet<TypeElementBuilder> getAllBuildersFor(Collection<JavaType> types) {
HashSet<TypeElementBuilder> nestedBuilder = new HashSet<>();
for (JavaType type : types) {
nestedBuilder.add(getBuilderFor(type));
}
return nestedBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.fasterxml.jackson.dataformat.protobuf.schemagen;

import java.util.Set;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor.Base;
import com.squareup.protoparser.EnumConstantElement;
import com.squareup.protoparser.EnumElement;
import com.squareup.protoparser.TypeElement;

public class EnumElementVisitor extends Base implements TypeElementBuilder {

EnumElement.Builder _builder;

DefaultTagGenerator _tagGenerator = new DefaultTagGenerator(0);

public EnumElementVisitor(SerializerProvider provider, JavaType type,
DefinedTypeElementBuilders definedTypeElementBuilders, boolean isNested) {

if (!type.isEnumType()) {
throw new IllegalArgumentException("Expected an enum, however given type is " + type);
}

_builder = EnumElement.builder();
_builder.name(type.getRawClass().getSimpleName());
_builder.documentation("Enum for " + type.toCanonical());

definedTypeElementBuilders.AddTypeElement(type, this, isNested);
}

@Override
public TypeElement build() {
return _builder.build();
}

@Override
public void enumTypes(Set<String> enums) {
for (String eName : enums) {
_builder.addConstant(buildEnumConstant(eName));
}
}

protected EnumConstantElement buildEnumConstant(String name) {
EnumConstantElement.Builder builder = EnumConstantElement.builder();
builder.name(name);
builder.tag(_tagGenerator.nextTag());
return builder.build();
}
}
Loading