Question

I've found a strange behavior when I generate the client libraries for my endpoints.

In my appengine project, I have two endpoint classes that handles operations for two entities:

GroupEndpoint for the entity Group

ContactEndpoint for the entity Contact

The group entity has a list of Contacts, because sometimes when an API method of GroupEndpoint is called, I have to update its contacts.

The problem is when I generate the client libraries, the Contact entity is generated in two different namespaces (one for each endpoint), which is quite confusing, because I end up with the same class (exactly the same) twice.

Here is an example:

Group.java

package backend;

import java.util.List;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
public class Group {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private List<Contact> contactList;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public List<Contact> getContactList() {
        return contactList;
    }

    public void setContactList(List<Contact> contactList) {
        this.contactList = contactList;
    }
}

GroupEndpoint.java (dummy code for the example)

package backend;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.response.CollectionResponse;

import javax.annotation.Nullable;
import javax.inject.Named;

@Api(name = "groupendpoint")
public class GroupEndpoint {

    @ApiMethod(name = "listContact")
    public CollectionResponse<Group> listGroup(
            @Nullable @Named("cursor") String cursorString,
            @Nullable @Named("limit") Integer limit) {
        return null;
    }
}

Contact.java

package backend;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
public class Contact {

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
}

ContactEndpoint.java (dummy code for the example)

package backend;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;

import javax.annotation.Nullable;
import javax.inject.Named;

@Api(name = "contactendpoint")
public class ContactEndpoint {

    @ApiMethod(name = "listContact")
    public CollectionResponse<Contact> listContact(
            @Nullable @Named("cursor") String cursorString,
            @Nullable @Named("limit") Integer limit) {
        return null;
    }
}

build.gradle

// Currently, the appengine gradle plugin's appengine devappserver launch doesn't interact well with Intellij/AndroidStudio's
// Gradle integration.  As a temporary solution, please launch from the command line.
// ./gradlew modulename:appengineRun
// If you would like more information on the gradle-appengine-plugin please refer to the github page
// https://github.com/GoogleCloudPlatform/gradle-appengine-plugin

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.appengine:gradle-appengine-plugin:1.9.1'
    }
}

repositories {
    mavenCentral();
}

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'

sourceCompatibility = 1.7
targetCompatibility = 1.7

dependencies {
  appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.1'
  compile 'javax.servlet:servlet-api:2.5'
    compile 'com.google.appengine:appengine-endpoints:1.9.1'
    compile 'com.google.appengine:appengine-endpoints-deps:1.9.1'
    compile 'javax.servlet:servlet-api:2.5'
    compile 'org.datanucleus:datanucleus-core:3.2.13'
    compile 'org.datanucleus:datanucleus-api-jpa:3.2.3'
    compile 'javax.jdo:jdo-api:3.0.1'
    compile 'org.datanucleus:datanucleus-api-jdo:3.2.8'
    compile 'org.datanucleus:datanucleus-jdo-query:3.0.2'
    compile 'com.google.appengine.orm:datanucleus-appengine:2.1.2'
    compile 'org.apache.geronimo.specs:geronimo-jpa_2.0_spec:1.1'
    compile 'com.ganyo:gcm-server:1.0.2'
    compile 'net.sf.javaprinciples.persistence:persistence-api:4.0.0'
}

appengine {
  downloadSdk = true
  appcfg {
    oauth2 = true
  }
}

When I generate the client libraries, it creates groupendpoint-v1-java.zip and contactendpoint-v1-java.zip in my build directory. If I extract these files I see that for each zip file I have a Contact class.

For groupendpoint-v1-java.zip:

/*
 * 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.
 */
/*
 * This code was generated by https://code.google.com/p/google-apis-client-generator/
 * (build: 2014-04-15 19:10:39 UTC)
 * on 2014-04-22 at 12:22:19 UTC 
 * Modify at your own risk.
 */

package com.appspot.myapplicationid.groupendpoint.model;

