From 34f83a5e025acfe9026b3b1746ea688b58ef2ea0 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Fri, 2 Sep 2016 18:51:47 -0500 Subject: [PATCH 1/2] Add support for object filters on Service objects. --- .../softlayer/api/example/ListServers.java | 35 ++-- .../api/example/ListServersAsync.java | 33 ++-- .../api/example/OrderVirtualServer.java | 4 +- .../com/softlayer/api/gen/ClassWriter.java | 47 ++++- src/main/java/com/softlayer/api/Filter.java | 161 ++++++++++++++++++ .../java/com/softlayer/api/Filterable.java | 21 +++ src/main/java/com/softlayer/api/Mask.java | 76 ++++++++- src/main/java/com/softlayer/api/Property.java | 143 ++++++++++++++++ .../java/com/softlayer/api/RestApiClient.java | 83 ++++++++- src/main/java/com/softlayer/api/Service.java | 2 +- .../com/softlayer/api/RestApiClientTest.java | 30 ++-- .../com/softlayer/api/service/TestEntity.java | 18 +- 12 files changed, 572 insertions(+), 81 deletions(-) create mode 100644 src/main/java/com/softlayer/api/Filter.java create mode 100644 src/main/java/com/softlayer/api/Filterable.java create mode 100644 src/main/java/com/softlayer/api/Property.java diff --git a/examples/src/main/java/com/softlayer/api/example/ListServers.java b/examples/src/main/java/com/softlayer/api/example/ListServers.java index fd04277..80ece1e 100644 --- a/examples/src/main/java/com/softlayer/api/example/ListServers.java +++ b/examples/src/main/java/com/softlayer/api/example/ListServers.java @@ -3,6 +3,7 @@ import com.softlayer.api.ApiClient; import com.softlayer.api.service.Account; import com.softlayer.api.service.Hardware; +import com.softlayer.api.service.software.Description; import com.softlayer.api.service.virtual.Guest; /** List all physical and virtual servers on an account */ @@ -13,23 +14,23 @@ public void run(ApiClient client) throws Exception { Account.Service service = Account.service(client); // To get specific information on an account (servers in this case) a mask is provided - service.withMask().hardware(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().hardware().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - service.withMask().virtualGuests(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().virtualGuests().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - + Hardware.Mask hardwareMask = service.withMask().hardware(); + hardwareMask.fullyQualifiedDomainName(); + hardwareMask.primaryIpAddress(); + hardwareMask.primaryBackendIpAddress(); + Description.Mask descriptionMask = hardwareMask.operatingSystem().softwareLicense().softwareDescription(); + descriptionMask.manufacturer(); + descriptionMask.name(); + descriptionMask.version(); + Guest.Mask guestMask = service.withMask().virtualGuests(); + guestMask.fullyQualifiedDomainName(); + guestMask.primaryIpAddress(); + guestMask.primaryBackendIpAddress(); + descriptionMask = guestMask.operatingSystem().softwareLicense().softwareDescription(); + descriptionMask.manufacturer(); + descriptionMask.name(); + descriptionMask.version(); + // Calling getObject will now use the mask Account account = service.getObject(); diff --git a/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java b/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java index 75daa49..2dc3a94 100644 --- a/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java +++ b/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java @@ -4,6 +4,7 @@ import com.softlayer.api.ResponseHandler; import com.softlayer.api.service.Account; import com.softlayer.api.service.Hardware; +import com.softlayer.api.service.software.Description; import com.softlayer.api.service.virtual.Guest; /** Asynchronous version of {@link ListServers} */ @@ -14,22 +15,22 @@ public void run(ApiClient client) throws Exception { Account.Service service = Account.service(client); // To get specific information on an account (servers in this case) a mask is provided - service.withMask().hardware(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().hardware().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - service.withMask().virtualGuests(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().virtualGuests().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); + Hardware.Mask hardwareMask = service.withMask().hardware(); + hardwareMask.fullyQualifiedDomainName(); + hardwareMask.primaryIpAddress(); + hardwareMask.primaryBackendIpAddress(); + Description.Mask descriptionMask = hardwareMask.operatingSystem().softwareLicense().softwareDescription(); + descriptionMask.manufacturer(); + descriptionMask.name(); + descriptionMask.version(); + Guest.Mask guestMask = service.withMask().virtualGuests(); + guestMask.fullyQualifiedDomainName(); + guestMask.primaryIpAddress(); + guestMask.primaryBackendIpAddress(); + descriptionMask = guestMask.operatingSystem().softwareLicense().softwareDescription(); + descriptionMask.manufacturer(); + descriptionMask.name(); + descriptionMask.version(); // Calling getObject will now use the mask // By using asAsync this runs on a separate thread pool, and get() is called on the resulting future diff --git a/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java b/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java index 8ca59e0..f55a823 100644 --- a/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java +++ b/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java @@ -85,7 +85,9 @@ public void run(ApiClient client) throws Exception { guest.getId(), minutesToWait); } else { // Using a mask, we can ask for the operating system password - service.withNewMask().primaryIpAddress().operatingSystem().passwords(); + Guest.Mask mask = service.withNewMask(); + mask.primaryIpAddress(); + mask.operatingSystem().passwords(); guest = service.getObject(); if (guest.getOperatingSystem() == null) { System.out.println("Unable to find operating system on completed guest"); diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index 06395a8..d3f62fe 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -35,6 +35,11 @@ public class ClassWriter extends JavaWriter { public static final String TYPE_SERVICE = "com.softlayer.api.Service"; public static final String TYPE_SERVICE_ASYNC = "com.softlayer.api.ServiceAsync"; public static final String TYPE_TYPE = "com.softlayer.api.Type"; + public static final String TYPE_FILTERABLE_BOOLEAN_PROPERTY = "com.softlayer.api.Property.BooleanProperty"; + public static final String TYPE_FILTERABLE_BYTE_ARRAY_PROPERTY = "com.softlayer.api.Property.ByteArrayProperty"; + public static final String TYPE_FILTERABLE_DATE_TIME_PROPERTY = "com.softlayer.api.Property.DateTimeProperty"; + public static final String TYPE_FILTERABLE_NUMBER_PROPERTY = "com.softlayer.api.Property.NumberProperty"; + public static final String TYPE_FILTERABLE_STRING_PROPERTY = "com.softlayer.api.Property.StringProperty"; private static final Set PROTECTED = EnumSet.of(Modifier.PROTECTED); private static final Set PUBLIC = EnumSet.of(Modifier.PUBLIC); @@ -107,8 +112,8 @@ public ClassWriter emitAnnotationWithAttrs(String annotationType, Object... attr } public ClassWriter emitMask() throws IOException { - - String baseMask = type.baseJavaType != null ? type.baseJavaType + ".Mask" : TYPE_MASK; + + String baseMask = type.baseJavaType != null ? type.baseJavaType + ".Mask" : TYPE_MASK; beginType("Mask", "class", PUBLIC_STATIC, baseMask).emitEmptyLine(); for (TypeClass.Property property : type.properties) { @@ -118,13 +123,36 @@ public ClassWriter emitMask() throws IOException { compressType(property.nonArrayJavaType + ".Mask")). endMethod().emitEmptyLine(); } else { - beginMethod("Mask", property.name, PUBLIC). - emitStatement("withLocalProperty(%s)", stringLiteral(property.name)). - emitStatement("return this"). - endMethod().emitEmptyLine(); + String method; + String returnType; + + if ("Boolean".equals(property.nonArrayJavaType)) { + method = "withBooleanProperty"; + returnType = TYPE_FILTERABLE_BOOLEAN_PROPERTY; + } else if ("byte[]".equals(property.nonArrayJavaType)) { + method = "withByteArrayProperty"; + returnType = TYPE_FILTERABLE_BYTE_ARRAY_PROPERTY; + } else if ("java.util.GregorianCalendar".equals(property.nonArrayJavaType)) { + method = "withDateTimeProperty"; + returnType = TYPE_FILTERABLE_DATE_TIME_PROPERTY; + } else if ("java.math.BigDecimal".equals(property.nonArrayJavaType) + || "java.math.BigInteger".equals(property.nonArrayJavaType) + || "Long".equals(property.nonArrayJavaType)) { + method = "withNumberProperty"; + returnType = TYPE_FILTERABLE_NUMBER_PROPERTY; + } else if ("String".equals(property.nonArrayJavaType)) { + method = "withStringProperty"; + returnType = TYPE_FILTERABLE_STRING_PROPERTY; + } else { + throw new IllegalArgumentException("Unrecognized primitive type: " + property.nonArrayJavaType); + } + beginMethod(returnType, property.name, PUBLIC) + .emitStatement("return " + method + "(%s)", stringLiteral(property.name)) + .endMethod() + .emitEmptyLine(); } } - + endType().emitEmptyLine(); return this; } @@ -212,8 +240,9 @@ public ClassWriter emitService() throws IOException { beginMethod("ServiceAsync", "asAsync", PUBLIC).endMethod(); beginMethod("Mask", "withNewMask", PUBLIC).endMethod(); beginMethod("Mask", "withMask", PUBLIC).endMethod(); - beginMethod("void", "setMask", PUBLIC, "Mask", "mask").endMethod().emitEmptyLine(); - + beginMethod("Mask", "withNewFilter", PUBLIC).endMethod(); + beginMethod("Mask", "withFilter", PUBLIC).endMethod().emitEmptyLine(); + for (TypeClass.Method method : type.methods) { emitServiceMethod(method, false); } diff --git a/src/main/java/com/softlayer/api/Filter.java b/src/main/java/com/softlayer/api/Filter.java new file mode 100644 index 0000000..03e5803 --- /dev/null +++ b/src/main/java/com/softlayer/api/Filter.java @@ -0,0 +1,161 @@ +package com.softlayer.api; + +import java.util.Collections; +import java.util.Map; + +public abstract class Filter { + protected abstract Map getFilterMap(); + + public enum SimpleOperation { + EQUAL_TO { + @Override + String withValue(String value) { + requireNotNull(value); + return value; + } + }, + NOT_EQUAL_TO { + @Override + String withValue(String value) { + requireNotNull(value); + return "!= " + value; + } + }, + GREATER_THAN { + String withValue(String value) { + requireNotNull(value); + return "> " + value; + } + }, + GREATER_OR_EQUAL_TO { + @Override + String withValue(String value) { + requireNotNull(value); + return ">= " + value; + } + }, + LESS_THAN { + @Override + String withValue(String value) { + requireNotNull(value); + return "< " + value; + } + }, + LESS_OR_EQUAL_TO { + @Override + String withValue(String value) { + requireNotNull(value); + return "<= " + value; + } + }, + STARTS_WITH { + @Override + String withValue(String value) { + requireNotNull(value); + return "^= " + value; + } + }, + NOT_STARTS_WITH { + @Override + String withValue(String value) { + requireNotNull(value); + return "!^= " + value; + } + }, + ENDS_WITH { + @Override + String withValue(String value) { + requireNotNull(value); + return "$= " + value; + } + }, + NOT_ENDS_WITH { + @Override + String withValue(String value) { + requireNotNull(value); + return "!$= " + value; + } + }, + CONTAINS { + @Override + String withValue(String value) { + requireNotNull(value); + return "*= " + value; + } + }, + NOT_CONTAINS { + @Override + String withValue(String value) { + requireNotNull(value); + return "!*= " + value; + } + }, + EQUAL_TO_IGNORE_CASE { + @Override + String withValue(String value) { + requireNotNull(value); + return "_= " + value; + } + }, + NOT_EQUAL_TO_IGNORE_CASE { + @Override + String withValue(String value) { + requireNotNull(value); + return "!_= " + value; + } + }, + NOT_NULL { + @Override + String withValue(String value) { + requireNull(value); + return "not null"; + } + }, + IS_NULL { + @Override + String withValue(String value) { + requireNull(value); + return "null"; + } + }; + + void requireNull(String value) { + if (value != null) { + throw new IllegalArgumentException("Null is required for operation " + this); + } + } + + void requireNotNull(String value) { + if (value == null) { + throw new IllegalArgumentException("Null is not allowed for operation " + this); + } + } + + abstract String withValue(String value); + } + + public static class SimpleFilter extends Filter { + private final SimpleOperation operation; + private final T value; + + public SimpleFilter(SimpleOperation operation, T value) { + this.operation = operation; + this.value = value; + } + + public SimpleOperation getOperation() { + return operation; + } + + public T getValue() { + return value; + } + + @Override + protected Map getFilterMap() { + return Collections.singletonMap("operation", getOperation().withValue( + getValue() == null ? null : getValue().toString() + )); + } + } +} diff --git a/src/main/java/com/softlayer/api/Filterable.java b/src/main/java/com/softlayer/api/Filterable.java new file mode 100644 index 0000000..e672a2e --- /dev/null +++ b/src/main/java/com/softlayer/api/Filterable.java @@ -0,0 +1,21 @@ +package com.softlayer.api; + +/** + * Interface implemented by services that accept filters for some calls. + */ +public interface Filterable { + /** Overwrite the existing filter on this object with a new one and return it. */ + public Mask withNewFilter(); + + /** Use the existing filter on this object or create it if not present. */ + public Mask withFilter(); + + /** Set the filter to the given object. */ + public void setFilter(Mask filter); + + /** Set the filter to a string. */ + public void setFilter(String filter); + + /** Removes the filter from this object. */ + public void clearFilter(); +} diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index a62c3bb..d9ed8ce 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -1,13 +1,17 @@ package com.softlayer.api; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; + +import com.softlayer.api.Property.BooleanProperty; +import com.softlayer.api.Property.ByteArrayProperty; +import com.softlayer.api.Property.DateTimeProperty; +import com.softlayer.api.Property.NumberProperty; +import com.softlayer.api.Property.StringProperty; /** Object mask parameter. See http://sldn.softlayer.com/article/Object-Masks */ public class Mask { - private final Set localProperties = new HashSet(); + private final Map> localProperties = new HashMap>(); private final Map subMasks = new HashMap(); /** Clear out all previously masked objects and local properties */ @@ -20,8 +24,49 @@ private int getChildCount() { return localProperties.size() + subMasks.size(); } - protected void withLocalProperty(String localProperty) { - localProperties.add(localProperty); + protected BooleanProperty withBooleanProperty(String name) { + Property property = localProperties.get(name); + if (property == null) { + property = new BooleanProperty(name); + localProperties.put(name, property); + } + return (BooleanProperty) property; + } + + protected ByteArrayProperty withByteArrayProperty(String name) { + Property property = localProperties.get(name); + if (property == null) { + property = new ByteArrayProperty(name); + localProperties.put(name, property); + } + return (ByteArrayProperty) property; + } + + protected DateTimeProperty withDateTimeProperty(String name) { + Property property = localProperties.get(name); + if (property == null) { + property = new DateTimeProperty(name); + localProperties.put(name, property); + } + return (DateTimeProperty) property; + } + + protected NumberProperty withNumberProperty(String name) { + Property property = localProperties.get(name); + if (property == null) { + property = new NumberProperty(name); + localProperties.put(name, property); + } + return (NumberProperty) property; + } + + protected StringProperty withStringProperty(String name) { + Property property = localProperties.get(name); + if (property == null) { + property = new StringProperty(name); + localProperties.put(name, property); + } + return (StringProperty) property; } @SuppressWarnings("unchecked") @@ -50,7 +95,7 @@ public String toString() { /** Append this mask's string representation to the given builder and return it */ public StringBuilder toString(StringBuilder builder) { boolean first = true; - for (String localProperty : localProperties) { + for (String localProperty : localProperties.keySet()) { if (first) { first = false; } else { @@ -77,4 +122,23 @@ public StringBuilder toString(StringBuilder builder) { } return builder; } + + protected Map getFilterMap() { + Map map = new HashMap(); + // Sub masks first + for (Map.Entry subMask : subMasks.entrySet()) { + Map subMap = subMask.getValue().getFilterMap(); + if (!subMap.isEmpty()) { + map.put(subMask.getKey(), subMap); + } + } + // Now local properties + for (Map.Entry> localProperty : localProperties.entrySet()) { + Map localPropertyMap = localProperty.getValue().getFilterMap(); + if (!localPropertyMap.isEmpty()) { + map.put(localProperty.getKey(), localPropertyMap); + } + } + return map; + } } diff --git a/src/main/java/com/softlayer/api/Property.java b/src/main/java/com/softlayer/api/Property.java new file mode 100644 index 0000000..480d66e --- /dev/null +++ b/src/main/java/com/softlayer/api/Property.java @@ -0,0 +1,143 @@ +package com.softlayer.api; + +import com.softlayer.api.Filter.SimpleFilter; +import com.softlayer.api.Filter.SimpleOperation; + +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.Map; + +public abstract class Property { + public final String name; + private Filter filter; + + Property(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public Filter getFilter() { + return filter; + } + + public void setFilter(Filter filter) { + this.filter = filter; + } + + public void equalTo(T value) { + if (value == null) { + isNull(); + } else { + setFilter(new SimpleFilter(SimpleOperation.EQUAL_TO, value)); + } + } + + public void notEqualTo(T value) { + if (value == null) { + notNull(); + } else { + setFilter(new SimpleFilter(SimpleOperation.NOT_EQUAL_TO, value)); + } + } + + public void isNull() { + setFilter(new SimpleFilter(SimpleOperation.IS_NULL, null)); + } + + public void notNull() { + setFilter(new SimpleFilter(SimpleOperation.NOT_EQUAL_TO, null)); + } + + protected Map getFilterMap() { + if (getFilter() == null) { + return Collections.emptyMap(); + } + return getFilter().getFilterMap(); + } + + public static class BooleanProperty extends Property { + public BooleanProperty(String name) { + super(name); + } + } + + public static class ByteArrayProperty extends Property { + public ByteArrayProperty(String name) { + super(name); + } + + @Override + public void setFilter(Filter filter) { + throw new UnsupportedOperationException("Byte arrays do not support filters"); + } + } + + public static class DateTimeProperty extends Property { + protected DateTimeProperty(String name) { + super(name); + } + } + + public static class NumberProperty extends Property { + protected NumberProperty(String name) { + super(name); + } + + public void greaterThan(Number value) { + setFilter(new SimpleFilter(SimpleOperation.GREATER_THAN, value)); + } + + public void greaterOrEqualTo(Number value) { + setFilter(new SimpleFilter(SimpleOperation.GREATER_OR_EQUAL_TO, value)); + } + + public void lessThan(Number value) { + setFilter(new SimpleFilter(SimpleOperation.LESS_THAN, value)); + } + + public void lessOrEqualTo(Number value) { + setFilter(new SimpleFilter(SimpleOperation.LESS_OR_EQUAL_TO, value)); + } + } + + public static class StringProperty extends Property { + public StringProperty(String name) { + super(name); + } + + public void startsWith(String value) { + setFilter(new SimpleFilter(SimpleOperation.STARTS_WITH, value)); + } + + public void notStartsWith(String value) { + setFilter(new SimpleFilter(SimpleOperation.NOT_STARTS_WITH, value)); + } + + public void endsWith(String value) { + setFilter(new SimpleFilter(SimpleOperation.ENDS_WITH, value)); + } + + public void notEndsWith(String value) { + setFilter(new SimpleFilter(SimpleOperation.NOT_ENDS_WITH, value)); + } + + public void contains(String value) { + setFilter(new SimpleFilter(SimpleOperation.CONTAINS, value)); + } + + public void notContains(String value) { + setFilter(new SimpleFilter(SimpleOperation.NOT_CONTAINS, value)); + } + + public void equalToIgnoreCase(String value) { + setFilter(new SimpleFilter(SimpleOperation.EQUAL_TO_IGNORE_CASE, value)); + } + + public void notEqualToIgnoreCase(String value) { + setFilter(new SimpleFilter(SimpleOperation.NOT_EQUAL_TO_IGNORE_CASE, value)); + } + } +} diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index c945442..46c4b72 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -137,8 +137,9 @@ protected String getHttpMethodFromMethodName(String methodName) { } protected String getFullUrl(String serviceName, String methodName, String id, - ResultLimit resultLimit, String maskString) { - StringBuilder url = new StringBuilder(baseUrl + serviceName); + ResultLimit resultLimit, String maskString, String filterString) { + StringBuilder url = new StringBuilder(baseUrl); + url.append(serviceName); // ID present? add it if (id != null) { url.append('/').append(id); @@ -152,17 +153,32 @@ protected String getFullUrl(String serviceName, String methodName, String id, url.append('/').append(methodName); } url.append(".json"); + + StringBuilder urlParameters = new StringBuilder(); + if (resultLimit != null) { - url.append("?resultLimit=").append(resultLimit.offset).append(',').append(resultLimit.limit); + urlParameters.append("?resultLimit=") + .append(resultLimit.offset) + .append(',') + .append(resultLimit.limit); } if (maskString != null && !maskString.isEmpty()) { - url.append(resultLimit == null ? '?' : '&'); + urlParameters.append(urlParameters.length() == 0 ? '?' : '&'); + try { + urlParameters.append("objectMask=").append(URLEncoder.encode(maskString, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + if (filterString != null && !filterString.isEmpty()) { + urlParameters.append(urlParameters.length() == 0 ? '?' : '&'); try { - url.append("objectMask=").append(URLEncoder.encode(maskString, "UTF-8")); + urlParameters.append("objectFilter=").append(URLEncoder.encode(filterString, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } + url.append(urlParameters); return url.toString(); } @@ -194,11 +210,13 @@ public S createService(Class serviceClass, String id) { } class ServiceProxy implements InvocationHandler { - + final Class serviceClass; final String id; Mask mask; String maskString; + Mask filter; + String filterString; ResultLimit resultLimit; Integer lastResponseTotalItemCount; @@ -206,6 +224,18 @@ public ServiceProxy(Class serviceClass, String id) { this.serviceClass = serviceClass; this.id = id; } + + protected String getFilterString() throws UnsupportedEncodingException { + if (filter != null) { + Map filterMap = filter.getFilterMap(); + if (!filterMap.isEmpty()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getJsonMarshallerFactory().getJsonMarshaller().toJson(filterMap, out); + return out.toString("UTF-8"); + } + } + return filterString; + } public void logRequestAndWriteBody(HttpClient client, String httpMethod, String url, Object[] args) { if (loggingEnabled) { @@ -285,7 +315,11 @@ public Object invokeService(Method method, final Object[] args) throws Throwable final String httpMethod = getHttpMethodFromMethodName(methodName); String methodId = methodInfo.instanceRequired() ? this.id : null; final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), - methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); + methodName, + methodId, + resultLimit, + mask == null ? maskString : mask.getMask(), + getFilterString()); final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); // Invoke with response @@ -324,7 +358,11 @@ public Object invokeServiceAsync(final Method asyncMethod, final Object[] args) final String httpMethod = getHttpMethodFromMethodName(methodName); String methodId = methodInfo.instanceRequired() ? this.id : null; final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), - methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); + methodName, + methodId, + resultLimit, + mask == null ? maskString : mask.getMask(), + getFilterString()); final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); Callable setupBody = new Callable() { @@ -422,6 +460,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl ServiceProxy asyncProxy = new ServiceProxy(serviceClass, id); asyncProxy.mask = mask; asyncProxy.maskString = maskString; + asyncProxy.filter = filter; + asyncProxy.filterString = filterString; asyncProxy.resultLimit = resultLimit; return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { method.getReturnType() }, asyncProxy); @@ -452,6 +492,33 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl mask = null; maskString = null; return null; + } else if ("withNewFilter".equals(method.getName()) && noParams) { + filter = (Mask) method.getReturnType().newInstance(); + filterString = null; + return filter; + } else if ("withFilter".equals(method.getName()) && noParams) { + if (filter == null) { + filter = (Mask) method.getReturnType().newInstance(); + filterString = null; + } + return filter; + } else if ("setFilter".equals(method.getName()) &&args != null + && args.length == 1 && args[0] instanceof String) { + filter = null; + filterString = args[0].toString(); + return null; + } else if ("setFilter".equals(method.getName()) && args != null + && args.length == 1 && args[0] instanceof Mask) { + filter = (Mask) args[0]; + filterString = null; + return null; + } else if ("setFilter".equals(method.getName()) && args != null + && args.length == 1 && args[0] == null) { + throw new IllegalArgumentException("Cannot set null filter. Use clearFilter to remove a filter."); + } else if ("clearFilter".equals(method.getName())) { + filter = null; + filterString = null; + return null; } else if ("setResultLimit".equals(method.getName()) && method.getDeclaringClass() == ResultLimitable.class) { resultLimit = (ResultLimit) args[0]; diff --git a/src/main/java/com/softlayer/api/Service.java b/src/main/java/com/softlayer/api/Service.java index db71a62..85b8e0a 100644 --- a/src/main/java/com/softlayer/api/Service.java +++ b/src/main/java/com/softlayer/api/Service.java @@ -1,7 +1,7 @@ package com.softlayer.api; /** Interface extended by individual service interfaces on types */ -public interface Service extends Maskable, ResultLimitable { +public interface Service extends Maskable, Filterable, ResultLimitable { /** Get an async version of this service */ public ServiceAsync asAsync(); diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java index a141cb7..93a7973 100644 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ b/src/test/java/com/softlayer/api/RestApiClientTest.java @@ -46,29 +46,29 @@ public void testGetHttpMethodFromMethodName() { public void testGetFullUrl() { RestApiClient client = new RestApiClient("http://example.com/"); assertEquals("http://example.com/SomeService/someMethod.json", - client.getFullUrl("SomeService", "someMethod", null, null, null)); + client.getFullUrl("SomeService", "someMethod", null, null, null, null)); assertEquals("http://example.com/SomeService/1234/someMethod.json", - client.getFullUrl("SomeService", "someMethod", "1234", null, null)); + client.getFullUrl("SomeService", "someMethod", "1234", null, null, null)); assertEquals("http://example.com/SomeService/1234/someMethod.json?resultLimit=5,6", - client.getFullUrl("SomeService", "someMethod", "1234", new ResultLimit(5, 6), null)); + client.getFullUrl("SomeService", "someMethod", "1234", new ResultLimit(5, 6), null, null)); assertEquals("http://example.com/SomeService/1234/someMethod.json?resultLimit=5,6&objectMask=someMask%26%26", - client.getFullUrl("SomeService", "someMethod", "1234", new ResultLimit(5, 6), "someMask&&")); + client.getFullUrl("SomeService", "someMethod", "1234", new ResultLimit(5, 6), "someMask&&", null)); assertEquals("http://example.com/SomeService/1234/someMethod.json?objectMask=someMask%26%26", - client.getFullUrl("SomeService", "someMethod", "1234", null, "someMask&&")); + client.getFullUrl("SomeService", "someMethod", "1234", null, "someMask&&", null)); assertEquals("http://example.com/SomeService/Something.json", - client.getFullUrl("SomeService", "getSomething", null, null, null)); + client.getFullUrl("SomeService", "getSomething", null, null, null, null)); assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "getObject", null, null, null)); + client.getFullUrl("SomeService", "getObject", null, null, null, null)); assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "deleteObject", null, null, null)); + client.getFullUrl("SomeService", "deleteObject", null, null, null, null)); assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "createObject", null, null, null)); + client.getFullUrl("SomeService", "createObject", null, null, null, null)); assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "createObjects", null, null, null)); + client.getFullUrl("SomeService", "createObjects", null, null, null, null)); assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "editObject", null, null, null)); + client.getFullUrl("SomeService", "editObject", null, null, null, null)); assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "editObjects", null, null, null)); + client.getFullUrl("SomeService", "editObjects", null, null, null, null)); } private String withOutputCaptured(Callable closure) throws Exception { @@ -311,7 +311,8 @@ public void testWithMask() throws Exception { TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); - service.withMask().foo().child().date(); + service.withMask().foo(); + service.withMask().child().date(); service.withMask().child().baz(); assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" @@ -329,7 +330,8 @@ public void testSetObjectMask() throws Exception { entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); TestEntity.Mask mask = new TestEntity.Mask(); - mask.foo().child().date(); + mask.foo(); + mask.child().date(); mask.child().baz(); service.setMask(mask); assertEquals("some response", service.doSomethingStatic(123L, entity)); diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java index 66411e2..2d62c09 100644 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ b/src/test/java/com/softlayer/api/service/TestEntity.java @@ -6,6 +6,9 @@ import java.util.concurrent.Future; import com.softlayer.api.ApiClient; +import com.softlayer.api.Property; +import com.softlayer.api.Property.DateTimeProperty; +import com.softlayer.api.Property.StringProperty; import com.softlayer.api.ResponseHandler; import com.softlayer.api.annotation.ApiMethod; import com.softlayer.api.annotation.ApiProperty; @@ -161,19 +164,16 @@ public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { public static class Mask extends Entity.Mask { - public Mask foo() { - withLocalProperty("bar"); - return this; + public StringProperty foo() { + return withStringProperty("foo"); } - public Mask baz() { - withLocalProperty("baz"); - return this; + public StringProperty baz() { + return withStringProperty("baz"); } - public Mask date() { - withLocalProperty("date"); - return this; + public DateTimeProperty date() { + return withDateTimeProperty("date"); } public Mask child() { From be925a740e2f8bc2448bfbd0e8b0c877e57e720d Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 23 Nov 2020 12:06:22 -0600 Subject: [PATCH 2/2] Fixing up unit tests --- src/test/java/com/softlayer/api/MaskTest.java | 36 +++++------- .../com/softlayer/api/RestApiClientTest.java | 57 +------------------ .../com/softlayer/api/service/TestThing.java | 21 ++++--- 3 files changed, 30 insertions(+), 84 deletions(-) diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java index 928b0be..310546f 100644 --- a/src/test/java/com/softlayer/api/MaskTest.java +++ b/src/test/java/com/softlayer/api/MaskTest.java @@ -4,10 +4,10 @@ import java.net.URLEncoder; import java.util.Collections; +import java.util.List; import org.junit.Ignore; import org.junit.Test; - import com.softlayer.api.http.FakeHttpClientFactory; import com.softlayer.api.json.GsonJsonMarshallerFactoryTest; import com.softlayer.api.service.TestEntity; @@ -20,63 +20,57 @@ public class MaskTest { @Test public void testWithMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); + Collections.>emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); - service.withMask().foo().child().date(); + service.withMask().foo(); + service.withMask().child().date(); service.withMask().child().baz(); - assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); assertTrue(http.invokeSyncCalled); } - + @Test public void testSetObjectMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); + Collections.>emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); TestEntity.Mask mask = new TestEntity.Mask(); - mask.foo().child().date(); + mask.foo(); + mask.child().date(); mask.child().baz(); service.setMask(mask); - assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); assertTrue(http.invokeSyncCalled); } - + @Test - public void testSetStringMask() { + public void testSetStringMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); + Collections.>emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); service.setMask("yay-a-mask"); - assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + "?objectMask=yay-a-mask", http.fullUrl); assertTrue(http.invokeSyncCalled); } + @Test(expected = IllegalArgumentException.class) public void testMaskMustNotBeNull() { diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java index eb3b66e..3cefaf1 100644 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ b/src/test/java/com/softlayer/api/RestApiClientTest.java @@ -61,7 +61,8 @@ public void testGetFullUrl() { client.getFullUrl("SomeService", "deleteObject", null, null, null, null)); assertEquals("http://example.com/SomeService.json", client.getFullUrl("SomeService", "createObject", null, null, null, null)); - assertEquals("http://example.com/SomeService.json", + // createObjects is supposed to be implicit, but is required + assertEquals("http://example.com/SomeService/createObjects.json", client.getFullUrl("SomeService", "createObjects", null, null, null, null)); assertEquals("http://example.com/SomeService.json", client.getFullUrl("SomeService", "editObject", null, null, null, null)); @@ -313,59 +314,7 @@ public void testDifferentMethodName() throws Exception { } - @Test - public void testWithMask() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); - client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.Service service = TestEntity.service(client); - service.withMask().foo(); - service.withMask().child().date(); - service.withMask().child().baz(); - assertEquals("some response", service.doSomethingStatic(123L, entity)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); - assertTrue(http.invokeSyncCalled); - } - - @Test - public void testSetObjectMask() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); - client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.Service service = TestEntity.service(client); - TestEntity.Mask mask = new TestEntity.Mask(); - mask.foo(); - mask.child().date(); - mask.child().baz(); - service.setMask(mask); - assertEquals("some response", service.doSomethingStatic(123L, entity)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); - assertTrue(http.invokeSyncCalled); - } - - @Test - public void testSetStringMask() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); - client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.Service service = TestEntity.service(client); - service.setMask("yay-a-mask"); - assertEquals("some response", service.doSomethingStatic(123L, entity)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=yay-a-mask", http.fullUrl); - assertTrue(http.invokeSyncCalled); - } + @Test public void testWithResultLimit() throws Exception { diff --git a/src/test/java/com/softlayer/api/service/TestThing.java b/src/test/java/com/softlayer/api/service/TestThing.java index 548b87c..cdb261d 100644 --- a/src/test/java/com/softlayer/api/service/TestThing.java +++ b/src/test/java/com/softlayer/api/service/TestThing.java @@ -5,6 +5,8 @@ import java.util.concurrent.Future; import com.softlayer.api.ApiClient; +import com.softlayer.api.Property; +import com.softlayer.api.Property.StringProperty; import com.softlayer.api.ResponseHandler; import com.softlayer.api.ResultLimit; import com.softlayer.api.annotation.ApiMethod; @@ -117,19 +119,20 @@ public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { public static class Mask extends Entity.Mask { - public Mask id() { - withLocalProperty("id"); - return this; + public StringProperty id() { + // withLocalProperty("id"); + // return this + return withStringProperty("id"); } - public Mask first() { - withLocalProperty("first"); - return this; + public StringProperty first() { + // withStringProperty("first"); + return withStringProperty("first"); } - public Mask second() { - withLocalProperty("second"); - return this; + public StringProperty second() { + // withStringProperty("second"); + return withStringProperty("second"); } public Mask testEntity() {