/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.resource; import org.apache.commons.lang3.StringUtils; import org.candlepin.auth.Verify; import org.candlepin.common.auth.SecurityHole; import org.candlepin.common.config.Configuration; import org.candlepin.common.exceptions.BadRequestException; import org.candlepin.common.exceptions.ForbiddenException; import org.candlepin.common.exceptions.NotFoundException; import org.candlepin.config.ConfigProperties; import org.candlepin.controller.ProductManager; import org.candlepin.model.Content; import org.candlepin.model.CandlepinQuery; import org.candlepin.model.Owner; import org.candlepin.model.OwnerContentCurator; import org.candlepin.model.OwnerCurator; import org.candlepin.model.OwnerProductCurator; import org.candlepin.model.Product; import org.candlepin.model.ProductCertificate; import org.candlepin.model.ProductCertificateCurator; import org.candlepin.model.ProductContent; import org.candlepin.model.ProductCurator; import org.candlepin.model.dto.ProductData; import org.candlepin.pinsetter.tasks.RefreshPoolsForProductJob; import org.candlepin.util.ElementTransformer; import com.google.inject.Inject; import com.google.inject.persist.Transactional; import org.quartz.JobDetail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnap.commons.i18n.I18n; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; /** * API Gateway into /product * * @version $Rev$ */ @Path("/owners/{owner_key}/products") @Api(value = "owners", authorizations = { @Authorization("basic") }) public class OwnerProductResource { private static Logger log = LoggerFactory.getLogger(OwnerProductResource.class); private Configuration config; private I18n i18n; private OwnerContentCurator ownerContentCurator; private OwnerCurator ownerCurator; private OwnerProductCurator ownerProductCurator; private ProductCertificateCurator productCertCurator; private ProductCurator productCurator; private ProductManager productManager; @Inject public OwnerProductResource(Configuration config, I18n i18n, OwnerCurator ownerCurator, OwnerContentCurator ownerContentCurator, OwnerProductCurator ownerProductCurator, ProductCertificateCurator productCertCurator, ProductCurator productCurator, ProductManager productManager) { this.config = config; this.i18n = i18n; this.ownerContentCurator = ownerContentCurator; this.ownerCurator = ownerCurator; this.ownerProductCurator = ownerProductCurator; this.productCertCurator = productCertCurator; this.productCurator = productCurator; this.productManager = productManager; } /** * Retrieves an Owner instance for the owner with the specified key/account. If a matching owner * could not be found, this method throws an exception. * * @param key * The key for the owner to retrieve * * @throws NotFoundException * if an owner could not be found for the specified key. * * @return * the Owner instance for the owner with the specified key. * * @httpcode 200 * @httpcode 404 */ protected Owner getOwnerByKey(String key) { Owner owner = this.ownerCurator.lookupByKey(key); if (owner == null) { throw new NotFoundException(i18n.tr("Owner with key \"{0}\" was not found.", key)); } return owner; } /** * Retrieves a Product instance for the product with the specified id. If no matching product * could be found, this method throws an exception. * * @param owner * The organization * * @param productId * The ID of the product to retrieve * * @throws NotFoundException * if no matching product could be found with the specified id * * @return * the Product instance for the product with the specified id */ protected Product fetchProduct(Owner owner, String productId) { Product product = this.ownerProductCurator.getProductById(owner, productId); if (product == null) { throw new NotFoundException( i18n.tr("Product with ID ''{0}'' could not be found.", productId) ); } return product; } /** * Retrieves a Content instance for the content with the specified id. If no matching content * could be found, this method throws an exception. * * @param owner * The organization * * @param contentId * The ID of the content to retrieve * * @throws NotFoundException * if no matching content could be found with the specified id. * * @return * the Content instance for the content with the specified id */ protected Content fetchContent(Owner owner, String contentId) { Content content = this.ownerContentCurator.getContentById(owner, contentId); if (content == null) { throw new NotFoundException( i18n.tr("Content with ID \"{0}\" could not be found.", contentId) ); } return content; } @ApiOperation(notes = "Retrieves a list of Products", value = "List Products for an Owner", response = Product.class, responseContainer = "list") @GET @Produces(MediaType.APPLICATION_JSON) public CandlepinQuery<ProductData> listProducts( @Verify(Owner.class) @PathParam("owner_key") String ownerKey, @QueryParam("product") List<String> productIds) { Owner owner = this.getOwnerByKey(ownerKey); CandlepinQuery<Product> query = productIds != null && !productIds.isEmpty() ? this.ownerProductCurator.getProductsByIds(owner, productIds) : this.ownerProductCurator.getProductsByOwner(owner); return query.transform(new ElementTransformer<Product, ProductData>() { @Override public ProductData transform(Product element) { return element.toDTO(); } }); } @ApiOperation(notes = "Retrieves a single Product", value = "getProduct") @ApiResponses({ @ApiResponse(code = 404, message = "") }) @GET @Path("/{product_id}") @Produces(MediaType.APPLICATION_JSON) @SecurityHole public ProductData getProduct( @Verify(Owner.class) @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId) { Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); return product.toDTO(); } @ApiOperation(notes = "Retreives a Certificate for a Product", value = "getProductCertificate") @ApiResponses({ @ApiResponse(code = 404, message = "") }) @GET @Path("/{product_id}/certificate") @Produces(MediaType.APPLICATION_JSON) @SecurityHole @Transactional public ProductCertificate getProductCertificate( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId) { Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); return this.productCertCurator.getCertForProduct(product); } @ApiOperation(notes = "Creates a Product. Returns either the new created Product or " + "the Product that already existed.", value = "createProduct") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Transactional public ProductData createProduct( @PathParam("owner_key") String ownerKey, ProductData product) { Owner owner = this.getOwnerByKey(ownerKey); Product entity = productManager.createProduct(product, owner); return entity.toDTO(); } @ApiOperation(notes = "Updates a Product", value = "updateProduct") @ApiResponses({ @ApiResponse(code = 400, message = "") }) @PUT @Path("/{product_id}") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Transactional public ProductData updateProduct( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId, @ApiParam(name = "update", required = true) ProductData update) { if (StringUtils.isEmpty(update.getId())) { update.setId(productId); } else if (!StringUtils.equals(update.getId(), productId)) { throw new BadRequestException( i18n.tr("Contradictory ids in update request: {0}, {1}", productId, update.getId()) ); } Owner owner = this.getOwnerByKey(ownerKey); Product existing = this.fetchProduct(owner, productId); if (existing.isLocked()) { throw new ForbiddenException(i18n.tr("product \"{0}\" is locked", existing.getId())); } Product updated = this.productManager.updateProduct(update, owner, true); return updated.toDTO(); } @ApiOperation(notes = "Adds one or more Content entities to a Product", value = "addBatchContent") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("/{product_id}/batch_content") @Transactional public ProductData addBatchContent( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId, @ApiParam(name = "contentMap", required = true) Map<String, Boolean> contentMap) { Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); Collection<ProductContent> productContent = new LinkedList<ProductContent>(); if (product.isLocked()) { throw new ForbiddenException(i18n.tr("product \"{0}\" is locked", product.getId())); } this.productCurator.lock(product); for (Entry<String, Boolean> entry : contentMap.entrySet()) { Content content = this.fetchContent(owner, entry.getKey()); productContent.add(new ProductContent(product, content, entry.getValue())); } product = this.productManager.addContentToProduct(product, productContent, owner, true); return product.toDTO(); } @ApiOperation(notes = "Adds a single Content to a Product", value = "addContent") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.WILDCARD) @Path("/{product_id}/content/{content_id}") @Transactional public ProductData addContent( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId, @PathParam("content_id") String contentId, @QueryParam("enabled") Boolean enabled) { Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); Content content = this.fetchContent(owner, contentId); if (product.isLocked()) { throw new ForbiddenException(i18n.tr("product \"{0}\" is locked", product.getId())); } this.productCurator.lock(product); product = this.productManager.addContentToProduct( product, Arrays.asList(new ProductContent(product, content, enabled)), owner, true ); return product.toDTO(); } @ApiOperation(notes = "Removes a single Content from a Product", value = "removeContent") @DELETE @Produces(MediaType.APPLICATION_JSON) @Path("/{product_id}/content/{content_id}") @Transactional public void removeContent( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId, @PathParam("content_id") String contentId) { Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); Content content = this.fetchContent(owner, contentId); if (product.isLocked()) { throw new ForbiddenException(i18n.tr("product \"{0}\" is locked", product.getId())); } // Remove content this.productManager.removeProductContent(product, Arrays.asList(content), owner, true); } @ApiOperation(notes = "Removes a Product", value = "deleteProduct") @ApiResponses({ @ApiResponse(code = 400, message = ""), @ApiResponse(code = 404, message = "") }) @DELETE @Produces(MediaType.APPLICATION_JSON) @Path("/{product_id}") @Transactional public void deleteProduct( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId) { Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); if (product.isLocked()) { throw new ForbiddenException(i18n.tr("product \"{0}\" is locked", product.getId())); } if (this.productCurator.productHasSubscriptions(owner, product)) { throw new BadRequestException( i18n.tr( "Product with ID ''{0}'' cannot be deleted while subscriptions exist.", productId ) ); } this.productManager.removeProduct(owner, product); } @ApiOperation(notes = "Refreshes Pools by Product", value = "refreshPoolsForProduct") @PUT @Path("/{product_id}/subscriptions") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.WILDCARD) @Transactional public JobDetail refreshPoolsForProduct( @PathParam("owner_key") String ownerKey, @PathParam("product_id") String productId, @QueryParam("lazy_regen") @DefaultValue("true") Boolean lazyRegen) { if (config.getBoolean(ConfigProperties.STANDALONE)) { log.warn("Ignoring refresh pools request due to standalone config."); return null; } Owner owner = this.getOwnerByKey(ownerKey); Product product = this.fetchProduct(owner, productId); return RefreshPoolsForProductJob.forProduct(product, lazyRegen); } }