Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the logic to build cache key for GraphQL input type #143

Merged
merged 17 commits into from
Apr 26, 2019
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
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ android:
- android-22
- sys-img-armeabi-v7a-android-22
script:
- ./gradlew publishToMavenLocal
- cd aws-android-sdk-appsync-tests
- android list target
- "./gradlew build connectedCheck --stacktrace "
- cd ..
after_failure:
- cat aws-android-sdk-appsync-tests/build/outputs/androidTest-results/connected/*.xml
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log - AWS AppSync SDK for Android

## [Release 2.7.11](https://github.com/awslabs/aws-mobile-appsync-sdk-android/releases/tag/release_v2.7.11)

### Bug Fixes
* All the GraphQL input types now derive from the base class [InputType](https://github.com/awslabs/aws-mobile-appsync-sdk-android/blob/c88808c75cf25948a78eee210515c8b7dfcca12b/aws-android-sdk-appsync-api/src/main/java/com/apollographql/apollo/api/InputType.java). [cacheKey](https://github.com/awslabs/aws-mobile-appsync-sdk-android/pull/143/files#diff-3b667778e3f6cc993de08b4e7459c329) method from ResponseField is no longer used to compute the cache key and is being deleted. [CacheFieldValueResolver](https://github.com/awslabs/aws-mobile-appsync-sdk-android/pull/143/files#diff-0b41556c1e8c6bd843aafff408e59f1d) now accepts an instance of [CacheKeyBuilder](https://github.com/awslabs/aws-mobile-appsync-sdk-android/pull/143/files#diff-e329cc0b9923fb69cc28ea780b055493) which is used to compute cache keys instead of the cacheKey method. See [issue #103](https://github.com/awslabs/aws-mobile-appsync-sdk-android/issues/103)

## [Release 2.7.10](https://github.com/awslabs/aws-mobile-appsync-sdk-android/releases/tag/release_v2.7.10)

### Bug Fixes
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@ private GraphQLCall.Callback<AddPostMutation.Data> postsCallback = new GraphQLCa
};
```

## Running the Integration tests

Integration test for the SDK are in ```aws-android-sdk-appsync-tests``` folder and is an independent gradle project. The tests depend on certain artifacts being published to the local maven repository. In order to publish required artifacts to the local maven repository and run the tests execute following commands from the project root:

```
./gradlew publishToMavenLocal
cd aws-android-sdk-appsync-tests/
./gradlew connectedAndroidTest
```

To run tests from Android Studio, run ```./gradlew publishToMavenLocal``` from project root, open ```aws-android-sdk-appsync-tests/``` folder in Android Studio and run the tests normally.

## License

This library is licensed under the Amazon Software License.
12 changes: 12 additions & 0 deletions aws-android-sdk-appsync-api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apply plugin: 'java-library'
apply plugin: 'maven-publish'

apply from: rootProject.file('gradle-mvn-push.gradle')

Expand All @@ -10,5 +11,16 @@ dependencies {
compile "com.squareup.okhttp3:okhttp:3.8.1" // compileOnly
}

publishing {
publications {
pluginPublication(MavenPublication) {
from components.java
groupId 'com.amazonaws'
artifactId 'aws-android-sdk-appsync-api'
version VERSION_NAME + "-SNAPSHOT"
}
}
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
minbi marked this conversation as resolved.
Show resolved Hide resolved
* Copyright 2018-2019 Amazon.com,
* Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Amazon Software License (the "License").
* You may not use this file except in compliance with the
* License. A copy of the License is located at
*
* http://aws.amazon.com/asl/
*
* or in the "license" file accompanying this file. This file is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, express or implied. See the License
* for the specific language governing permissions and
* limitations under the License.
*/

package com.apollographql.apollo.api;

import javax.annotation.Nonnull;

