/** * */ package org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.dotproto; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager; import org.ebayopensource.turmeric.tools.codegen.CodeGenContext; import org.ebayopensource.turmeric.tools.codegen.exception.CodeGenFailedException; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.ProtobufArtifactsGenerator; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.model.ProtobufEnumEntry; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.model.ProtobufEnumMessage; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.model.ProtobufField; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.model.ProtobufMessage; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.model.ProtobufSchema; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.tag.DefaultProtobufTagGenerator; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.tag.ProtobufMetadataFileWriter; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.tag.ProtobufMetadataWriter; import org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.tag.ProtobufTagGenerator; /** * @author rkulandaivel * * This class creates the Dot proto file, from the protobuf schema. * Creates a single file for the entire schema object. * The file is created with path as * <ProjectRoot>/meta-src/META-INF/soa/services/proto/<ServiceAdminName>/ServiceAdminName.proto * * */ public class DotProtoGenerator { private static Logger s_logger = LogManager .getInstance(ProtobufArtifactsGenerator.class); private static DotProtoGenerator s_instance = new DotProtoGenerator(); private Logger getLogger() { return s_logger; } public static DotProtoGenerator getInstance(){ return s_instance; } /** * Creates the proto file under path <ProjectRoot>/meta-src/META-INF/soa/services/proto/<ServiceAdminName>/ServiceAdminName.proto. * It uses input option dest location as base path. * If location is empty, then it uses project root * * Before it writes the file, it generates the sequence number for each message with the help of ProtobufTagGenerator. * * * @param schema * @param codeGenContext * @throws CodeGenFailedException */ public void generate( ProtobufSchema schema, CodeGenContext codeGenContext ) throws CodeGenFailedException{ FileContentWriter writer; try { writer = new FileContentWriter( new File( schema.getDotprotoTargetDir() ) ); } catch (IOException e) { getLogger().log(Level.SEVERE, "Failed to create File Content writer", e); throw new CodeGenFailedException("Failed to create File Content writer", e); } getLogger().log(Level.INFO, "Start updating sequence numbers"); updateSchemaWithSequenceNumbers( schema, codeGenContext ); getLogger().log(Level.INFO, "Start generating dot proto file"); try { build(writer, schema); } catch (Exception e) { getLogger().log(Level.SEVERE, "Failed to write the dot proto file", e); throw new CodeGenFailedException("Failed to write the dot proto file", e); } } /** * This method is responsible for two things. * 1. Updates all the fields with the sequence number generated. * 2. get the tags to be persisted from ProtobufTagGenerator, and persist in protobuf schema. * * @param schema * @param codeGenContext * @throws CodeGenFailedException */ private void updateSchemaWithSequenceNumbers( ProtobufSchema schema, CodeGenContext codeGenContext) throws CodeGenFailedException{ String wsdlFile = codeGenContext.getInputOptions().getInputFile(); File protoFile = new File( schema.getDotprotoTargetDir(), schema.getDotprotoFileName() ); if( protoFile.exists() && !protoFile.canWrite() ){ String message = "The dot proto file under the path '"+protoFile.getPath()+"' is not writable. Please make the file writable."; getLogger().log(Level.SEVERE, message); throw new CodeGenFailedException(message); } ProtobufTagGenerator generator; try { generator = new DefaultProtobufTagGenerator(new File(wsdlFile), protoFile); } catch (Exception e) { getLogger().log(Level.SEVERE, "Protobuf tag generator creation failed.", e); throw new CodeGenFailedException("Protobuf tag generator creation failed.", e); } try { updateSchemaWithSequenceNumbers( schema, generator, false ); } catch (Exception e1) { getLogger().log(Level.SEVERE, "Protobuf Sequence number retreival and updation failed.", e1); throw new CodeGenFailedException("Protobuf Sequence number retreival and updation failed.", e1); } ByteArrayOutputStream outputStream; try { outputStream = new ByteArrayOutputStream(); ProtobufMetadataWriter metadataWriter = new ProtobufMetadataFileWriter(); metadataWriter.write(generator.getTagsToPersist(), outputStream ); } catch (Exception e) { getLogger().log(Level.SEVERE, "Protobuf metadata writer failed.", e); throw new CodeGenFailedException("Protobuf metadata writer failed.", e); } //persist the metadata bytes which will written at the end of proto file. schema.setMetadataBytes( outputStream.toByteArray() ); } /** * Updates the Sequence number for each field in all messages with the help of ProtobufTagGenerator. * If strictUpdate and if any of the following occurs, exception is thrown. * 1. If the field name returned by the ProtobufTagGenerator does not match with any of the fields in ProtobufMessage. * 2. If one or more fields from the message schema does not have tag numbers generated. * * For #1, exception is thrown immediately. * But for #2, exception is thrown at last by collecting all the fields which does not have tag numbers generated. * * @param schema * @param codeGenContext * @param writer * @throws CodeGenFailedException */ public void updateSchemaWithSequenceNumbers( ProtobufSchema schema, ProtobufTagGenerator generator, boolean strictUpdate ) throws CodeGenFailedException{ Map<QName, Set<String>> fieldsDoesNotHaveSeqNo = new HashMap<QName, Set<String>>(); for( ProtobufMessage message : schema.getMessages() ){ QName typeName = message.getSchemaTypeName().getTypeName(); Map<String, Integer> tagsForType = generator.getTagsForType( typeName ); if(message.isEnumType()){ Set<String> enumFieldsDontHaveSeqNo = new HashSet<String>(); for( ProtobufEnumEntry enumField : ((ProtobufEnumMessage)message).getEnumEntries() ) { Integer seqNo = tagsForType.get(enumField.getXsdEnumValue() ); if( seqNo == null ){ enumFieldsDontHaveSeqNo.add( enumField.getXsdEnumValue() ); }else{ enumField.setSequenceNumber( seqNo ); } } if( enumFieldsDontHaveSeqNo.size() > 0 ){ fieldsDoesNotHaveSeqNo.put(typeName, enumFieldsDontHaveSeqNo ); } }else{ //if no fields, then continue if(message.getFields().size() == 0 ){ continue; } Map<String, ProtobufField> fieldsMap = getFieldsMap( message.getFields() ); for( Map.Entry<String, Integer> entry : tagsForType.entrySet() ){ String fieldname = entry.getKey(); if( fieldname.startsWith("@") ){ fieldname = fieldname.replace("@", typeName.getLocalPart()+"_" ); } if(fieldsMap.get(fieldname) == null ){ if( strictUpdate ){ throw new CodeGenFailedException("The field name '"+ fieldname+"' does not exists in proto message " + typeName); } continue; } fieldsMap.get(fieldname).setSequenceTagNumber( entry.getValue() ); fieldsMap.remove(fieldname); } if( fieldsMap.size() > 0 ){ fieldsDoesNotHaveSeqNo.put(typeName, fieldsMap.keySet() ); } } } if( fieldsDoesNotHaveSeqNo.size() > 0 ){ if( strictUpdate ){ throw new CodeGenFailedException("Sequence no generation is not successful for following fields. " + fieldsDoesNotHaveSeqNo.toString() ); } } } private Map<String, ProtobufField> getFieldsMap( List<ProtobufField> fields){ Map<String, ProtobufField> fieldsMap = new HashMap<String, ProtobufField>(); for(ProtobufField field : fields ){ fieldsMap.put(field.getFieldName(), field ); } return fieldsMap; } /** * Creates the formatter and writes the file. * * @param writer * @param schema * @throws Exception */ private void build( FileContentWriter writer, ProtobufSchema schema ) throws Exception{ DotProtoFormatter formatter = createSourceFileWriter( writer, schema ); formatter.write( new ProtobufSchemaWriter( schema ) ); formatter.close(); } /** * Creates the formatter which takes care of indentation, formatting comments. * * @param writer * @param schema * @return * @throws Exception */ private DotProtoFormatter createSourceFileWriter( FileContentWriter writer, ProtobufSchema schema ) throws Exception{ Writer bw = new BufferedWriter( writer.openSource( schema.getDotprotoFileName() ) ); return new DotProtoFormatter(new PrintWriter(bw)); } }