Java tutorial
/* * Weblounge: Web Content Management System * Copyright (c) 2003 - 2011 The Weblounge Team * http://entwinemedia.com/weblounge * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.contentrepository.impl.endpoint; import ch.entwine.weblounge.common.content.Resource; import ch.entwine.weblounge.common.content.ResourceContent; import ch.entwine.weblounge.common.content.ResourceContentReader; import ch.entwine.weblounge.common.content.ResourceReader; import ch.entwine.weblounge.common.content.ResourceSearchResultItem; import ch.entwine.weblounge.common.content.ResourceURI; import ch.entwine.weblounge.common.content.ResourceUtils; import ch.entwine.weblounge.common.content.SearchQuery; import ch.entwine.weblounge.common.content.SearchQuery.Order; import ch.entwine.weblounge.common.content.SearchResult; import ch.entwine.weblounge.common.content.SearchResultItem; import ch.entwine.weblounge.common.content.file.FileResource; import ch.entwine.weblounge.common.content.page.Page; import ch.entwine.weblounge.common.impl.content.GeneralResourceURIImpl; import ch.entwine.weblounge.common.impl.content.ResourceURIImpl; import ch.entwine.weblounge.common.impl.content.SearchQueryImpl; import ch.entwine.weblounge.common.impl.content.file.FileResourceImpl; import ch.entwine.weblounge.common.impl.content.page.PageSearchResultItemImpl; import ch.entwine.weblounge.common.impl.language.LanguageUtils; import ch.entwine.weblounge.common.impl.security.SecurityUtils; import ch.entwine.weblounge.common.impl.security.SystemRole; import ch.entwine.weblounge.common.impl.security.UserImpl; import ch.entwine.weblounge.common.impl.url.WebUrlImpl; import ch.entwine.weblounge.common.language.Language; import ch.entwine.weblounge.common.language.UnknownLanguageException; import ch.entwine.weblounge.common.repository.ContentRepository; import ch.entwine.weblounge.common.repository.ContentRepositoryException; import ch.entwine.weblounge.common.repository.ReferentialIntegrityException; import ch.entwine.weblounge.common.repository.ResourceSerializer; import ch.entwine.weblounge.common.repository.ResourceSerializerService; import ch.entwine.weblounge.common.repository.WritableContentRepository; import ch.entwine.weblounge.common.security.SecurityService; import ch.entwine.weblounge.common.security.User; import ch.entwine.weblounge.common.site.Site; import ch.entwine.weblounge.common.url.UrlUtils; import ch.entwine.weblounge.common.url.WebUrl; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.fileupload.util.Streams; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.tika.Tika; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Calendar; import java.util.Date; import java.util.StringTokenizer; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; 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.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import javax.xml.parsers.ParserConfigurationException; /** * This class implements the <code>REST</code> endpoint for file data. */ @Path("/") @Produces(MediaType.TEXT_XML) public class FilesEndpoint extends ContentRepositoryEndpoint { /** Request parameter name for the path */ public static final String OPT_PATH = "path"; /** Request parameter name for the content language */ public static final String OPT_LANGUAGE = "language"; /** Request parameter name for the content type */ public static final String OPT_MIMETYPE = "mimeType"; /** Logging facility */ private static final Logger logger = LoggerFactory.getLogger(FilesEndpoint.class); /** Mime type detector */ private final Tika mimeTypeDetector = new Tika(); /** The security service */ protected SecurityService securityService = null; /** The resource serializer service */ private ResourceSerializerService serializerService = null; /** The endpoint documentation */ private String docs = null; /** * Returns a collection of files which match the given criteria. * * @param request * the request * @param path * the file path (e.g. <code>/my/simple/path</code>) * @param subjectstring * one ore more subjects, divided by a comma * @param searchterms * fulltext search terms * @param filter * further search result filtering * @param type * the file type, e. g. * {@link ch.entwine.weblounge.common.content.image.ImageResource#TYPE} * @param sort * sort order, possible values are * <code>created-asc, created-desc, published-asc, published-desc, modified-asc & modified-desc</code> * @param limit * search result limit * @param offset * search result offset (for paging in combination with limit) * @return a collection of matching files */ @GET @Path("/") public Response getAllFiles(@Context HttpServletRequest request, @QueryParam("path") String path, @QueryParam("subjects") String subjectstring, @QueryParam("searchterms") String searchterms, @QueryParam("filter") String filter, @QueryParam("type") String type, @QueryParam("sort") @DefaultValue("modified-desc") String sort, @QueryParam("limit") @DefaultValue("10") int limit, @QueryParam("offset") @DefaultValue("0") int offset) { // Create search query Site site = getSite(request); SearchQuery q = new SearchQueryImpl(site); q.withVersion(Resource.LIVE); // Type q.withoutTypes(Page.TYPE); if (StringUtils.isNotBlank(type)) q.withTypes(type); // Path if (StringUtils.isNotBlank(path)) q.withPath(path); // Subjects if (StringUtils.isNotBlank(subjectstring)) { StringTokenizer subjects = new StringTokenizer(subjectstring, ","); while (subjects.hasMoreTokens()) q.withSubject(subjects.nextToken()); } // Search terms if (StringUtils.isNotBlank(searchterms)) q.withText(true, searchterms); Calendar today = Calendar.getInstance(); today.set(Calendar.HOUR_OF_DAY, 0); today.set(Calendar.MINUTE, 0); today.set(Calendar.SECOND, 0); today.set(Calendar.MILLISECOND, 0); Calendar yesterday = Calendar.getInstance(); yesterday.add(Calendar.DATE, -1); yesterday.set(Calendar.HOUR_OF_DAY, 0); yesterday.set(Calendar.MINUTE, 0); yesterday.set(Calendar.SECOND, 0); yesterday.set(Calendar.MILLISECOND, 0); Calendar tomorrow = Calendar.getInstance(); tomorrow.add(Calendar.DATE, 1); tomorrow.set(Calendar.HOUR_OF_DAY, 0); tomorrow.set(Calendar.MINUTE, 0); tomorrow.set(Calendar.SECOND, 0); tomorrow.set(Calendar.MILLISECOND, 0); // Filter query if (StringUtils.isNotBlank(filter)) { if ("/".equals(filter)) { q.withPath("/"); } // by user else if (filter.startsWith("creator:") && filter.length() > "creator:".length()) { String creator = StringUtils.trim(filter.substring("creator:".length())); if ("me".equals(creator)) q.withCreator(securityService.getUser()); else q.withCreator(new UserImpl(creator)); } else if (filter.startsWith("modifier:") && filter.length() > "modifier:".length()) { String modifier = StringUtils.trim(filter.substring("modifier:".length())); if ("me".equals(modifier)) q.withModifier(securityService.getUser()); else q.withModifier(new UserImpl(modifier)); } else if (filter.startsWith("publisher:") && filter.length() > "publisher:".length()) { String publisher = StringUtils.trim(filter.substring("publisher:".length())); if ("me".equals(publisher)) q.withPublisher(securityService.getUser()); else q.withPublisher(new UserImpl(publisher)); } // by date else if (filter.startsWith("created:") && filter.length() > "created:".length()) { String created = StringUtils.trim(filter.substring("created:".length())); if ("today".equals(created)) q.withCreationDateBetween(today.getTime()).and(tomorrow.getTime()); else if ("yesterday".equals(created)) q.withCreationDateBetween(yesterday.getTime()).and(today.getTime()); else q.withCreationDate(tomorrow.getTime()); } else if (filter.startsWith("modified:") && filter.length() > "modified:".length()) { String modified = StringUtils.trim(filter.substring("modified:".length())); if ("today".equals(modified)) q.withModificationDateBetween(today.getTime()).and(tomorrow.getTime()); else if ("yesterday".equals(modified)) q.withModificationDateBetween(yesterday.getTime()).and(today.getTime()); else q.withCreationDate(tomorrow.getTime()); } else if (filter.startsWith("publisher:") && filter.length() > "publisher:".length()) { String published = StringUtils.trim(filter.substring("published:".length())); if ("today".equals(published)) q.withPublishingDateBetween(today.getTime()).and(tomorrow.getTime()); else if ("yesterday".equals(published)) q.withPublishingDateBetween(yesterday.getTime()).and(today.getTime()); else q.withCreationDate(tomorrow.getTime()); } // by id else if (filter.contains("id:")) { String[] searchTerms = StringUtils.split(filter); for (String searchTerm : searchTerms) { if (searchTerm.startsWith("id:") && filter.length() > "id:".length()) { q.withIdentifier(StringUtils.trim(searchTerm.substring("id:".length()))); } } } // simple filter else { q.withFulltext(true, filter); } } // Limit and Offset q.withLimit(limit); q.withOffset(offset); // Sort order if (StringUtils.equalsIgnoreCase("modified-asc", sort)) { q.sortByModificationDate(Order.Ascending); } else if (StringUtils.equalsIgnoreCase("modified-desc", sort)) { q.sortByModificationDate(Order.Descending); } else if (StringUtils.equalsIgnoreCase("created-asc", sort)) { q.sortByCreationDate(Order.Ascending); } else if (StringUtils.equalsIgnoreCase("created-desc", sort)) { q.sortByCreationDate(Order.Descending); } else if (StringUtils.equalsIgnoreCase("published-asc", sort)) { q.sortByPublishingDate(Order.Ascending); } else if (StringUtils.equalsIgnoreCase("published-desc", sort)) { q.sortByPublishingDate(Order.Descending); } // Load the result String result = loadResultSet(q); // Return the response return Response.ok(result).build(); } /** * Returns a collection of files that are defined as pending. * * @param request * the request * @param filter * further search result filtering * @param type * the file type, e. g. * {@link ch.entwine.weblounge.common.content.image.ImageResource#TYPE} * @param sort * sort order, possible values are * <code>created-asc, created-desc, published-asc, published-desc, modified-asc & modified-desc</code> * @param limit * search result limit * @param offset * search result offset (for paging in combination with limit) * @return a collection of matching files */ @GET @Path("/pending") public Response getPending(@Context HttpServletRequest request, @QueryParam("filter") String filter, @QueryParam("type") String type, @QueryParam("sort") @DefaultValue("modified-desc") String sort, @QueryParam("limit") @DefaultValue("10") int limit, @QueryParam("offset") @DefaultValue("0") int offset) { // Create search query Site site = getSite(request); SearchQuery q = new SearchQueryImpl(site); q.withVersion(Resource.LIVE); // Only take resources that have not been modified q.withoutModification(); // Type q.withoutTypes(Page.TYPE); if (StringUtils.isNotBlank(type)) q.withTypes(type); // Filter query if (StringUtils.isNotBlank(filter)) q.withFilter(filter); // Limit and Offset q.withLimit(limit); q.withOffset(offset); // Sort order if (StringUtils.equalsIgnoreCase("modified-asc", sort)) { q.sortByModificationDate(Order.Ascending); } else if (StringUtils.equalsIgnoreCase("modified-desc", sort)) { q.sortByModificationDate(Order.Descending); } else if (StringUtils.equalsIgnoreCase("created-asc", sort)) { q.sortByCreationDate(Order.Ascending); } else if (StringUtils.equalsIgnoreCase("created-desc", sort)) { q.sortByCreationDate(Order.Descending); } else if (StringUtils.equalsIgnoreCase("published-asc", sort)) { q.sortByPublishingDate(Order.Ascending); } else if (StringUtils.equalsIgnoreCase("published-desc", sort)) { q.sortByPublishingDate(Order.Descending); } // Load the result String result = loadResultSet(q); // Return the response return Response.ok(result).build(); } /** * Returns the resource with the given identifier or a <code>404</code> if the * resource could not be found. * * @param request * the request * @param resourceId * the resource identifier * @return the resource */ @GET @Produces("text/xml") @Path("/{resource}") public Response getFileById(@Context HttpServletRequest request, @PathParam("resource") String resourceId) { // Check the parameters if (resourceId == null) throw new WebApplicationException(Status.BAD_REQUEST); // Get the resource Resource<?> resource = loadResource(request, resourceId, null); if (resource == null) { throw new WebApplicationException(Status.NOT_FOUND); } // Is there an up-to-date, cached version on the client side? if (!ResourceUtils.hasChanged(request, resource)) { return Response.notModified().build(); } // Create the response ResponseBuilder response = Response.ok(resource.toXml()); response.tag(ResourceUtils.getETagValue(resource)); response.lastModified(ResourceUtils.getModificationDate(resource)); return response.build(); } /** * Returns pages containing pagelets with properties of name * <code>resourceid</code> and a value equal to that of the resource * identifier. * * @param request * the request * @param resourceId * the resource identifier * @return the referring pages */ @GET @Path("/{resource}/referrer") public Response getReferencesByURI(@Context HttpServletRequest request, @PathParam("resource") String resourceId) { // Check the parameters if (resourceId == null) return Response.status(Status.BAD_REQUEST).build(); Site site = getSite(request); SearchQuery q = new SearchQueryImpl(site); q.withVersion(Resource.LIVE); q.withTypes(Page.TYPE); q.withProperty("resourceid", resourceId); ContentRepository repository = getContentRepository(site, false); SearchResult result = null; try { result = repository.find(q); } catch (ContentRepositoryException e) { return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } StringBuffer buf = new StringBuffer("<pages>"); for (SearchResultItem item : result.getItems()) { if (resourceId.equals(item.getId())) continue; String headerXml = ((PageSearchResultItemImpl) item).getPageHeaderXml(); buf.append(headerXml); } buf.append("</pages>"); // Create the response return Response.ok(buf.toString()).build(); } /** * Returns the resource content with the given identifier or a * <code>404</code> if the resource or the resource content could not be * found. * * @param request * the request * @param resourceId * the resource identifier * @param language * the language identifier * @return the resource */ @GET @Path("/{resource}/content/{language}") public Response getFileContent(@Context HttpServletRequest request, @PathParam("resource") String resourceId, @PathParam("language") String languageId) { // Check the parameters if (resourceId == null) throw new WebApplicationException(Status.BAD_REQUEST); // Extract the language Language language; try { language = LanguageUtils.getLanguage(languageId); } catch (UnknownLanguageException e) { throw new WebApplicationException(Status.NOT_FOUND); } if (language == null) throw new WebApplicationException(Status.NOT_FOUND); // Get the resource Resource<?> resource = loadResource(request, resourceId, null); if (resource == null || resource.contents().isEmpty()) { throw new WebApplicationException(Status.NOT_FOUND); } return getResourceContent(request, resource, language); } /** * Adds the resource content with language <code>language</code> to the * specified resource. * * @param request * the request * @param resourceId * the resource identifier * @param languageId * the language identifier * @param is * the input stream * @return the resource */ @POST @Path("/{resource}/content/{language}") @Produces(MediaType.MEDIA_TYPE_WILDCARD) public Response addFileContent(@Context HttpServletRequest request, @PathParam("resource") String resourceId, @PathParam("language") String languageId) { Site site = getSite(request); // Check the parameters if (resourceId == null) throw new WebApplicationException(Status.BAD_REQUEST); // Extract the language Language language = LanguageUtils.getLanguage(languageId); if (language == null) { throw new WebApplicationException(Status.NOT_FOUND); } // Get the resource Resource<?> resource = loadResource(request, resourceId, null); if (resource == null || resource.contents().isEmpty()) { throw new WebApplicationException(Status.NOT_FOUND); } String fileName = null; String mimeType = null; File uploadedFile = null; try { // Multipart form encoding? if (ServletFileUpload.isMultipartContent(request)) { try { ServletFileUpload payload = new ServletFileUpload(); for (FileItemIterator iter = payload.getItemIterator(request); iter.hasNext();) { FileItemStream item = iter.next(); if (item.isFormField()) { String fieldName = item.getFieldName(); String fieldValue = Streams.asString(item.openStream()); if (StringUtils.isBlank(fieldValue)) continue; if (OPT_MIMETYPE.equals(fieldName)) { mimeType = fieldValue; } } else { // once the body gets read iter.hasNext must not be invoked // or the stream can not be read fileName = StringUtils.trim(item.getName()); mimeType = StringUtils.trim(item.getContentType()); uploadedFile = File.createTempFile("upload-", null); FileOutputStream fos = new FileOutputStream(uploadedFile); try { IOUtils.copy(item.openStream(), fos); } catch (IOException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(fos); } } } } catch (FileUploadException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IOException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } } // Octet binary stream else { try { fileName = StringUtils.trimToNull(request.getHeader("X-File-Name")); mimeType = StringUtils.trimToNull(request.getParameter(OPT_MIMETYPE)); } catch (UnknownLanguageException e) { throw new WebApplicationException(Status.BAD_REQUEST); } InputStream is = null; FileOutputStream fos = null; try { is = request.getInputStream(); if (is == null) throw new WebApplicationException(Status.BAD_REQUEST); uploadedFile = File.createTempFile("upload-", null); fos = new FileOutputStream(uploadedFile); IOUtils.copy(is, fos); } catch (IOException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(fos); } } // Has there been a file in the request? if (uploadedFile == null) throw new WebApplicationException(Status.BAD_REQUEST); // A mime type would be nice as well if (StringUtils.isBlank(mimeType)) { mimeType = detectMimeTypeFromFile(fileName, uploadedFile); if (mimeType == null) throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Get the current user User user = securityService.getUser(); if (user == null) throw new WebApplicationException(Status.UNAUTHORIZED); // Make sure the user has editing rights if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR)) throw new WebApplicationException(Status.UNAUTHORIZED); // Try to create the resource content InputStream is = null; ResourceContent content = null; ResourceContentReader<?> reader = null; ResourceSerializer<?, ?> serializer = serializerService .getSerializerByType(resource.getURI().getType()); try { reader = serializer.getContentReader(); is = new FileInputStream(uploadedFile); content = reader.createFromContent(is, user, language, uploadedFile.length(), fileName, mimeType); } catch (IOException e) { logger.warn("Error reading resource content {} from request", resource.getURI()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (ParserConfigurationException e) { logger.warn("Error configuring parser to read resource content {}: {}", resource.getURI(), e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (SAXException e) { logger.warn("Error parsing udpated resource {}: {}", resource.getURI(), e.getMessage()); throw new WebApplicationException(Status.BAD_REQUEST); } finally { IOUtils.closeQuietly(is); } URI uri = null; WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true); try { is = new FileInputStream(uploadedFile); resource = contentRepository.putContent(resource.getURI(), content, is); uri = new URI(resource.getURI().getIdentifier()); } catch (IOException e) { logger.warn("Error writing content to resource {}: {}", resource.getURI(), e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IllegalStateException e) { logger.warn("Illegal state while adding content to resource {}: {}", resource.getURI(), e.getMessage()); throw new WebApplicationException(Status.PRECONDITION_FAILED); } catch (ContentRepositoryException e) { logger.warn("Error adding content to resource {}: {}", resource.getURI(), e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (URISyntaxException e) { logger.warn("Error creating a uri for resource {}: {}", resource.getURI(), e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(is); } // Create the response ResponseBuilder response = Response.created(uri); response.type(MediaType.MEDIA_TYPE_WILDCARD); response.tag(ResourceUtils.getETagValue(resource)); response.lastModified(ResourceUtils.getModificationDate(resource, language)); return response.build(); } finally { FileUtils.deleteQuietly(uploadedFile); } } /** * Returns the resource content with the given identifier or a * <code>404</code> if the resource or the resource content could not be * found. * * @param request * the request * @param resourceId * the resource identifier * @param languageId * the language identifier * @return the resource */ @DELETE @Path("/{resource}/content/{language}") public Response deleteFileContent(@Context HttpServletRequest request, @PathParam("resource") String resourceId, @PathParam("language") String languageId) { // Check the parameters if (resourceId == null) throw new WebApplicationException(Status.BAD_REQUEST); // Extract the language Language language = LanguageUtils.getLanguage(languageId); if (language == null) { throw new WebApplicationException(Status.NOT_FOUND); } // Get the resource Resource<?> resource = loadResource(request, resourceId, null); if (resource == null || resource.contents().isEmpty()) { throw new WebApplicationException(Status.NOT_FOUND); } // Get the resource content ResourceContent content = resource.getContent(language); if (content == null) { throw new WebApplicationException(Status.NOT_FOUND); } ResourceURI uri = resource.getURI(); Site site = getSite(request); // Get the current user User user = securityService.getUser(); if (user == null) throw new WebApplicationException(Status.UNAUTHORIZED); // Make sure the user has editing rights if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR)) throw new WebApplicationException(Status.UNAUTHORIZED); WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true); // Delete the resource try { resource = contentRepository.deleteContent(uri, content); resource.setModified(user, new Date()); // TODO: Remove existing preview images contentRepository.put(resource); } catch (IllegalStateException e) { logger.warn("Tried to remove content from missing resource " + uri); throw new WebApplicationException(Status.NOT_FOUND); } catch (ContentRepositoryException e) { logger.warn("Error while accessing resource " + uri); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IOException e) { logger.warn("Error while deleting content from resource " + uri); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Create the response ResponseBuilder response = Response.ok(resource.toXml()); response.tag(ResourceUtils.getETagValue(resource)); response.lastModified(ResourceUtils.getModificationDate(resource)); return response.build(); } /** * Updates the indicated resource. * * @param request * the http request * @param resourceId * the resource identifier * @param ifMatchHeader * the resource's <code>etag</code> value * @param resourceContent * the resource content * @return response an empty response * @throws WebApplicationException * if the update fails */ @PUT @Path("/{resource}") public Response updateFile(@Context HttpServletRequest request, @PathParam("resource") String resourceId, @FormParam("content") String resourceXml, @HeaderParam("If-Match") String ifMatchHeader) { // Check the parameters if (resourceId == null) return Response.status(Status.BAD_REQUEST).build(); if (resourceXml == null) return Response.status(Status.BAD_REQUEST).build(); // Extract the site Site site = getSite(request); // Make sure the content repository is writable if (site.getContentRepository().isReadOnly()) { logger.warn("Attempt to write to read-only content repository {}", site); throw new WebApplicationException(Status.PRECONDITION_FAILED); } WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true); ResourceURI resourceURI = null; // Does the resource exist? try { resourceURI = contentRepository.getResourceURI(resourceId); if (resourceURI == null) { throw new WebApplicationException(Status.NOT_FOUND); } } catch (ContentRepositoryException e) { logger.warn("Error lookup up resource {} from repository: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } Resource<?> currentResource; try { currentResource = contentRepository.get(resourceURI); } catch (ContentRepositoryException e) { logger.warn("Error reading current resource {} from repository: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Check the value of the If-Match header against the etag if (ifMatchHeader != null) { String etag = Long.toString(ResourceUtils.getModificationDate(currentResource).getTime()); if (!etag.equals(ifMatchHeader)) { throw new WebApplicationException(Status.PRECONDITION_FAILED); } } // Get the current user User user = securityService.getUser(); if (user == null) throw new WebApplicationException(Status.UNAUTHORIZED); // Make sure the user has editing rights if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR)) throw new WebApplicationException(Status.UNAUTHORIZED); // Parse the resource and update it in the repository Resource<?> resource = null; // TOOD: Extract resource type String resourceType = resourceURI.getType(); try { ResourceSerializer<?, ?> serializer = serializerService.getSerializerByType(resourceType); ResourceReader<?, ?> resourceReader = serializer.getReader(); resource = resourceReader.read(IOUtils.toInputStream(resourceXml, "utf-8"), site); resource.setModified(user, new Date()); contentRepository.put(resource, false); // Check if the resource has been moved String currentPath = currentResource.getURI().getPath(); String newPath = StringUtils.trimToNull(resource.getURI().getPath()); if (currentPath != null && newPath != null && !currentPath.equals(newPath)) { contentRepository.move(currentResource.getURI(), newPath, true); } } catch (IOException e) { logger.warn("Error reading udpated resource {} from request", resourceURI); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (ParserConfigurationException e) { logger.warn("Error configuring parser to read udpated resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (SAXException e) { logger.warn("Error parsing udpated resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.BAD_REQUEST); } catch (IllegalStateException e) { logger.warn("Illegal state while udpating resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.PRECONDITION_FAILED); } catch (ContentRepositoryException e) { logger.warn("Error udpating resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Create the response ResponseBuilder response = Response.ok(); response.tag(ResourceUtils.getETagValue(resource)); response.lastModified(ResourceUtils.getModificationDate(resource)); return response.build(); } /** * Creates a file resource at the site's content repository and returns the * location to post updates to. * * @param request * the http request * @param resourceXml * the new resource * @param path * the path to store the resource at * @return response the resource location */ @POST @Path("/") public Response createFile(@Context HttpServletRequest request, @FormParam("path") String path) { Site site = getSite(request); WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true); // Get the current user User user = securityService.getUser(); if (user == null) throw new WebApplicationException(Status.UNAUTHORIZED); // Make sure the user has editing rights if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR)) throw new WebApplicationException(Status.UNAUTHORIZED); // Create the resource uri ResourceURIImpl resourceURI = null; String uuid = UUID.randomUUID().toString(); if (!StringUtils.isBlank(path)) { try { if (!path.startsWith("/")) path = "/" + path; WebUrl url = new WebUrlImpl(site, path); resourceURI = new GeneralResourceURIImpl(site, url.getPath(), uuid); // Make sure the resource doesn't exist if (contentRepository.exists(new GeneralResourceURIImpl(site, url.getPath()))) { logger.warn("Tried to create already existing resource {} in site '{}'", resourceURI, site); throw new WebApplicationException(Status.CONFLICT); } } catch (IllegalArgumentException e) { logger.warn("Tried to create a resource with an invalid path '{}': {}", path, e.getMessage()); throw new WebApplicationException(Status.BAD_REQUEST); } catch (ContentRepositoryException e) { logger.warn("Resource lookup {} failed for site '{}'", resourceURI, site); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } } else { resourceURI = new GeneralResourceURIImpl(site, "/" + uuid.replaceAll("-", ""), uuid); } URI uri = null; Resource<?> resource = null; try { // Parse the resource and store it logger.debug("Creating new resource at {}", resourceURI); resource = new FileResourceImpl(resourceURI); resource.setCreated(user, new Date()); contentRepository.put(resource, true); uri = new URI(UrlUtils.concat(request.getRequestURL().toString(), resourceURI.getIdentifier())); } catch (URISyntaxException e) { logger.warn("Error creating a uri for resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IOException e) { logger.warn("Error writing new resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IllegalStateException e) { logger.warn("Illegal state while adding new resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.PRECONDITION_FAILED); } catch (ContentRepositoryException e) { logger.warn("Error adding new resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Create the response ResponseBuilder response = Response.created(uri); response.tag(ResourceUtils.getETagValue(resource)); response.lastModified(ResourceUtils.getModificationDate(resource)); return response.build(); } /** * Removes the indicated resource from the site. * * @param request * the http request * @param resourceId * the resource identifier * @return response an empty response */ @DELETE @Path("/{resource}") public Response deleteFile(@Context HttpServletRequest request, @PathParam("resource") String resourceId) { // Check the parameters if (resourceId == null) return Response.status(Status.BAD_REQUEST).build(); Site site = getSite(request); WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true); // Get the current user User user = securityService.getUser(); if (user == null) throw new WebApplicationException(Status.UNAUTHORIZED); // Make sure the user has editing rights if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR)) throw new WebApplicationException(Status.UNAUTHORIZED); ResourceURI resourceURI = null; // Make sure the resource exists try { resourceURI = contentRepository.getResourceURI(resourceId); if (resourceURI == null) { logger.warn("Tried to delete non existing resource {} in site '{}'", resourceURI, site); throw new WebApplicationException(Status.NOT_FOUND); } } catch (ContentRepositoryException e) { logger.warn("File lookup {} failed for site '{}'", resourceURI, site); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Delete the resource try { // TODO: Versions? resourceURI = contentRepository.get(resourceURI).getURI(); contentRepository.delete(resourceURI); } catch (SecurityException e) { logger.warn("Tried to delete file {} of site '{}' without permission", resourceURI, site); throw new WebApplicationException(Status.FORBIDDEN); } catch (ReferentialIntegrityException e) { logger.warn("Tried to delete referenced file {} of site '{}'", resourceURI, site); throw new WebApplicationException(Status.PRECONDITION_FAILED); } catch (IOException e) { logger.warn("Error deleting resource {} from site '{}': {}", new Object[] { resourceURI, site, e.getMessage() }); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (ContentRepositoryException e) { logger.warn("Error removing resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } return Response.ok().build(); } /** * Creates a file resource at the site's content repository by uploading * initial file content and returns the location to post updates to. * * @param request * the http request * @param resourceXml * the new resource * @param path * the path to store the resource at * @param mimeType * the content mime type * @return response the resource location */ @POST @Path("/uploads") @Produces(MediaType.MEDIA_TYPE_WILDCARD) public Response uploadFile(@Context HttpServletRequest request) { Site site = getSite(request); // Make sure the content repository is writable if (site.getContentRepository().isReadOnly()) { logger.warn("Attempt to write to read-only content repository {}", site); throw new WebApplicationException(Status.PRECONDITION_FAILED); } String fileName = null; Language language = null; String path = null; String mimeType = null; File uploadedFile = null; try { // Multipart form encoding? if (ServletFileUpload.isMultipartContent(request)) { try { ServletFileUpload payload = new ServletFileUpload(); for (FileItemIterator iter = payload.getItemIterator(request); iter.hasNext();) { FileItemStream item = iter.next(); String fieldName = item.getFieldName(); if (item.isFormField()) { String fieldValue = Streams.asString(item.openStream()); if (StringUtils.isBlank(fieldValue)) continue; if (OPT_PATH.equals(fieldName)) { path = fieldValue; } else if (OPT_LANGUAGE.equals(fieldName)) { try { language = LanguageUtils.getLanguage(fieldValue); } catch (UnknownLanguageException e) { throw new WebApplicationException(Status.BAD_REQUEST); } } else if (OPT_MIMETYPE.equals(fieldName)) { mimeType = fieldValue; } } else { // once the body gets read iter.hasNext must not be invoked // or the stream can not be read fileName = StringUtils.trim(item.getName()); mimeType = StringUtils.trim(item.getContentType()); uploadedFile = File.createTempFile("upload-", null); FileOutputStream fos = new FileOutputStream(uploadedFile); try { IOUtils.copy(item.openStream(), fos); } catch (IOException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(fos); } } } } catch (FileUploadException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IOException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } } // Octet binary stream else { try { fileName = StringUtils.trimToNull(request.getHeader("X-File-Name")); path = StringUtils.trimToNull(request.getParameter(OPT_PATH)); mimeType = StringUtils.trimToNull(request.getParameter(OPT_MIMETYPE)); language = LanguageUtils.getLanguage(request.getParameter(OPT_LANGUAGE)); } catch (UnknownLanguageException e) { throw new WebApplicationException(Status.BAD_REQUEST); } InputStream is = null; FileOutputStream fos = null; try { is = request.getInputStream(); if (is == null) throw new WebApplicationException(Status.BAD_REQUEST); uploadedFile = File.createTempFile("upload-", null); fos = new FileOutputStream(uploadedFile); IOUtils.copy(is, fos); } catch (IOException e) { throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(fos); } } // Has there been a file in the request? if (uploadedFile == null) throw new WebApplicationException(Status.BAD_REQUEST); // Check the filename if (fileName == null) { logger.warn("No filename found for upload, request header 'X-File-Name' not specified"); fileName = uploadedFile.getName(); } // Make sure there is a language if (language == null) { language = LanguageUtils.getPreferredLanguage(request, site); } // A mime type would be nice as well if (StringUtils.isBlank(mimeType)) { mimeType = detectMimeTypeFromFile(fileName, uploadedFile); if (mimeType == null) throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } // Set owner and date created User user = securityService.getUser(); if (user == null) throw new WebApplicationException(Status.UNAUTHORIZED); // Make sure the user has editing rights if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR)) throw new WebApplicationException(Status.UNAUTHORIZED); WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true); // Create the resource uri URI uri = null; InputStream is = null; Resource<?> resource = null; ResourceURI resourceURI = null; logger.debug("Adding resource to {}", resourceURI); ResourceSerializer<?, ?> serializer = serializerService.getSerializerByMimeType(mimeType); if (serializer == null) { logger.debug("No specialized resource serializer found, using regular file serializer"); serializer = serializerService.getSerializerByType(FileResource.TYPE); } // Create the resource try { is = new FileInputStream(uploadedFile); resource = serializer.newResource(site, is, user, language); resourceURI = resource.getURI(); } catch (FileNotFoundException e) { logger.warn("Error creating resource at {} from image: {}", uri, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(is); } // If a path has been specified, set it if (path != null && StringUtils.isNotBlank(path)) { try { if (!path.startsWith("/")) path = "/" + path; WebUrl url = new WebUrlImpl(site, path); resourceURI.setPath(url.getPath()); // Make sure the resource doesn't exist if (contentRepository.exists(new GeneralResourceURIImpl(site, url.getPath()))) { logger.warn("Tried to create already existing resource {} in site '{}'", resourceURI, site); throw new WebApplicationException(Status.CONFLICT); } } catch (IllegalArgumentException e) { logger.warn("Tried to create a resource with an invalid path '{}': {}", path, e.getMessage()); throw new WebApplicationException(Status.BAD_REQUEST); } catch (ContentRepositoryException e) { logger.warn("Resource lookup {} failed for site '{}'", resourceURI, site); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } } // Store the new resource try { uri = new URI(resourceURI.getIdentifier()); contentRepository.put(resource, true); } catch (URISyntaxException e) { logger.warn("Error creating a uri for resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IOException e) { logger.warn("Error writing new resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IllegalStateException e) { logger.warn("Illegal state while adding new resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.PRECONDITION_FAILED); } catch (ContentRepositoryException e) { logger.warn("Error adding new resource {}: {}", resourceURI, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } ResourceContent content = null; ResourceContentReader<?> reader = null; try { reader = serializer.getContentReader(); is = new FileInputStream(uploadedFile); content = reader.createFromContent(is, user, language, uploadedFile.length(), fileName, mimeType); } catch (IOException e) { logger.warn("Error reading resource content {} from request", uri); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (ParserConfigurationException e) { logger.warn("Error configuring parser to read resource content {}: {}", uri, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (SAXException e) { logger.warn("Error parsing udpated resource {}: {}", uri, e.getMessage()); throw new WebApplicationException(Status.BAD_REQUEST); } catch (Throwable t) { logger.warn("Unknown error while trying to read resource content {}: {}", uri, t.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(is); if (content == null) { try { contentRepository.delete(resourceURI); } catch (Throwable t) { logger.error("Error deleting orphan resource {}", resourceURI, t); } } } try { is = new FileInputStream(uploadedFile); resource = contentRepository.putContent(resource.getURI(), content, is); } catch (IOException e) { logger.warn("Error writing content to resource {}: {}", uri, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } catch (IllegalStateException e) { logger.warn("Illegal state while adding content to resource {}: {}", uri, e.getMessage()); throw new WebApplicationException(Status.PRECONDITION_FAILED); } catch (ContentRepositoryException e) { logger.warn("Error adding content to resource {}: {}", uri, e.getMessage()); throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(is); } // Create the response ResponseBuilder response = Response.created(uri); response.type(MediaType.MEDIA_TYPE_WILDCARD); response.tag(ResourceUtils.getETagValue(resource)); response.lastModified(ResourceUtils.getModificationDate(resource)); return response.build(); } finally { FileUtils.deleteQuietly(uploadedFile); } } /** * Returns the endpoint documentation. * * @return the endpoint documentation */ @GET @Path("/docs") @Produces(MediaType.TEXT_HTML) public String getDocumentation(@Context HttpServletRequest request) { if (docs == null) { String docsPath = request.getRequestURI(); String docsPathExtension = request.getPathInfo(); String servicePath = request.getRequestURI().substring(0, docsPath.length() - docsPathExtension.length()); docs = FilesEndpointDocs.createDocumentation(servicePath); } return docs; } /** * Callback from OSGi to set the security service. * * @param securityService * the security service */ void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * Try to detect the mimetype from filename or file. * * @param fileName * the file name * @param uploadedFile * the uploaded file * @return the mimetype or <code>null</code> if no mimetype could be detected */ private String detectMimeTypeFromFile(String fileName, File uploadedFile) { String mimeType = null; if (fileName.endsWith(".ogg")) { mimeType = "video/ogg"; } else if (fileName.endsWith(".mp4")) { mimeType = "video/mp4"; } else if (fileName.endsWith(".webm")) { mimeType = "video/webm"; } else { mimeType = mimeTypeDetector.detect(fileName); } if (!StringUtils.isBlank(mimeType)) return mimeType; InputStream is = null; try { is = new FileInputStream(uploadedFile); mimeType = mimeTypeDetector.detect(is); } catch (IOException e) { logger.warn("Error detecting mime type: {}", e.getMessage()); } finally { IOUtils.closeQuietly(is); } return mimeType; } /** * Loads the files from the site's content repository. * * @param q * the search query * @return the files * @throws WebApplicationException * if the content repository is unavailable or if the content can't * be loaded */ private String loadResultSet(SearchQuery q) throws WebApplicationException { ContentRepository repository = getContentRepository(q.getSite(), false); if (repository == null) throw new WebApplicationException(Status.SERVICE_UNAVAILABLE); SearchResult result = null; try { result = repository.find(q); } catch (ContentRepositoryException e) { logger.warn(e.getMessage()); throw new WebApplicationException(); } StringBuffer buf = new StringBuffer("<files "); buf.append("hits=\"").append(result.getHitCount()).append("\" "); buf.append("offset=\"").append(result.getOffset()).append("\" "); if (q.getLimit() > 0) buf.append("limit=\"").append(result.getLimit()).append("\" "); buf.append("page=\"").append(result.getPage()).append("\" "); buf.append("pagesize=\"").append(result.getPageSize()).append("\""); buf.append(">"); for (SearchResultItem item : result.getItems()) { String xml = ((ResourceSearchResultItem) item).getResourceXml(); // TODO: Remove this hack once the importer is fixed xml = xml.replace("292278994-08-17T07:12:55Z", "2010-08-17T07:12:55Z"); buf.append(xml); } buf.append("</files>"); return buf.toString(); } /** * OSGi callback that is setting the resource serializer. * * @param serializer * the resource serializer service */ void setResourceSerializer(ResourceSerializerService serializer) { this.serializerService = serializer; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return "File rest endpoint"; } }