public interface InputType {
@Nonnull InputFieldMarshaller marshaller();
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public class ResponseField {
private final boolean optional;
private final List<Condition> conditions;

private static final String VARIABLE_IDENTIFIER_KEY = "kind";
private static final String VARIABLE_IDENTIFIER_VALUE = "Variable";
private static final String VARIABLE_NAME_KEY = "variableName";
public static final String VARIABLE_IDENTIFIER_KEY = "kind";
public static final String VARIABLE_IDENTIFIER_VALUE = "Variable";
public static final String VARIABLE_NAME_KEY = "variableName";

/**
* Factory method for creating a Field instance representing {@link Type#STRING}.
Expand Down Expand Up @@ -250,13 +250,6 @@ public List<Condition> conditions() {
return conditions;
}

public String cacheKey(Operation.Variables variables) {
if (arguments.isEmpty()) {
return fieldName();
}
return String.format("%s(%s)", fieldName(), orderIndependentKey(arguments, variables));
}

/**
* Resolve field argument value by name. If argument represents a references to the variable, it will be resolved from
* provided operation variables values.
Expand All @@ -283,61 +276,12 @@ public String cacheKey(Operation.Variables variables) {
return argumentValue;
}

private String orderIndependentKey(Map<String, Object> objectMap, Operation.Variables variables) {
if (isArgumentValueVariableType(objectMap)) {
return orderIndependentKeyForVariableArgument(objectMap, variables);
}
List<Map.Entry<String, Object>> sortedArguments = new ArrayList<>(objectMap.entrySet());
Collections.sort(sortedArguments, new Comparator<Map.Entry<String, Object>>() {
@Override public int compare(Map.Entry<String, Object> argumentOne, Map.Entry<String, Object> argumentTwo) {
return argumentOne.getKey().compareTo(argumentTwo.getKey());
}
});
StringBuilder independentKey = new StringBuilder();
for (int i = 0; i < sortedArguments.size(); i++) {
Map.Entry<String, Object> argument = sortedArguments.get(i);
if (argument.getValue() instanceof Map) {
//noinspection unchecked
final Map<String, Object> objectArg = (Map<String, Object>) argument.getValue();
boolean isArgumentVariable = isArgumentValueVariableType(objectArg);
independentKey
.append(argument.getKey())
.append(":")
.append(isArgumentVariable ? "" : "[")
.append(orderIndependentKey(objectArg, variables))
.append(isArgumentVariable ? "" : "]");
} else {
independentKey.append(argument.getKey())
.append(":")
.append(argument.getValue().toString());
}
if (i < sortedArguments.size() - 1) {
independentKey.append(",");
}
}
return independentKey.toString();
}

private boolean isArgumentValueVariableType(Map<String, Object> objectMap) {
public static boolean isArgumentValueVariableType(Map<String, Object> objectMap) {
return objectMap.containsKey(VARIABLE_IDENTIFIER_KEY)
&& objectMap.get(VARIABLE_IDENTIFIER_KEY).equals(VARIABLE_IDENTIFIER_VALUE)
&& objectMap.containsKey(VARIABLE_NAME_KEY);
}

private String orderIndependentKeyForVariableArgument(Map<String, Object> objectMap, Operation.Variables variables) {
Object variable = objectMap.get(VARIABLE_NAME_KEY);
//noinspection SuspiciousMethodCalls
Object resolvedVariable = variables.valueMap().get(variable);
if (resolvedVariable == null) {
return null;
} else if (resolvedVariable instanceof Map) {
//noinspection unchecked
return orderIndependentKey((Map<String, Object>) resolvedVariable, variables);
} else {
return resolvedVariable.toString();
}
}

/**
* An abstraction for the field types
*/
Expand Down
12 changes: 12 additions & 0 deletions aws-android-sdk-appsync-compiler/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'maven-publish'

apply from: rootProject.file('gradle-mvn-push.gradle')

Expand All @@ -19,6 +20,17 @@ dependencies {
compile project(':aws-android-sdk-appsync-api') // impl
}

publishing {
publications {
pluginPublication(MavenPublication) {
from components.java
groupId 'com.amazonaws'
artifactId 'aws-android-sdk-appsync-compiler'
version VERSION_NAME + "-SNAPSHOT"
}
}
}

task pluginVersion {
def outputDir = file("src/generated/kotlin")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ object ClassNames {
val JAVA_OPTIONAL: ClassName = ClassName.get("java.util", "Optional")
val API_UTILS: ClassName = ClassName.get(Utils::class.java)
val FRAGMENT: ClassName = ClassName.get(GraphqlFragment::class.java)
val INPUT_TYPE: ClassName = ClassName.get(Input::class.java)
val INPUT: ClassName = ClassName.get(Input::class.java)
val BUILDER: ClassName = ClassName.get("", "Builder")
val MUTATOR: ClassName = ClassName.get(Mutator::class.java)
var S3ObjectInput: ClassName = ClassName.get(S3InputObjectInterface::class.java)
var S3Object: ClassName = ClassName.get(S3ObjectInterface::class.java)
val INPUT_TYPE: ClassName = ClassName.get(InputType::class.java)

fun <K : Any> parameterizedListOf(type: Class<K>): TypeName =
ParameterizedTypeName.get(LIST, ClassName.get(type))
Expand Down Expand Up @@ -84,6 +85,6 @@ object ClassNames {
ParameterizedTypeName.get(JAVA_OPTIONAL, type)

fun parameterizedInputType(type: TypeName): TypeName =
ParameterizedTypeName.get(INPUT_TYPE, type)
ParameterizedTypeName.get(INPUT, type)

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class InputTypeSpecBuilder(
TypeSpec.classBuilder(objectClassName)
.addAnnotation(Annotations.GENERATED_BY_APOLLO)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(ClassNames.INPUT_TYPE)
.addConstructor()
.addFields()
.addBuilder()
Expand Down Expand Up @@ -127,11 +128,14 @@ class InputTypeSpecBuilder(
.addSuperinterface(InputFieldMarshaller::class.java)
.addMethod(methodSpec)
.build()
return MethodSpec.methodBuilder(MARSHALLER_PARAM_NAME)
.addModifiers(Modifier.PUBLIC)
.returns(InputFieldMarshaller::class.java)
.addStatement("return \$L", marshallerType)
.build()
var marshallerSpec = MethodSpec.methodBuilder(MARSHALLER_PARAM_NAME)
.addModifiers(Modifier.PUBLIC)
.returns(InputFieldMarshaller::class.java)
.addStatement("return \$L", marshallerType);
if (! objectClassName.toString().equals("S3ObjectInput")) {
marshallerSpec = marshallerSpec.addAnnotation(Annotations.OVERRIDE)
}
return marshallerSpec.build()
}

private fun TypeSpec.Builder.addFields(): TypeSpec.Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ fun TypeName.isNullable(): Boolean = isOptional() || annotations.contains(Annota
fun TypeName.isOptional(): Boolean {
val rawType = (this as? ParameterizedTypeName)?.rawType ?: this
return rawType == ClassNames.OPTIONAL || rawType == ClassNames.GUAVA_OPTIONAL || rawType == ClassNames.JAVA_OPTIONAL
|| rawType == ClassNames.INPUT_TYPE
|| rawType == ClassNames.INPUT
}

fun TypeName.unwrapOptionalType(withoutAnnotations: Boolean = false): TypeName {
Expand All @@ -272,7 +272,7 @@ fun TypeName.unwrapOptionalType(withoutAnnotations: Boolean = false): TypeName {
fun TypeName.unwrapOptionalValue(varName: String, checkIfPresent: Boolean = true,
transformation: ((CodeBlock) -> CodeBlock)? = null): CodeBlock {
return if (isOptional() && this is ParameterizedTypeName) {
if (rawType == ClassNames.INPUT_TYPE) {
if (rawType == ClassNames.INPUT) {
val valueCode = CodeBlock.of("\$L.value", varName)
if (checkIfPresent) {
CodeBlock.of("\$L != null ? \$L : null", valueCode, transformation?.invoke(valueCode) ?: valueCode)
Expand Down
12 changes: 12 additions & 0 deletions aws-android-sdk-appsync-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apply plugin: 'groovy'
apply plugin: 'idea'
apply plugin: 'java-gradle-plugin'
apply plugin: 'maven-publish'

apply from: rootProject.file('gradle-mvn-push.gradle')

Expand All @@ -19,5 +20,16 @@ dependencies {
compile project(':aws-android-sdk-appsync-compiler') // impl
}

publishing {
publications {
pluginPublication(MavenPublication) {
from components.java
groupId 'com.amazonaws'
artifactId 'aws-android-sdk-appsync-gradle-plugin'
version VERSION_NAME + "-SNAPSHOT"
}
}
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
12 changes: 12 additions & 0 deletions aws-android-sdk-appsync-runtime/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apply plugin: 'java-library'
apply plugin: 'maven-publish'

apply from: rootProject.file('gradle-mvn-push.gradle')

Expand All @@ -12,5 +13,16 @@ dependencies {
compile project(':aws-android-sdk-appsync-api') // api
}

publishing {
publications {
pluginPublication(MavenPublication) {
from components.java
groupId 'com.amazonaws'
artifactId 'aws-android-sdk-appsync-runtime'
version VERSION_NAME + "-SNAPSHOT"
}
}
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright 2018-2019 Amazon.com,
* Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Amazon Software License (the "License").
* You may not use this file except in compliance with the
* License. A copy of the License is located at
*
* http://aws.amazon.com/asl/
*
* or in the "license" file accompanying this file. This file is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, express or implied. See the License
* for the specific language governing permissions and
* limitations under the License.
*/

package com.apollographql.apollo.internal.cache.normalized;

import com.apollographql.apollo.api.Operation;
import com.apollographql.apollo.api.ResponseField;

import javax.annotation.Nonnull;

public interface CacheKeyBuilder {
@Nonnull String build(@Nonnull ResponseField field, @Nonnull Operation.Variables variables);
}
Loading