Polymorphism with swagger not working as expected - swagger

I am using springfox version 2.9.2 and swagger annotations 1.5.x. The ApiModel annotations support the discriminator, subTypes and parent attribute which are required to make polymorphism work but I am not seeing the correct apidocs generated to enable polymorphism.
Here is my annotated code.
#RestController
#RequestMapping("/api/vehicles")
public class VehicleController {
private static final Logger LOGGER = LoggerFactory.getLogger(VehicleController.class);
#PostMapping(consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
void post(#RequestBody Vehicle anyVehicle) {
LOGGER.info("Vehicle : {}", anyVehicle);
}
}
#ApiModel(discriminator = "type", subTypes = {Car.class, Bike.class})
public class Vehicle {
String brand;
String type;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
#ApiModel(parent = Vehicle.class)
public class Car extends Vehicle {
int noOfDoors;
boolean powerWindows;
public int getNoOfDoors() {
return noOfDoors;
}
public void setNoOfDoors(int noOfDoors) {
this.noOfDoors = noOfDoors;
}
public boolean isPowerWindows() {
return powerWindows;
}
public void setPowerWindows(boolean powerWindows) {
this.powerWindows = powerWindows;
}
}
#ApiModel(parent = Vehicle.class)
public class Bike extends Vehicle {
boolean pillion;
public boolean isPillion() {
return pillion;
}
public void setPillion(boolean pillion) {
this.pillion = pillion;
}
}
When the docs get generated is basically shows one endpoint which handles a POST request and takes in a Vehicle as the model.
Is what I am doing here supposed to work? Can someone point me to a working example of this with SpringFox that I can look at?

Support for discriminator is not available in Swagger UI yet. You can follow these issues for status updates:
Discriminator does not switch schema
subTypes not displayed in model

Related

SpringDoc swagger documentation generation exception in nested complextype

We have a Person class. Person class has a property with type PersonDetail. And PersonDetail has a property with type Mail class.
When we start the application and navigate to swagger ui html page, Mail class is not generated in components section of openapi definition and we get "Could not resolve reference: Could not resolve pointer: /components/schemas/Mail does not exist in document" error on page. As we checked if there is a complex type in the third level that time springdoc can not resolve that type.
Person and PersonDetail works fine but Mail fails.
Person->PersonDetail->Mail
public class Person {
private String name;
private PersonDetail personDetail;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PersonDetail getPersonDetail() {
return personDetail;
}
public void setPersonDetail(PersonDetail personDetail) {
this.personDetail = personDetail;
}
}
public class PersonDetail {
private String surname;
private List<Mail> mails;
public List<Mail> getMails() {
return mails;
}
public void setMails(List<Mail> mails) {
this.mails = mails;
}
}
public class Mail {
private String mailAddress;
public String getMailAddress() {
return mailAddress;
}
public void setMailAddress(String mailAddress) {
this.mailAddress = mailAddress;
}
}
#get(path = "/getPersonTest")
#operation(description = "Testttt")
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "successful operation",
content = #content(schema = #Schema(implementation = Person.class)))})
public ResponseEntity getPerson(#RequestParam String name){
Person person = new Person();
return ResponseEntity.status(HttpStatus.OK).body(person);
}
There is no issue.
It seems that you are not using the right configuration.
We already answered you here: https://github.com/springdoc/springdoc-openapi/issues/679

Using the "version" annotation in a document with spring boot elasticearch

i'm using spring-boot-starter-data-elasticsearch (1.4.0.M3).
I'm unable to get the version (_version in elasticsearch query result) of a document using the annoation "version".
Any idea why the annotation isn't working ?
f.e.:
#GwtCompatible
#Document(indexName = "myIndexName")
public class Catalog implements Serializable {
private List<GroupProduct> groups;
#Id
private String uuid;
#Version
private Long version;
#Field(type = FieldType.Nested)
private List<Product> products;
private String label;
#NotEmpty
private String organizationUuid;
private List<String> organizationUnitUuids;
private Date updateDate;
private List<VAT> vats;
public Catalog() {
}
public List<GroupProduct> getGroups() {
return groups;
}
public List<Product> getProducts() {
return products;
}
public Date getUpdateDate() {
return updateDate;
}
public void setGroups(List<GroupProduct> groups) {
this.groups = groups;
}
public void setProducts(List<Product> products) {
this.products = products;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public List<VAT> getVats() {
return vats;
}
public void setVats(List<VAT> vats) {
this.vats = vats;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getOrganizationUuid() {
return organizationUuid;
}
public void setOrganizationUuid(String organizationUuid) {
this.organizationUuid = organizationUuid;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public List<String> getOrganizationUnitUuids() {
return organizationUnitUuids;
}
public void setOrganizationUnitUuids(List<String> organizationUnitUuids) {
this.organizationUnitUuids = organizationUnitUuids;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
Spring Data Elasticsearch (as of version 2.0.2) seems to have only partial support for the #Version annotation. If you annotate a document with a version field, it will be used when indexing a document. It will tell Elasticsearch that the document being saved is that specified version. If the new version is less than or equal to the version of the current document, Elasticsearch will throw a VersionConflictEngineException.
Unfortunately, Spring does not appear to populate this version field when a document is retrieved. As far as I can tell, this makes the version annotation useless. Perhaps the project will add this support in the near future. In the meantime, I have found a workaround by extending the default ResultMapper that Spring uses:
public class ExtendedResultMapper extends DefaultResultMapper {
protected MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
public ExtendedResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
super(mappingContext);
this.mappingContext = mappingContext;
}
#Override
public <T> T mapResult(GetResponse response, Class<T> clazz) {
T result = super.mapResult(response, clazz);
if (result != null) {
setPersistentEntityVersion(result, response.getVersion(), clazz);
}
return result;
}
#Override
public <T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
LinkedList<T> results = super.mapResults(responses, clazz);
if (results != null) {
for (int i = 0; i < results.size(); i++) {
setPersistentEntityVersion(results.get(i), responses.getResponses()[i].getResponse().getVersion(), clazz);
}
}
return results;
}
private <T> void setPersistentEntityVersion(T result, Long version, Class<T> clazz) {
if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) {
PersistentProperty<ElasticsearchPersistentProperty> versionProperty = mappingContext.getPersistentEntity(clazz).getVersionProperty();
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
Method setter = versionProperty.getSetter();
if (setter != null) {
try {
setter.invoke(result, version);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}
}
You can tell Spring to use this version instead of the default mapper as follows:
#Autowired
private Client client;
#Bean
public ElasticsearchTemplate elasticsearchTemplate() {
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
ExtendedResultMapper mapper = new ExtendedResultMapper(converter.getMappingContext());
return new ElasticsearchTemplate(client, converter, mapper);
}
Note that the version is only populated for Get or Multi-Get requests. Search results do not include version information.
You could also use this same approach to extract other information from the GetResponse objects.
Using this code, if you get a document and then try to save it back, it will fail unless you increment the version.

ResultSet mapping to object dynamically in dropwizard

I was trying to map ResultSet data to an object and returning it. Here is how i'm mapping data to an object. Now i'm having only 7 columns in resultset so this is working fine but what if i'm having 20 or 30 columns. How can i map dynamically those columns.
public class ProductsWrapperMapper implements ResultSetMapper<ProductsWrapper> {
public ProductsWrapper map(int i, ResultSet resultSet,
StatementContext statementContext) throws SQLException {
ProductsWrapper product = new ProductsWrapper();
if ((isColumnPresent(resultSet,"a_productid"))) {
product.setId(resultSet.getInt("a_productid"));
}
if ((isColumnPresent(resultSet,"a_productname"))) {
product.setProductName(resultSet.getString("a_productname"));
}
if ((isColumnPresent(resultSet,"a_productlink"))) {
product.setLink(resultSet.getString("a_productlink"));
}
if ((isColumnPresent(resultSet,"a_productimagelink"))) {
product.setImageLink(resultSet.getString("a_productimagelink"));
}
if ((isColumnPresent(resultSet,"a_websiteid"))) {
product.setWebsiteId(resultSet.getInt("a_websiteid"));
}
if ((isColumnPresent(resultSet,"a_productidentification"))) {
product.setProductIdentification(resultSet
.getString("a_productidentification"));
}
if ((isColumnPresent(resultSet,"a_adddate"))) {
product.setAddDate(resultSet.getString("a_adddate"));
}
return product;
}
public boolean isColumnPresent(ResultSet resultSet,String column) {
try {
#SuppressWarnings("unused")
int index = resultSet.findColumn(column);
return true;
} catch (SQLException e) {
// TODO Auto-generated catch block
return false;
}
}
}
Below one is my class which i was returning the object from mapper class above.
#JsonInclude(Include.NON_NULL)
public class ProductsWrapper {
private int id;
private String productName;
private String link;
private String imageLink;
private int websiteId;
private String productIdentification;
private String addDate;
int getWebsiteId() {
return websiteId;
}
public void setWebsiteId(int websiteId) {
this.websiteId = websiteId;
}
public String getProductIdentification() {
return productIdentification;
}
public void setProductIdentification(String productIdentification) {
this.productIdentification = productIdentification;
}
public String getAddDate() {
return addDate;
}
public void setAddDate(String addDate) {
this.addDate = addDate;
}`enter code here`
public ProductsWrapper(int id) {
this.setId(id);
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getImageLink() {
return imageLink;
}
public void setImageLink(String imageLink) {
this.imageLink = imageLink;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
You can also try Jdbi-folder. It automatically takes care of dynamic bynding and also it provides one to many mapping relationship.
You can add Rosetta as a mapper for your JDBI result sets (it also works for bindings). Have a look at the advanced features to map column names with underscores to snake snake case java names.
Beware that there is no warning message if Rosetta is unable to map a value: any missed property in the target bean will just be empty. I found that my database returned column names in capital letters, therefore the LowerCaseWithUnderscoresStrategy in the example didn't work for me. I created a UpperCaseWithUnderscoresStrategy.
To skip writing getters and setters in ProductsWrapper have a look at Lombok's #Data annotation.

Customizing model metadata data annotations

Is there any way to add a custom data annotation for metadata? I find than [DefaultValue] doesn't work
namespace PROJECT.Common.Attributes
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class MyDefaultValueAttribute : Attribute, IMetadataAware
{
public string DefaultValue;
private dynamic _DefaultValue;
public MyDefaultValueAttribute(string m_value_tx)
{
_DefaultValue = m_value_tx;
}
public MyDefaultValueAttribute(bool m_default_yn)
{
_DefaultValue = m_default_yn;
}
public MyDefaultValueAttribute(Int32 m_default_no)
{
_DefaultValue = m_default_no;
}
public MyDefaultValueAttribute(DateTime m_default_dt)
{
_DefaultValue = m_default_dt;
}
public MyDefaultValueAttribute(decimal m_defaul_tx)
{
_DefaultValue = m_defaul_tx;
}
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["DefaultValue"] = _DefaultValue;
}
}
and on i created a model binder but the problem is, the data annotation that i make only works for string..
i am passing dynamic on my constructor...
any help is appreciated... I clearly want custom annotations
THANKS

db4o Transparent Persistence Not Working

Using db4o client/server, updates are not working for collection properties of an object. I'm using transparent persistence, but that's not helping. Then, I changed my Collection property to ActivatableCollection, but no luck.
This is the server setup:
private void StartDatabase()
{
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.Networking.MessageRecipient = this;
serverConfiguration.Common.Add(new TransparentActivationSupport());
serverConfiguration.Common.Add(new TransparentPersistenceSupport());
string db4oDatabasePath = AppDomain.CurrentDomain.BaseDirectory;
string db4oDatabaseFileName = ConfigurationManager.AppSettings["db4oDatabaseFileName"];
int databaseServerPort = Convert.ToInt32(ConfigurationManager.AppSettings["databaseServerPort"], CultureInfo.InvariantCulture);
_db4oServer = Db4oClientServer.OpenServer(serverConfiguration, db4oDatabasePath + db4oDatabaseFileName, databaseServerPort);
string databaseUser = ConfigurationManager.AppSettings["databaseUser"];
string databasePassword = ConfigurationManager.AppSettings["databasePassword"];
_db4oServer.GrantAccess(databaseUser, databasePassword);
}
This is the entity that I want to save:
public class Application : ActivatableEntity
And this is the property within the Application entity:
public ActivatableCollection<TaskBase> Tasks { get; private set; }
This is the client code to update each object within the collection:
Application application = (from Application app in db
where app.Name == "Foo"
select app).FirstOrDefault();
foreach (TaskBase task in application.Tasks)
{
task.Description += ".";
}
db.Store(application);
Curiously, db.Commit() didn't work either.
There are two work-arounds, but I'd rather do this the "right" way.
Work-around 1: Call db.Store(task) on each task as the change is made.
Work-around 2: Before calling db.Store(), do this:
db.Ext().Configure().UpdateDepth(5);
Can anyone tell me why the list isn't updating?
If it helps, here is the ActivatableCollection class:
public class ActivatableCollection<T> : Collection<T>, IActivatable
{
[Transient]
private IActivator _activator;
/// <summary>
/// Activates the specified purpose.
/// </summary>
/// <param name="purpose">The purpose.</param>
public void Activate(ActivationPurpose purpose)
{
if (this._activator != null)
{
this._activator.Activate(purpose);
}
}
/// <summary>
/// Binds the specified activator.
/// </summary>
/// <param name="activator">The activator.</param>
public void Bind(IActivator activator)
{
if (_activator == activator) { return; }
if (activator != null && null != _activator) { throw new System.InvalidOperationException(); }
_activator = activator;
}
}
Indeed, transparent persistence needs a call to it's activator before every field access. However the intentions is that you do this with the enhancer-tool instead of implementing manually.
Another note: When you're using CascadeOnUpdate(true) everywhere db4o will end up storing every reachable activated object. If the object-graph is huge, this can be a major performance bottle-neck.
I was able to get transparent activation and persistence to work. I decided not to go with the approach for the reasons mentioned in my comment above. I think the easiest way to handle cascading updates is to simply use a client config like this:
IClientConfiguration clientConfig = Db4oClientServer.NewClientConfiguration();
And then either a bunch of these (this isn't so bad because we can add an attribute to every domain entity, then reflectively do this on each one):
clientConfig.Common.ObjectClass(typeof(Application)).CascadeOnUpdate(true);
Or this:
clientConfig.Common.UpdateDepth = 10;
return Db4oClientServer.OpenClient(clientConfig, databaseServerName, databaseServerPort, databaseUser, databasePassword);
Now, here is the server config that allowed me to get transparent persistence working.
private void StartDatabase()
{
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.Networking.MessageRecipient = this;
serverConfiguration.Common.Add(new TransparentActivationSupport());
serverConfiguration.Common.Add(new TransparentPersistenceSupport());
string db4oDatabasePath = AppDomain.CurrentDomain.BaseDirectory;
string db4oDatabaseFileName = ConfigurationManager.AppSettings["db4oDatabaseFileName"];
int databaseServerPort = Convert.ToInt32(ConfigurationManager.AppSettings["databaseServerPort"], CultureInfo.InvariantCulture);
_db4oServer = Db4oClientServer.OpenServer(serverConfiguration, db4oDatabasePath + db4oDatabaseFileName, databaseServerPort);
string databaseUser = ConfigurationManager.AppSettings["databaseUser"];
string databasePassword = ConfigurationManager.AppSettings["databasePassword"];
_db4oServer.GrantAccess(databaseUser, databasePassword);
}
Hope this helps someone.
I had the same problem with Transparent Activation and Persistence in java. I managed to get it to work cleaning the database and starting from scratch. However, no works by calling commit() after changing the object graph. You must call store() on the root object.
This is a simple example:
/*************** Item.java ******************************************/
import com.db4o.activation.ActivationPurpose;
import com.db4o.activation.Activator;
import com.db4o.collections.ActivatableSupport;
import com.db4o.ta.Activatable;
public class Item implements Activatable {
private String name;
private transient Activator activator;
public Item(String name) {
this.name = name;
}
public String getName() {
activate(ActivationPurpose.READ);
return name;
}
public void setName(String name) {
activate(ActivationPurpose.WRITE);
this.name = name;
}
#Override
public String toString() {
activate(ActivationPurpose.READ);
return "Item [name=" + name + "]";
}
public void activate(ActivationPurpose purpose) {
ActivatableSupport.activate(this.activator, purpose);
}
public void bind(Activator activator) {
this.activator = ActivatableSupport.validateForBind(this.activator, activator);
}
}
/******************* Container.java *********************************/
import java.util.Set;
import com.db4o.activation.ActivationPurpose;
import com.db4o.activation.Activator;
import com.db4o.collections.ActivatableHashSet;
import com.db4o.collections.ActivatableSupport;
import com.db4o.ta.Activatable;
public class Container implements Activatable {
private String name;
private Set<Item> items;
private transient Activator activator;
public Container() {
items = new ActivatableHashSet<Item>();
}
public String getName() {
activate(ActivationPurpose.READ);
return name;
}
public void setName(String name) {
activate(ActivationPurpose.WRITE);
this.name = name;
}
public void addItem(Item item) {
activate(ActivationPurpose.WRITE);
items.add(item);
}
public Set<Item> getItems() {
activate(ActivationPurpose.READ);
return items;
}
#Override
public String toString() {
activate(ActivationPurpose.READ);
return "Container [items=" + items + "]";
}
public void activate(ActivationPurpose purpose) {
ActivatableSupport.activate(this.activator, purpose);
}
public void bind(Activator activator) {
this.activator = ctivatableSupport.validateForBind(this.activator, activator);
}
}
/************* Main.java ********************************************/
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.ta.TransparentActivationSupport;
import com.db4o.ta.TransparentPersistenceSupport;
public class Main {
public static void main() {
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().add(new TransparentActivationSupport());
config.common().add(new TransparentPersistenceSupport());
ObjectContainer db = Db4oEmbedded.openFile(config, System.getProperty("user.home") + "/testTP.db4o");
Container c = new Container();
c.setName("Container0");
ObjectSet<Container> result = db.queryByExample(c);
if(result.hasNext()) {
c = result.next();
System.out.println(c);
}
c.addItem(new Item("Item" + c.getItems().size()));
db.store(c);
System.out.println(c);
db.close();
}
}

Resources