/* * Copyright (c) 2010 Red Hat, Inc. * * 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. */ package org.ovirt.engine.api.common.util; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import javax.ws.rs.Path; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import org.ovirt.engine.api.model.ActionsBuilder; import org.ovirt.engine.api.model.BaseResource; import org.ovirt.engine.api.model.CdRom; import org.ovirt.engine.api.model.Cluster; import org.ovirt.engine.api.model.DataCenter; import org.ovirt.engine.api.model.DetailedLink; import org.ovirt.engine.api.model.Disk; import org.ovirt.engine.api.model.Domain; import org.ovirt.engine.api.model.Event; import org.ovirt.engine.api.model.File; import org.ovirt.engine.api.model.GlusterVolume; import org.ovirt.engine.api.model.Group; import org.ovirt.engine.api.model.Host; import org.ovirt.engine.api.model.HostNIC; import org.ovirt.engine.api.model.Link; import org.ovirt.engine.api.model.LinkCapabilities; import org.ovirt.engine.api.model.NIC; import org.ovirt.engine.api.model.Network; import org.ovirt.engine.api.model.Parameter; import org.ovirt.engine.api.model.ParametersSet; import org.ovirt.engine.api.model.Permission; import org.ovirt.engine.api.model.Permit; import org.ovirt.engine.api.model.Request; import org.ovirt.engine.api.model.Role; import org.ovirt.engine.api.model.Snapshot; import org.ovirt.engine.api.model.Statistic; import org.ovirt.engine.api.model.Storage; import org.ovirt.engine.api.model.StorageDomain; import org.ovirt.engine.api.model.Tag; import org.ovirt.engine.api.model.Template; import org.ovirt.engine.api.model.Url; import org.ovirt.engine.api.model.User; import org.ovirt.engine.api.model.VM; import org.ovirt.engine.api.model.VmPool; import org.ovirt.engine.api.resource.AssignedNetworkResource; import org.ovirt.engine.api.resource.AssignedNetworksResource; import org.ovirt.engine.api.resource.AssignedPermissionsResource; import org.ovirt.engine.api.resource.AssignedRolesResource; import org.ovirt.engine.api.resource.AssignedTagResource; import org.ovirt.engine.api.resource.AssignedTagsResource; import org.ovirt.engine.api.resource.AttachedStorageDomainResource; import org.ovirt.engine.api.resource.AttachedStorageDomainsResource; import org.ovirt.engine.api.resource.ClusterResource; import org.ovirt.engine.api.resource.ClustersResource; import org.ovirt.engine.api.resource.DataCenterResource; import org.ovirt.engine.api.resource.DataCentersResource; import org.ovirt.engine.api.resource.DeviceResource; import org.ovirt.engine.api.resource.DevicesResource; import org.ovirt.engine.api.resource.DiskResource; import org.ovirt.engine.api.resource.DomainGroupResource; import org.ovirt.engine.api.resource.DomainGroupsResource; import org.ovirt.engine.api.resource.DomainResource; import org.ovirt.engine.api.resource.DomainUserResource; import org.ovirt.engine.api.resource.DomainUsersResource; import org.ovirt.engine.api.resource.DomainsResource; import org.ovirt.engine.api.resource.EventResource; import org.ovirt.engine.api.resource.EventsResource; import org.ovirt.engine.api.resource.FileResource; import org.ovirt.engine.api.resource.FilesResource; import org.ovirt.engine.api.resource.GlusterVolumeResource; import org.ovirt.engine.api.resource.GlusterVolumesResource; import org.ovirt.engine.api.resource.GroupResource; import org.ovirt.engine.api.resource.GroupsResource; import org.ovirt.engine.api.resource.HostNicResource; import org.ovirt.engine.api.resource.HostNicsResource; import org.ovirt.engine.api.resource.HostResource; import org.ovirt.engine.api.resource.HostStorageResource; import org.ovirt.engine.api.resource.HostsResource; import org.ovirt.engine.api.resource.NetworkResource; import org.ovirt.engine.api.resource.NetworksResource; import org.ovirt.engine.api.resource.NicResource; import org.ovirt.engine.api.resource.PermissionResource; import org.ovirt.engine.api.resource.PermitResource; import org.ovirt.engine.api.resource.PermitsResource; import org.ovirt.engine.api.resource.ReadOnlyDeviceResource; import org.ovirt.engine.api.resource.ReadOnlyDevicesResource; import org.ovirt.engine.api.resource.RoleResource; import org.ovirt.engine.api.resource.RolesResource; import org.ovirt.engine.api.resource.SnapshotResource; import org.ovirt.engine.api.resource.SnapshotsResource; import org.ovirt.engine.api.resource.StatisticResource; import org.ovirt.engine.api.resource.StatisticsResource; import org.ovirt.engine.api.resource.StorageDomainContentResource; import org.ovirt.engine.api.resource.StorageDomainContentsResource; import org.ovirt.engine.api.resource.StorageDomainResource; import org.ovirt.engine.api.resource.StorageDomainsResource; import org.ovirt.engine.api.resource.StorageResource; import org.ovirt.engine.api.resource.TagResource; import org.ovirt.engine.api.resource.TagsResource; import org.ovirt.engine.api.resource.TemplateResource; import org.ovirt.engine.api.resource.TemplatesResource; import org.ovirt.engine.api.resource.UserResource; import org.ovirt.engine.api.resource.UsersResource; import org.ovirt.engine.api.resource.VmPoolResource; import org.ovirt.engine.api.resource.VmPoolsResource; import org.ovirt.engine.api.resource.VmResource; import org.ovirt.engine.api.resource.VmsResource; /** * Contains a static addLinks() method which constructs any href attributes * and action links required by a representation. * * The information used to build links is obtained from the annotations on * the API definition interfaces. * For example, a link to a VM is the combination of the @Path attribute on * VmsResource and the VM id - i.e. '/restapi-definition/vms/{vm_id}' * * Resource collections which are a sub-resource of a parent collection * present a more difficult challenge. For example, the link to a VM tag * is the combination of the @Path attribute on VmsResource, the VM id, * the @Path attribute on VmResource.getTagsResource() and the tag id - * i.e. '/restapi-definition/vms/{vm_id}/tags/{tag_id}' * In most cases the parent type may be computed, but in exceptional * cases there are a number of equally valid candidates. Disambiguation * is achieved via an explicit suggestedParentType parameter. * * To be able to do this we need, for each collection, the collection type * (e.g. AssignedTagsResource), the resource type (e.g. AssignedTagResource) * and the parent model type (e.g. VM). The TYPES map below is populated * with this information for every resource type. */ public class LinkHelper { private static final String SEARCH_RELATION = "/search"; private static final String SEARCH_TEMPLATE = "?search={query}"; private static final String PARAMETER_TEMPLATE = "&%s={%s}"; /** * A constant representing the pseudo-parent of a top-level collection */ private static final Class<? extends BaseResource> NO_PARENT = BaseResource.class; /** * A map describing every possible collection */ private static ModelToCollectionsMap TYPES = new ModelToCollectionsMap(); static { ParentToCollectionMap map; map = new ParentToCollectionMap(ReadOnlyDeviceResource.class, ReadOnlyDevicesResource.class, Template.class); TYPES.put(CdRom.class, map); map = new ParentToCollectionMap(DeviceResource.class, DevicesResource.class, VM.class); TYPES.put(CdRom.class, map); map = new ParentToCollectionMap(ClusterResource.class, ClustersResource.class); TYPES.put(Cluster.class, map); map = new ParentToCollectionMap(DataCenterResource.class, DataCentersResource.class); TYPES.put(DataCenter.class, map); map = new ParentToCollectionMap(ReadOnlyDeviceResource.class, ReadOnlyDevicesResource.class, Template.class); TYPES.put(Disk.class, map); map = new ParentToCollectionMap(DeviceResource.class, DevicesResource.class, VM.class); TYPES.put(Disk.class, map); map = new ParentToCollectionMap(DiskResource.class, DevicesResource.class, VM.class); TYPES.put(Disk.class, map); map = new ParentToCollectionMap(HostResource.class, HostsResource.class); TYPES.put(Host.class, map); map = new ParentToCollectionMap(HostNicResource.class, HostNicsResource.class, Host.class); TYPES.put(HostNIC.class, map); map = new ParentToCollectionMap(FileResource.class, FilesResource.class, StorageDomain.class); TYPES.put(File.class, map); map = new ParentToCollectionMap(GroupResource.class, GroupsResource.class); map.add(DomainGroupResource.class, DomainGroupsResource.class, Domain.class); TYPES.put(Group.class, map); map = new ParentToCollectionMap(PermissionResource.class, AssignedPermissionsResource.class, User.class); map.add(PermissionResource.class, AssignedPermissionsResource.class, Group.class); map.add(PermissionResource.class, AssignedPermissionsResource.class, Role.class); map.add(PermissionResource.class, AssignedPermissionsResource.class, VM.class); TYPES.put(Permission.class, map); map = new ParentToCollectionMap(NetworkResource.class, NetworksResource.class); map.add(AssignedNetworkResource.class, AssignedNetworksResource.class, Cluster.class); TYPES.put(Network.class, map); map = new ParentToCollectionMap(DeviceResource.class, DevicesResource.class); map.add(DeviceResource.class, DevicesResource.class, VM.class); map.add(DeviceResource.class, DevicesResource.class, Template.class); map.add(ReadOnlyDeviceResource.class, ReadOnlyDevicesResource.class, Template.class); map.add(NicResource.class, DevicesResource.class, VM.class); TYPES.put(NIC.class, map); map = new ParentToCollectionMap(PermitResource.class, PermitsResource.class, Role.class); TYPES.put(Permit.class, map); map = new ParentToCollectionMap(RoleResource.class, RolesResource.class); map.add(RoleResource.class, AssignedRolesResource.class, User.class); TYPES.put(Role.class, map); map = new ParentToCollectionMap(SnapshotResource.class, SnapshotsResource.class, VM.class); TYPES.put(Snapshot.class, map); map = new ParentToCollectionMap(StorageResource.class, HostStorageResource.class, Host.class); TYPES.put(Storage.class, map); map = new ParentToCollectionMap(StorageDomainResource.class, StorageDomainsResource.class); map.add(AttachedStorageDomainResource.class, AttachedStorageDomainsResource.class, DataCenter.class); TYPES.put(StorageDomain.class, map); map = new ParentToCollectionMap(TagResource.class, TagsResource.class); map.add(AssignedTagResource.class, AssignedTagsResource.class, Host.class); map.add(AssignedTagResource.class, AssignedTagsResource.class, User.class); map.add(AssignedTagResource.class, AssignedTagsResource.class, VM.class); map.add(AssignedTagResource.class, AssignedTagsResource.class, Group.class); TYPES.put(Tag.class, map); map = new ParentToCollectionMap(TemplateResource.class, TemplatesResource.class); map.add(StorageDomainContentResource.class, StorageDomainContentsResource.class, StorageDomain.class); TYPES.put(Template.class, map); map = new ParentToCollectionMap(UserResource.class, UsersResource.class); map.add(DomainUserResource.class, DomainUsersResource.class, Domain.class); TYPES.put(User.class, map); map = new ParentToCollectionMap(VmResource.class, VmsResource.class); map.add(StorageDomainContentResource.class, StorageDomainContentsResource.class, StorageDomain.class); TYPES.put(VM.class, map); map = new ParentToCollectionMap(VmPoolResource.class, VmPoolsResource.class); TYPES.put(VmPool.class, map); map = new ParentToCollectionMap(EventResource.class, EventsResource.class); TYPES.put(Event.class, map); map = new ParentToCollectionMap(DomainResource.class, DomainsResource.class); TYPES.put(Domain.class, map); map = new ParentToCollectionMap(StatisticResource.class, StatisticsResource.class, Disk.class); map.add(StatisticResource.class, StatisticsResource.class, Host.class); map.add(StatisticResource.class, StatisticsResource.class, HostNIC.class); map.add(StatisticResource.class, StatisticsResource.class, NIC.class); map.add(StatisticResource.class, StatisticsResource.class, VM.class); TYPES.put(Statistic.class, map); map = new ParentToCollectionMap(GlusterVolumeResource.class, GlusterVolumesResource.class); map.add(GlusterVolumeResource.class, GlusterVolumesResource.class, Cluster.class); TYPES.put(GlusterVolume.class, map); } /** * Obtain the relative path to a top-level collection * * The path is simply the value of the @Path annotation on the * supplied collection resource type * * @param clz the collection resource type * @return the relative path to the collection */ private static String getPath(Class<?> clz) { Path pathAnnotation = clz.getAnnotation(Path.class); String path = pathAnnotation.value(); if (path.startsWith("/")) { path = path.substring(1); } return path; } /** * Obtain the relative path to a sub-collection * * The path is obtained from the @Path annotation on the method on @parent * which returns an instance of @clz * * A case-insensitive check for @type's name as a substring of the method * is also performed to guard against the case where @parent has multiple * methods returning instances of @clz, e.g. VmResource has multiple * methods return DevicesResource instances * * @param clz the collection resource type (e.g. AssignedTagsResource) * @param parent the parent resource type (e.g. VmResource) * @param type the model type (e.g. Tag) * @return the relative path to the collection */ private static String getPath(Class<?> clz, Class<?> parent, Class<?> type) { for (Method method : parent.getMethods()) { if (method.getName().startsWith("get") && clz.isAssignableFrom(method.getReturnType()) && isPluralResourceGetter(method.getName(), type.getSimpleName())) { Path pathAnnotation = method.getAnnotation(Path.class); return pathAnnotation.value(); } } return null; } private static boolean isPluralResourceGetter(String method, String type) { method = method.toLowerCase(); type = type.toLowerCase(); method = chopStart(method, "get"); method = chopEnd(method, "resource"); method = chopEnd(method, "s"); if (type.endsWith("y")) { method = chopEnd(method, "ie"); type = chopEnd(type, "y"); } return method.contains(type); } private static String chopStart(String str, String chop) { if (str.startsWith(chop)) { return str.substring(chop.length()); } else { return str; } } private static String chopEnd(String str, String chop) { if (str.endsWith(chop)) { return str.substring(0, str.length() - chop.length()); } else { return str; } } /** * Obtain a set of inline BaseResource objects from @obj * * i.e. return the value of any properties on @obj which are a * sub-type of BaseResource * * @param obj the object to check * @return a list of any inline BaseResource objects */ private static List<BaseResource> getInlineResources(Object obj) { ArrayList<BaseResource> ret = new ArrayList<BaseResource>(); for (Method method : obj.getClass().getMethods()) { if (method.getName().startsWith("get") && BaseResource.class.isAssignableFrom(method.getReturnType())) { try { BaseResource inline = (BaseResource)method.invoke(obj); if (inline != null) { ret.add(inline); } } catch (Exception e) { // invocation target exception should not occur on simple getter } } } return ret; } /** * Unset the property on @model of type @type * * @param model the object with the property to unset * @param type the type of the property */ private static void unsetInlineResource(BaseResource model, Class<?> type) { for (Method method : model.getClass().getMethods()) { if (method.getName().startsWith("set")) { try { if (type.isAssignableFrom(method.getParameterTypes()[0])) { method.invoke(model, new Object[]{null}); return; } } catch (Exception e) { // invocation target exception should not occur on simple setter } } } } /** * Return any parent object set on @model * * i.e. return the value of any bean property whose type matches @parentType * * @param model object to check * @param parentType the type of the parent * @return the parent object, or null if not set */ private static <R extends BaseResource> BaseResource getParentModel(R model, Class<?> parentType) { for (BaseResource inline : getInlineResources(model)) { if (parentType.isAssignableFrom(inline.getClass())) { return inline; } } return null; } /** * Lookup the #Collection instance which represents this object * * i.e. for a VM tag (i.e. a Tag object which its VM property set) * return the #Collection instance which encapsulates AssignedTagResource, * AssignedTagsResource and VM. * * @param model the object to query for * @return the #Collection instance representing the object's collection */ private static Collection getCollection(BaseResource model) { return getCollection(model, null); } /** * Lookup the #Collection instance which represents this object * * i.e. for a VM tag (i.e. a Tag object which its VM property set) * return the #Collection instance which encapsulates AssignedTagResource, * AssignedTagsResource and VM. * * @param model the object to query for * @param suggestedParentType the suggested parent type * @return the #Collection instance representing the object's collection */ private static Collection getCollection(BaseResource model, Class<? extends BaseResource> suggestedParentType) { ParentToCollectionMap collections = TYPES.get(model.getClass()); if (suggestedParentType != null) { for (Class<? extends BaseResource> parentType : collections.keySet()) { if (parentType.equals(suggestedParentType)) { return collections.get(parentType); } } } for (Class<? extends BaseResource> parentType : collections.keySet()) { if (parentType != NO_PARENT && getParentModel(model, parentType) != null) { return collections.get(parentType); } } return collections.get(NO_PARENT); } /** * Create a #UriBuilder which encapsulates the path to an object * * i.e. for a VM tag, return a UriBuilder which encapsulates * '/restapi-definition/vms/{vm_id}/tags/{tag_id}' * * @param uriInfo the URI info * @param model the object * @return the #UriBuilder encapsulating the object's path */ public static <R extends BaseResource> UriBuilder getUriBuilder(UriInfo uriInfo, R model) { return getUriBuilder(uriInfo, model, null); } /** * Create a #UriBuilder which encapsulates the path to an object * * i.e. for a VM tag, return a UriBuilder which encapsulates * '/restapi-definition/vms/{vm_id}/tags/{tag_id}' * * @param uriInfo the URI info * @param model the object * @param suggestedParentType the suggested parent type * @return the #UriBuilder encapsulating the object's path */ public static <R extends BaseResource> UriBuilder getUriBuilder(UriInfo uriInfo, R model, Class<? extends BaseResource> suggestedParentType) { Collection collection = getCollection(model, suggestedParentType); if (collection == null) { return null; } UriBuilder uriBuilder; if (collection.getParentType() != NO_PARENT) { BaseResource parent = getParentModel(model, collection.getParentType()); Collection parentCollection = getCollection(parent, suggestedParentType); String path = getPath(collection.getCollectionType(), parentCollection.getResourceType(), model.getClass()); uriBuilder = getUriBuilder(uriInfo, parent).path(path); } else { String path = getPath(collection.getCollectionType()); uriBuilder = uriInfo != null ? UriBuilder.fromPath(uriInfo.getBaseUri().getPath()).path(path) : UriBuilder.fromPath(path); } return uriBuilder.path(model.getId()); } /** * Set the href attribute on the supplied object * * e.g. set href = '/restapi-definition/vms/{vm_id}/tags/{tag_id}' on a VM tag * * @param uriInfo the URI info * @param model the object * @return the model, with the href attribute set */ private static <R extends BaseResource> void setHref(UriInfo uriInfo, R model) { setHref(uriInfo, model, null); } /** * Set the href attribute on the supplied object * * e.g. set href = '/restapi-definition/vms/{vm_id}/tags/{tag_id}' on a VM tag * * @param uriInfo the URI info * @param model the object * @param suggestedParentType the suggested parent type * @return the model, with the href attribute set */ private static <R extends BaseResource> void setHref(UriInfo uriInfo, R model, Class<? extends BaseResource> suggestedParentType) { UriBuilder uriBuilder = getUriBuilder(uriInfo, model, suggestedParentType); if (uriBuilder != null) { model.setHref(uriBuilder.build().toString()); } } /** * Construct the set of action links for an object * * @param uriInfo the URI info * @param model the object * @param suggestedParentType the suggested parent type * @return the object, including its set of action links */ private static <R extends BaseResource> void setActions(UriInfo uriInfo, R model, Class<? extends BaseResource> suggestedParentType) { Collection collection = getCollection(model); UriBuilder uriBuilder = getUriBuilder(uriInfo, model, suggestedParentType); if (uriBuilder != null) { ActionsBuilder actionsBuilder = new ActionsBuilder(uriBuilder, collection.getResourceType()); model.setActions(actionsBuilder.build()); } } /** * Set the href attribute on the object (and its inline objects) * and construct its set of action links * * @param uriInfo the URI info * @param model the object * @param suggestedParentType the suggested parent type * @return the object, with href attributes and action links */ public static <R extends BaseResource> R addLinks(UriInfo uriInfo, R model) { return addLinks(uriInfo, model, null); } public static <R extends BaseResource> R addLinks(UriInfo uriInfo, R model, Class<? extends BaseResource> suggestedParentType) { setHref(uriInfo, model, suggestedParentType); setActions(uriInfo, model, suggestedParentType); for (BaseResource inline : getInlineResources(model)) { if (inline.getId() != null) { setHref(uriInfo, inline); } for (BaseResource grandParent : getInlineResources(inline)) { unsetInlineResource(inline, grandParent.getClass()); } } return model; } /** * Appends searchable links to resource's Href * * @param url to append to * @param resource to add links to * @param rel link ro add * @param flags used to specify different link options */ public static void addLink(BaseResource resource, String rel, LinkFlags flags) { addLink(resource.getHref(), resource, rel, flags); } /** * Adds searchable links to resource * * @param url to append to * @param resource to add links to * @param rel link ro add * @param flags used to specify different link options */ public static void addLink(String url, BaseResource resource, String rel, LinkFlags flags) { addLink(url, resource, rel, flags, new ParametersSet()); } /** * Adds searchable links to resource * * @param url to append to * @param resource to add links to * @param rel link to add * @param flags used to specify different link options * @param params the URL params to append */ public static void addLink(String url, BaseResource resource, String rel, LinkFlags flags, ParametersSet params) { Link link = new Link(); link.setRel(rel); link.setHref(combine(url, rel)); resource.getLinks().add(link); if (flags == LinkFlags.SEARCHABLE) { addLink(url, resource, rel, params); } } /** * Appends searchable links to resource's Href * * @param url to append to and combine search dialect * @param resource to add links to * @param rel link ro add */ public static void addLink(BaseResource resource, String rel) { addLink(resource.getHref(), resource, rel); } /** * Adds searchable links to resource * * @param url to append to and combine search dialect * @param resource to add links to * @param rel link ro add * @param params the URL params to append */ public static void addLink(String url, BaseResource resource, String rel, ParametersSet params) { Link link = new Link(); link.setRel(rel + SEARCH_RELATION); link.setHref(combine(combine(url, rel) + SEARCH_TEMPLATE, params)); resource.getLinks().add(link); } /** * Adds searchable links to resource * * @param url to append to and combine search dialect * @param resource to add links to * @param rel link ro add */ public static void addLink(String url, BaseResource resource, String rel) { Link link = new Link(); link.setRel(rel + SEARCH_RELATION); link.setHref(combine(url, rel) + SEARCH_TEMPLATE); resource.getLinks().add(link); } /** * Combine URL params to URI path. * * @param head the path head * @param params the URL params to append * @return the combined head and params */ public static String combine(String head, ParametersSet params) { String combined_params = ""; if (params != null) { for (Parameter entry : params.getParameters()) { combined_params += String.format(PARAMETER_TEMPLATE, entry.getName(), entry.getValue()); } } return head + combined_params; } /** * Create a search link with the given parameters * * @param url the url * @param rel the link to add * @param flags flags for this link, e.g: 'searchable' * @return the link the was created */ public static DetailedLink createLink(String url, String rel, LinkFlags flags) { return createLink(url, rel, flags, new ParametersSet()); } /** * Create a search link with the given parameters * * @param url the url * @param rel the link to add * @param flags flags for this link, e.g: 'searchable' * @param params url parameters * @return the link the was created */ public static DetailedLink createLink(String url, String rel, LinkFlags flags, ParametersSet params) { DetailedLink link = new DetailedLink(); link.setRel(rel); link.setHref(combine(url, rel)); if (flags == LinkFlags.SEARCHABLE) { LinkCapabilities capabilities = new LinkCapabilities(); capabilities.setSearchable(true); link.setLinkCapabilities(capabilities); } link.setRequest(new Request()); link.getRequest().setUrl(new Url()); link.getRequest().getUrl().getParametersSets().add(params); return link; } /** * Create a search link with the given parameters * * @param url the url * @param rel the link to add * @param params url parameters * @return the link the was created */ public static Link createLink(String url, String rel, List<ParametersSet> params) { Link link = new Link(); link.setRel(rel + SEARCH_RELATION); link.setHref(combine(url + SEARCH_TEMPLATE, params)); return link; } public static Link createLink(String url, String rel) { Link link = new Link(); link.setRel(rel); link.setHref(url); return link; } /** * Create a search link with the given parameters * @param url the url * @param rel the link to add * @return link with search */ public static Link createSearchLink(String url, String rel) { Link link = new Link(); link.setRel(rel + SEARCH_RELATION); link.setHref(combine(url, rel) + SEARCH_TEMPLATE); return link; } /** * Combine head and tail portions of a URI path. * * @param head the path head * @param tail the path tail * @return the combined head and tail */ public static String combine(String head, String tail) { if (head.endsWith("/")) { head = head.substring(0, head.length() - 1); } if (tail.startsWith("/")) { tail = tail.substring(1); } return head + "/" + tail; } /** * Combine URL params to URI path. * * @param head the path head * @param params the URL params to append * @return the combined head and params */ public static String combine(String head, List<ParametersSet> params) { String combined_params = ""; if (params != null) { for (ParametersSet ps : params) { for (Parameter param : ps.getParameters()) { combined_params += String.format(PARAMETER_TEMPLATE, param.getName(), param.getValue()); } } } return head + combined_params; } /** * A #Map sub-class which maps a model type (e.g. Tag.class) to a * set of suitable collection definitions. */ private static class ModelToCollectionsMap extends HashMap<Class<? extends BaseResource>, ParentToCollectionMap> { } /** * A #Map sub-class which maps a parent model type to collection * definition. * * e.g. the map for Tag contains a collection definition for the * describing the VM, Host and User tags sub-collections. It also * contains a collection definition describing the top-level * tags collection which is keyed on the NO_PARENT key. */ private static class ParentToCollectionMap extends LinkedHashMap<Class<? extends BaseResource>, Collection> { public ParentToCollectionMap(Class<?> resourceType, Class<?> collectionType, Class<? extends BaseResource> parentType) { super(); add(resourceType, collectionType, parentType); } public ParentToCollectionMap(Class<?> resourceType, Class<?> collectionType) { this(resourceType, collectionType, NO_PARENT); } public void add(Class<?> resourceType, Class<?> collectionType, Class<? extends BaseResource> parentType) { put(parentType, new Collection(resourceType, collectionType, parentType)); } } /** * A description of a collection type, its resource type and the parent * resource which contains it, if any. * * e.g. for the VM tags collection, resourceType is AssignedTagResource, * collectionType is AssignedTagsResource and parentType is VM */ private static class Collection { private final Class<?> resourceType; private final Class<?> collectionType; private final Class<?> parentType; public Collection(Class<?> resourceType, Class<?> collectionType, Class<?> parentType) { this.resourceType = resourceType; this.collectionType = collectionType; this.parentType = parentType; } public Class<?> getResourceType() { return resourceType; } public Class<?> getCollectionType() { return collectionType; } public Class<?> getParentType() { return parentType; } } /** * Used to specify link options */ public enum LinkFlags { NONE, SEARCHABLE; } }