diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java index 9274631e87..2d685c8835 100644 --- a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java @@ -26,6 +26,7 @@ import org.jclouds.cloudstack.features.EventAsyncClient; import org.jclouds.cloudstack.features.FirewallAsyncClient; import org.jclouds.cloudstack.features.GuestOSAsyncClient; import org.jclouds.cloudstack.features.HypervisorAsyncClient; +import org.jclouds.cloudstack.features.IsoAsyncClient; import org.jclouds.cloudstack.features.LimitAsyncClient; import org.jclouds.cloudstack.features.LoadBalancerAsyncClient; import org.jclouds.cloudstack.features.NATAsyncClient; @@ -164,4 +165,9 @@ public interface CloudStackAsyncClient { @Delegate LimitAsyncClient getLimitClient(); + /** + * Provides asynchronous access to ISOs + */ + @Delegate + IsoAsyncClient getIsoClient(); } diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java index a2b99ddc54..61fe5cc406 100644 --- a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java @@ -28,6 +28,7 @@ import org.jclouds.cloudstack.features.EventClient; import org.jclouds.cloudstack.features.FirewallClient; import org.jclouds.cloudstack.features.GuestOSClient; import org.jclouds.cloudstack.features.HypervisorClient; +import org.jclouds.cloudstack.features.IsoClient; import org.jclouds.cloudstack.features.LimitClient; import org.jclouds.cloudstack.features.LoadBalancerClient; import org.jclouds.cloudstack.features.NATClient; @@ -167,4 +168,9 @@ public interface CloudStackClient { @Delegate LimitClient getLimitClient(); + /** + * Provides synchronous access to ISOs + */ + @Delegate + IsoClient getIsoClient(); } diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java index a6ec0dd18d..b6f63218a8 100644 --- a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java @@ -39,6 +39,8 @@ import org.jclouds.cloudstack.features.GuestOSAsyncClient; import org.jclouds.cloudstack.features.GuestOSClient; import org.jclouds.cloudstack.features.HypervisorAsyncClient; import org.jclouds.cloudstack.features.HypervisorClient; +import org.jclouds.cloudstack.features.IsoAsyncClient; +import org.jclouds.cloudstack.features.IsoClient; import org.jclouds.cloudstack.features.LimitAsyncClient; import org.jclouds.cloudstack.features.LimitClient; import org.jclouds.cloudstack.features.LoadBalancerAsyncClient; @@ -101,6 +103,7 @@ public class CloudStackRestClientModule extends RestClientModule { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private long id; + private String account; + private long accountId; + private boolean bootable; + private String checksum; + private Date created; + private boolean crossZones; + private String displayText; + private String domain; + private long domainid; + private String format; + private long hostId; + private String hostName; + private String hypervisor; + private boolean isExtractable; + private boolean isFeatured; + private boolean isPublic; + private boolean isReady; + private long jobId; + private String jobStatus; + private String name; + private long osTypeId; + private String osTypeName; + private boolean passwordEnabled; + private Date removed; + private long size; + private long sourceTemplateId; + private String status; + private String templateTag; + private String templateType; + private long zoneId; + private String zoneName; + + /** + * @param id the template ID + */ + public Builder id(long id) { + this.id = id; + return this; + } + + /** + * @param account the account name to which the template belongs + */ + public Builder account(String account) { + this.account = account; + return this; + } + + /** + * @param accountId the account id to which the template belongs + */ + public Builder accountId(long accountId) { + this.accountId = accountId; + return this; + } + + /** + * @param bootable true if the ISO is bootable, false otherwise + */ + public Builder bootable(boolean bootable) { + this.bootable = bootable; + return this; + } + + /** + * @param checksum checksum of the template + */ + public Builder checksum(String checksum) { + this.checksum = checksum; + return this; + } + + /** + * @param created the date this template was created + */ + public Builder created(Date created) { + this.created = created; + return this; + } + + /** + * @param crossZones true if the template is managed across all Zones, false otherwise + */ + public Builder crossZones(boolean crossZones) { + this.crossZones = crossZones; + return this; + } + + /** + * @param displayText the template display text + */ + public Builder displayText(String displayText) { + this.displayText = displayText; + return this; + } + + /** + * @param domain the name of the domain to which the template belongs + */ + public Builder domain(String domain) { + this.domain = domain; + return this; + } + + /** + * @param domainid the ID of the domain to which the template belongs + */ + public Builder domainid(long domainid) { + this.domainid = domainid; + return this; + } + + /** + * @param format the format of the template. + */ + public Builder format(String format) { + this.format = format; + return this; + } + + /** + * @param hostId the ID of the secondary storage host for the template + */ + public Builder hostId(long hostId) { + this.hostId = hostId; + return this; + } + + /** + * @param hostName the name of the secondary storage host for the template + */ + public Builder hostName(String hostName) { + this.hostName = hostName; + return this; + } + + /** + * @param hypervisor the hypervisor on which the template runs + */ + public Builder hypervisor(String hypervisor) { + this.hypervisor = hypervisor; + return this; + } + + /** + * @param isExtractable true if the template is extractable, false otherwise + */ + public Builder isExtractable(boolean isExtractable) { + this.isExtractable = isExtractable; + return this; + } + + /** + * @param isFeatured true if this template is a featured template, false otherwise + */ + public Builder isFeatured(boolean isFeatured) { + this.isFeatured = isFeatured; + return this; + } + + /** + * @param isPublic true if this template is a public template, false otherwise + */ + public Builder isPublic(boolean isPublic) { + this.isPublic = isPublic; + return this; + } + + /** + * @param isReady true if the template is ready to be deployed from, false otherwise. + */ + public Builder isReady(boolean isReady) { + this.isReady = isReady; + return this; + } + + /** + * @param jobId shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the template + */ + public Builder jobId(long jobId) { + this.jobId = jobId; + return this; + } + + /** + * @param jobStatus shows the current pending asynchronous job status + */ + public Builder jobStatus(String jobStatus) { + this.jobStatus = jobStatus; + return this; + } + + /** + * @param name the template name + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * @param osTypeId the ID of the OS type for this template. + */ + public Builder osTypeId(long osTypeId) { + this.osTypeId = osTypeId; + return this; + } + + /** + * @param osTypeName the name of the OS type for this template. + */ + public Builder osTypeName(String osTypeName) { + this.osTypeName = osTypeName; + return this; + } + + /** + * @param passwordEnabled true if the reset password feature is enabled, false otherwise + */ + public Builder passwordEnabled(boolean passwordEnabled) { + this.passwordEnabled = passwordEnabled; + return this; + } + + /** + * @param removed the date this template was removed + */ + public Builder removed(Date removed) { + this.removed = removed; + return this; + } + + /** + * @param size the size of the template + */ + public Builder size(long size) { + this.size = size; + return this; + } + + /** + * @param sourceTemplateId the template ID of the parent template if present + */ + public Builder sourceTemplateId(long sourceTemplateId) { + this.sourceTemplateId = sourceTemplateId; + return this; + } + + /** + * @param status the status of the template + */ + public Builder status(String status) { + this.status = status; + return this; + } + + /** + * @param templateTag the tag of this template + */ + public Builder templateTag(String templateTag) { + this.templateTag = templateTag; + return this; + } + + /** + * @param templateType the type of the template + */ + public Builder templateType(String templateType) { + this.templateType = templateType; + return this; + } + + /** + * @param zoneId the ID of the zone for this template + */ + public Builder zoneId(long zoneId) { + this.zoneId = zoneId; + return this; + } + + /** + * @param zoneName the name of the zone for this template + */ + public Builder zoneName(String zoneName) { + this.zoneName = zoneName; + return this; + } + + } + + private long id; + private String account; + @SerializedName("accountid") + private long accountId; + private boolean bootable; + private String checksum; + private Date created; + private boolean crossZones; + @SerializedName("displaytext") + private String displayText; + private String domain; + @SerializedName("domainId") + private long domainid; + private String format; + @SerializedName("hostid") + private long hostId; + @SerializedName("hostname") + private String hostName; + @SerializedName("") + private String hypervisor; + @SerializedName("isextractable") + private boolean isExtractable; + @SerializedName("isfeatured") + private boolean isFeatured; + @SerializedName("ispublic") + private boolean isPublic; + @SerializedName("isready") + private boolean isReady; + @SerializedName("jobid") + private long jobId; + @SerializedName("jobstatus") + private String jobStatus; + private String name; + @SerializedName("ostypeid") + private long osTypeId; + @SerializedName("ostypename") + private String osTypeName; + @SerializedName("passwordenabled") + private boolean passwordEnabled; + private Date removed; + private long size; + @SerializedName("sourcetemplateid") + private long sourceTemplateId; + private String status; + @SerializedName("templatetag") + private String templateTag; + @SerializedName("templatetype") + private String templateType; + @SerializedName("zoneid") + private long zoneId; + @SerializedName("zonename") + private String zoneName; + + /** + * present only for serializer + */ + Iso() { + } + + /** + * @return the template ID + */ + public long getId() { + return id; + } + + /** + * @return the account name to which the template belongs + */ + public String getAccount() { + return account; + } + + /** + * @return the account id to which the template belongs + */ + public long getAccountId() { + return accountId; + } + + /** + * @return true if the ISO is bootable, false otherwise + */ + public boolean getBootable() { + return bootable; + } + + /** + * @return checksum of the template + */ + public String getChecksum() { + return checksum; + } + + /** + * @return the date this template was created + */ + public Date getCreated() { + return created; + } + + /** + * @return true if the template is managed across all Zones, false otherwise + */ + public boolean getCrossZones() { + return crossZones; + } + + /** + * @return the template display text + */ + public String getDisplayText() { + return displayText; + } + + /** + * @return the name of the domain to which the template belongs + */ + public String getDomain() { + return domain; + } + + /** + * @return the ID of the domain to which the template belongs + */ + public long getDomainid() { + return domainid; + } + + /** + * @return the format of the template. + */ + public String getFormat() { + return format; + } + + /** + * @return the ID of the secondary storage host for the template + */ + public long getHostId() { + return hostId; + } + + /** + * @return the name of the secondary storage host for the template + */ + public String getHostName() { + return hostName; + } + + /** + * @return the hypervisor on which the template runs + */ + public String getHypervisor() { + return hypervisor; + } + + /** + * @return true if the template is extractable, false otherwise + */ + public boolean getIsExtractable() { + return isExtractable; + } + + /** + * @return true if this template is a featured template, false otherwise + */ + public boolean getIsFeatured() { + return isFeatured; + } + + /** + * @return true if this template is a public template, false otherwise + */ + public boolean getIsPublic() { + return isPublic; + } + + /** + * @return true if the template is ready to be deployed from, false otherwise. + */ + public boolean getIsReady() { + return isReady; + } + + /** + * @return shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the template + */ + public long getJobId() { + return jobId; + } + + /** + * @return shows the current pending asynchronous job status + */ + public String getJobStatus() { + return jobStatus; + } + + /** + * @return the template name + */ + public String getName() { + return name; + } + + /** + * @return the ID of the OS type for this template. + */ + public long getOsTypeId() { + return osTypeId; + } + + /** + * @return the name of the OS type for this template. + */ + public String getOsTypeName() { + return osTypeName; + } + + /** + * @return true if the reset password feature is enabled, false otherwise + */ + public boolean getPasswordEnabled() { + return passwordEnabled; + } + + /** + * @return the date this template was removed + */ + public Date getRemoved() { + return removed; + } + + /** + * @return the size of the template + */ + public long getSize() { + return size; + } + + /** + * @return the template ID of the parent template if present + */ + public long getSourceTemplateId() { + return sourceTemplateId; + } + + /** + * @return the status of the template + */ + public String getStatus() { + return status; + } + + /** + * @return the tag of this template + */ + public String getTemplateTag() { + return templateTag; + } + + /** + * @return the type of the template + */ + public String getTemplateType() { + return templateType; + } + + /** + * @return the ID of the zone for this template + */ + public long getZoneId() { + return zoneId; + } + + /** + * @return the name of the zone for this template + */ + public String getZoneName() { + return zoneName; + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public int hashCode() { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public String toString() { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public int compareTo(Iso other) { + throw new RuntimeException("FIXME: Implement me"); + } + + public enum IsoFilter { + + featured, self, self_executable, executable, community, UNRECOGNIZED; + + public static IsoFilter fromValue(String format) { + try { + return valueOf(checkNotNull(format, "format")); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } +} diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/IsoExtraction.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/IsoExtraction.java new file mode 100644 index 0000000000..8b3d4f49f8 --- /dev/null +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/IsoExtraction.java @@ -0,0 +1,292 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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. + */ +package org.jclouds.cloudstack.domain; + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; + +/** + * @author Richard Downer + */ +public class IsoExtraction implements Comparable { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private long id; + private long accountId; + private Date created; + private long extractId; + private ExtractMode extractMode; + private String name; + private String state; + private String status; + private String storageType; + private int uploadPercentage; + private String url; + private long zoneId; + private String zoneName; + + /** + * @param id the id of extracted object + */ + public Builder id(long id) { + this.id = id; + return this; + } + + /** + * @param accountId the account id to which the extracted object belongs + */ + public Builder accountId(long accountId) { + this.accountId = accountId; + return this; + } + + /** + * @param created the time and date the object was created + */ + public Builder created(Date created) { + this.created = created; + return this; + } + + /** + * @param extractId the upload id of extracted object + */ + public Builder extractId(long extractId) { + this.extractId = extractId; + return this; + } + + /** + * @param extractMode the mode of extraction - upload or download + */ + public Builder extractMode(ExtractMode extractMode) { + this.extractMode = extractMode; + return this; + } + + /** + * @param name the name of the extracted object + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * @param state the state of the extracted object + */ + public Builder state(String state) { + this.state = state; + return this; + } + + /** + * @param status the status of the extraction + */ + public Builder status(String status) { + this.status = status; + return this; + } + + /** + * @param storageType type of the storage + */ + public Builder storageType(String storageType) { + this.storageType = storageType; + return this; + } + + /** + * @param uploadPercentage the percentage of the entity uploaded to the specified location + */ + public Builder uploadPercentage(int uploadPercentage) { + this.uploadPercentage = uploadPercentage; + return this; + } + + /** + * @param url if mode = upload then url of the uploaded entity. if mode = download the url from which the entity can be downloaded + */ + public Builder url(String url) { + this.url = url; + return this; + } + + /** + * @param zoneId zone ID the object was extracted from + */ + public Builder zoneId(long zoneId) { + this.zoneId = zoneId; + return this; + } + + /** + * @param zoneName zone name the object was extracted from + */ + public Builder zoneName(String zoneName) { + this.zoneName = zoneName; + return this; + } + + } + + private long id; + @SerializedName("accountid") + private long accountId; + private Date created; + private long extractId; + private ExtractMode extractMode; + private String name; + private String state; + private String status; + @SerializedName("storagetype") + private String storageType; + @SerializedName("uploadpercentage") + private int uploadPercentage; + private String url; + @SerializedName("zoneid") + private long zoneId; + @SerializedName("zonename") + private String zoneName; + + /** + * present only for serializer + */ + IsoExtraction() { + } + + /** + * @return the id of extracted object + */ + public long getId() { + return id; + } + + /** + * @return the account id to which the extracted object belongs + */ + public long getAccountId() { + return accountId; + } + + /** + * @return the time and date the object was created + */ + public Date getCreated() { + return created; + } + + /** + * @return the upload id of extracted object + */ + public long getExtractId() { + return extractId; + } + + /** + * @return the mode of extraction - upload or download + */ + public ExtractMode getExtractMode() { + return extractMode; + } + + /** + * @return the name of the extracted object + */ + public String getName() { + return name; + } + + /** + * @return the state of the extracted object + */ + public String getState() { + return state; + } + + /** + * @return the status of the extraction + */ + public String getStatus() { + return status; + } + + /** + * @return type of the storage + */ + public String getStorageType() { + return storageType; + } + + /** + * @return the percentage of the entity uploaded to the specified location + */ + public int getUploadPercentage() { + return uploadPercentage; + } + + /** + * @return if mode = upload then url of the uploaded entity. if mode = download the url from which the entity can be downloaded + */ + public String getUrl() { + return url; + } + + /** + * @return zone ID the object was extracted from + */ + public long getZoneId() { + return zoneId; + } + + /** + * @return zone name the object was extracted from + */ + public String getZoneName() { + return zoneName; + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public int hashCode() { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public String toString() { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public int compareTo(IsoExtraction other) { + throw new RuntimeException("FIXME: Implement me"); + } + +} diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/IsoPermissions.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/IsoPermissions.java new file mode 100644 index 0000000000..81c0d3b5b3 --- /dev/null +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/IsoPermissions.java @@ -0,0 +1,134 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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. + */ +package org.jclouds.cloudstack.domain; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Richard Downer + */ +public class IsoPermissions implements Comparable { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private long id; + private String account; + private long domainId; + private boolean isPublic; + + /** + * @param id the template ID + */ + public Builder id(long id) { + this.id = id; + return this; + } + + /** + * @param account the list of accounts the template is available for + */ + public Builder account(String account) { + this.account = account; + return this; + } + + /** + * @param domainId the ID of the domain to which the template belongs + */ + public Builder domainId(long domainId) { + this.domainId = domainId; + return this; + } + + /** + * @param isPublic true if this template is a public template, false otherwise + */ + public Builder isPublic(boolean isPublic) { + this.isPublic = isPublic; + return this; + } + + } + + private long id; + private String account; + @SerializedName("domainid") + private long domainId; + @SerializedName("ispublic") + private boolean isPublic; + + /** + * present only for serializer + */ + IsoPermissions() { + } + + /** + * @return the template ID + */ + public long getId() { + return id; + } + + /** + * @return the list of accounts the template is available for + */ + public String getAccount() { + return account; + } + + /** + * @return the ID of the domain to which the template belongs + */ + public long getDomainId() { + return domainId; + } + + /** + * @return true if this template is a public template, false otherwise + */ + public boolean getIsPublic() { + return isPublic; + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public int hashCode() { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public String toString() { + throw new RuntimeException("FIXME: Implement me"); + } + + @Override + public int compareTo(IsoPermissions other) { + throw new RuntimeException("FIXME: Implement me"); + } + +} diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/PermissionOperation.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/PermissionOperation.java new file mode 100644 index 0000000000..23d21d11ba --- /dev/null +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/PermissionOperation.java @@ -0,0 +1,37 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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. + */ +package org.jclouds.cloudstack.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Richard Downer + */ +public enum PermissionOperation { + + add, remove, reset, UNRECOGNIZED; + + public static PermissionOperation fromValue(String format) { + try { + return valueOf(checkNotNull(format, "format")); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } +} diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Template.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Template.java index 1605cb67e8..1834456db1 100644 --- a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Template.java +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Template.java @@ -233,19 +233,6 @@ public class Template implements Comparable