/**
 * Model definition for Contact.
 *
 * <p> This is the Java data model class that specifies how to parse/serialize into the JSON that is
 * transmitted over HTTP when working with the groupendpoint. For a detailed explanation see:
 * <a href="http://code.google.com/p/google-http-java-client/wiki/JSON">http://code.google.com/p/google-http-java-client/wiki/JSON</a>
 * </p>
 *
 * @author Google, Inc.
 */
@SuppressWarnings("javadoc")
public final class Contact extends com.google.api.client.json.GenericJson {

  @Override
  public Contact set(String fieldName, Object value) {
    return (Contact) super.set(fieldName, value);
  }

  @Override
  public Contact clone() {
    return (Contact) super.clone();
  }

}

For contactendpoint-v1-java.zip:

/*
 * 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.
 */
/*
 * This code was generated by https://code.google.com/p/google-apis-client-generator/
 * (build: 2014-04-15 19:10:39 UTC)
 * on 2014-04-22 at 12:22:21 UTC 
 * Modify at your own risk.
 */

package com.appspot.myapplicationid.contactendpoint.model;

/**
 * Model definition for Contact.
 *
 * <p> This is the Java data model class that specifies how to parse/serialize into the JSON that is
 * transmitted over HTTP when working with the contactendpoint. For a detailed explanation see:
 * <a href="http://code.google.com/p/google-http-java-client/wiki/JSON">http://code.google.com/p/google-http-java-client/wiki/JSON</a>
 * </p>
 *
 * @author Google, Inc.
 */
@SuppressWarnings("javadoc")
public final class Contact extends com.google.api.client.json.GenericJson {

  @Override
  public Contact set(String fieldName, Object value) {
    return (Contact) super.set(fieldName, value);
  }

  @Override
  public Contact clone() {
    return (Contact) super.clone();
  }

}

Note that the only difference is that they belong to different namespaces. This is so confusing when I'm using the client libraries.

How can I avoid this behavior?

Thanks.

Was it helpful?

Solution

I was having the same problem and I figured out a way of solving this. It may have some advantages and restrictions but I will present them here.

You are having different models (on the Client side) because they are in different libs. For example, your Contact list will be in a libcontactendpoint and another libgroupendpoint, since your Contact class is used in both.

In order to have only one class representing your Entity on the client side, you'll need to keep it only in one endpoint (in the Client project). So, one way to do that, would be using the @Api annotation. This annotation is used in all your endpoint classes. So, if your class GroupEndpoint has @Api(name = "groupendpoint"), then a libgroupendpoint will be created. If you want both Contact and Group in the same endpoint, without duplications, your endpoint classes must point to the same API. Solution:

package backend;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.response.CollectionResponse;

import javax.annotation.Nullable;
import javax.inject.Named;

//Changing api name
@Api(name = "generalendpoint")
public class GroupEndpoint {

    @ApiMethod(name = "listContact")
    public CollectionResponse<Group> listGroup(
            @Nullable @Named("cursor") String cursorString,
            @Nullable @Named("limit") Integer limit) {
        return null;
    }
}

ContactEndpoint will also have generalendpoint (you can use any name with the condition that they are equal).

package backend;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;

import javax.annotation.Nullable;
import javax.inject.Named;

@Api(name = "generalendpoint")
public class ContactEndpoint {

    @ApiMethod(name = "listContact")
    public CollectionResponse<Contact> listContact(
            @Nullable @Named("cursor") String cursorString,
            @Nullable @Named("limit") Integer limit) {
        return null;
    }
}

Now, you won't have that problem. However, remember that now, in the Client side, instead of using Groupendpoint or Contactendpoint to do your operations and call those endpoint methods, now you must use Generalenpoint.

Disadvantage:

  1. all the methods of the classes that point to the same @Api will be in the same class after the generation of the cloud endpoint library
  2. Methods in classes that point to the same @Api (but different classes in Java) can not have the same name @ApiMethod(name = "nameOfMethod")

The 1 is not so important because you don't edit your library, just call the method. The 2nd requires attention when creating methods across your classes in your app engine project.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top