AbstractGalleryResource.java
/*
* Copyright (C) 2000 - 2024 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "https://www.silverpeas.org/legal/floss_exception.html"
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.silverpeas.components.gallery.web;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jboss.resteasy.plugins.providers.html.View;
import org.silverpeas.components.gallery.constant.GalleryResourceURIs;
import org.silverpeas.components.gallery.constant.MediaResolution;
import org.silverpeas.components.gallery.constant.MediaType;
import org.silverpeas.components.gallery.model.AlbumDetail;
import org.silverpeas.components.gallery.model.InternalMedia;
import org.silverpeas.components.gallery.model.Media;
import org.silverpeas.components.gallery.model.MediaPK;
import org.silverpeas.components.gallery.service.GalleryService;
import org.silverpeas.components.gallery.service.MediaServiceProvider;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.io.file.SilverpeasFile;
import org.silverpeas.core.io.file.SilverpeasFileProvider;
import org.silverpeas.core.io.media.Definition;
import org.silverpeas.core.io.media.video.ThumbnailPeriod;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.core.web.http.FileResponse;
import org.silverpeas.core.web.rs.RESTWebService;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.EnumSet;
import static org.silverpeas.components.gallery.constant.GalleryResourceURIs.GALLERY_BASE_URI;
/**
* @author Yohann Chastagnier
*/
abstract class AbstractGalleryResource extends RESTWebService {
@PathParam("componentInstanceId")
private String componentInstanceId;
/*
* (non-Javadoc)
* @see com.silverpeas.web.RESTWebService#getComponentId()
*/
@Override
public String getComponentId() {
return componentInstanceId;
}
@Override
protected String getResourceBasePath() {
return GALLERY_BASE_URI;
}
/**
* Converts the album into its corresponding web entity.
* @param album the album.
* @return the corresponding photo entity.
*/
AlbumEntity asWebEntity(AlbumDetail album) {
checkNotFoundStatus(album);
AlbumEntity albumEntity = AlbumEntity.createFrom(album, getUserPreferences().getLanguage())
.withURI(getUri().getRequestUri())
.withParentURI(GalleryResourceURIs.buildAlbumURI(album.getFatherPK()));
for (Media media : album.getMedia()) {
if (hasUserMediaAccess(media)) {
albumEntity.addMedia(asWebEntity(media, album));
}
}
return albumEntity;
}
/**
* Converts the photo into its corresponding web entity.
* @param media the photo to convert.
* @param album the album of the photo.
* @return the corresponding photo entity.
*/
private AbstractMediaEntity asWebEntity(Media media, AlbumDetail album) {
final AbstractMediaEntity entity;
switch (media.getType()) {
case Photo:
entity = PhotoEntity.createFrom(media.getPhoto())
.withNormalUrl(GalleryResourceURIs.buildMediaContentURI(media, MediaResolution.NORMAL))
.withPreviewUrl(GalleryResourceURIs.buildMediaContentURI(media, MediaResolution.PREVIEW))
.withThumbUrl(URI.create(media.getApplicationThumbnailUrl(MediaResolution.SMALL)));
break;
case Video:
entity = VideoEntity.createFrom(media.getVideo())
.withThumbUrl(URI.create(media.getApplicationThumbnailUrl(MediaResolution.MEDIUM)));
break;
case Sound:
entity = SoundEntity.createFrom(media.getSound())
.withThumbUrl(URI.create(media.getApplicationThumbnailUrl(MediaResolution.MEDIUM)));
break;
case Streaming:
entity = StreamingEntity.createFrom(media.getStreaming())
.withOriginalUrl(URI.create(media.getStreaming().getHomepageUrl()))
.withThumbUrl(URI.create(media.getApplicationThumbnailUrl(MediaResolution.MEDIUM)));
break;
default:
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
if (media.getInternalMedia() != null) {
entity.withOriginalUrl(
GalleryResourceURIs.buildMediaContentURI(media, MediaResolution.ORIGINAL));
}
return entity.withURI(GalleryResourceURIs.buildMediaInAlbumURI(album, media)).withParentURI(
GalleryResourceURIs.buildAlbumURI(album));
}
/**
* Centralization of getting of media data.
* @param expectedMediaType expected media type
* @param albumId the identifier of the album in which the media must exist
* @param mediaId the identifier of the expected media
* @return the corresponding media entity.
*/
AbstractMediaEntity getMediaEntity(final MediaType expectedMediaType, final String albumId,
final String mediaId) {
try {
final AlbumDetail album = getMediaService().getAlbum(new NodePK(albumId, getComponentId()));
final Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
checkNotFoundStatus(media);
verifyUserMediaAccess(media);
verifyMediaIsInAlbum(media, album);
// Verifying the physical file exists and that the type of media is the one expected
if (media.getInternalMedia() != null) {
checkMediaExistsWithRequestedMimeType(expectedMediaType, media, MediaResolution.PREVIEW);
}
// Getting the web entity
return asWebEntity(media, album);
} catch (final WebApplicationException ex) {
throw ex;
} catch (final Exception ex) {
throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
}
}
/**
* Gets the media and verifies the user rights.
* @param expectedMediaType expected media type
* @param mediaId the media identifier
* @param requestedMediaResolution requested media resolution
* @param size a specific size applied on requested resolution
* @return the requested media.
*/
private Pair<Media, SilverpeasFile> getCheckedMedia(final MediaType expectedMediaType,
final String mediaId, final MediaResolution requestedMediaResolution, final String size) {
try {
final Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
checkNotFoundStatus(media);
verifyUserMediaAccess(media);
// Adjusting the resolution according to the user rights
MediaResolution mediaResolution = getUserMediaResolution(requestedMediaResolution, media);
// Verifying the physical file exists and that the type of media is the one expected
final SilverpeasFile file = media.getFile(mediaResolution, size);
if (!file.exists() || expectedMediaType != media.getType()) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return Pair.of(media, file);
} catch (final WebApplicationException ex) {
throw ex;
} catch (final Exception ex) {
throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
}
}
/**
* Gets the user resolution according to its rights and the resolution requested.
* @param requestedMediaResolution the requested media resolution.
* @param media the media.
* @return a {@link MediaResolution} instance.
*/
private MediaResolution getUserMediaResolution(final MediaResolution requestedMediaResolution,
final Media media) {
MediaResolution mediaResolution = MediaResolution.ORIGINAL;
if (media.getType().isPhoto()) {
mediaResolution = requestedMediaResolution;
if (MediaResolution.ORIGINAL == requestedMediaResolution && !isUserPrivileged() &&
!media.isDownloadable()) {
mediaResolution = MediaResolution.PREVIEW;
}
}
return mediaResolution;
}
/**
* Centralization of getting of media content.
* @param expectedMediaType expected media type
* @param mediaId the media identifier
* @param requestedMediaResolution requested media resolution
* @param size a specific size applied on requested resolution
* @return the response.
*/
Response getMediaContent(final MediaType expectedMediaType, final String mediaId,
final MediaResolution requestedMediaResolution, final String size) {
try {
final Pair<Media, SilverpeasFile> checkedMedia =
getCheckedMedia(expectedMediaType, mediaId, requestedMediaResolution, size);
final Media media = checkedMedia.getLeft();
final SilverpeasFile file = checkedMedia.getRight();
return FileResponse.fromRest(getHttpServletRequest(), getHttpServletResponse())
.forceMimeType(((InternalMedia) media).getFileMimeType().getMimeType())
.forceFileId(mediaId)
.silverpeasFile(file)
.build();
} catch (final WebApplicationException ex) {
throw ex;
} catch (final Exception ex) {
throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
}
}
/**
* Centralization of getting of media embed.
* @param expectedMediaType expected media type
* @param mediaId the media identifier
* @param requestedMediaResolution requested media resolution
*/
View getMediaEmbed(final MediaType expectedMediaType, final String mediaId,
final MediaResolution requestedMediaResolution) {
try {
final Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
checkNotFoundStatus(media);
verifyUserMediaAccess(media);
// Adjusting the resolution according to the user rights
MediaResolution mediaResolution = MediaResolution.PREVIEW;
if (requestedMediaResolution.getWidth() != null) {
mediaResolution = requestedMediaResolution;
}
// Verifying the physical file exists and that the type of media is the one expected
checkMediaExistsWithRequestedMimeType(expectedMediaType, media, mediaResolution);
final Definition definition;
MediaType mediaType = media.getType();
if (mediaType == MediaType.Video) {
definition = media.getVideo().getDefinition();
} else {
definition = Definition.of(mediaResolution.getWidth(), mediaResolution.getHeight());
}
getHttpServletRequest().setAttribute("mediaUrl", media.getApplicationOriginalUrl());
getHttpServletRequest().setAttribute("posterUrl", media.getApplicationThumbnailUrl(mediaResolution));
getHttpServletRequest().setAttribute("playerType", media.getType().getName());
getHttpServletRequest().setAttribute("mimeType", media.getInternalMedia().getFileMimeType().getMimeType());
getHttpServletRequest().setAttribute("definition", definition);
getHttpServletRequest().setAttribute("backgroundColor", getHttpServletRequest().getParameter("backgroundColor"));
getHttpServletRequest().setAttribute("autoPlay", getHttpServletRequest().getParameter("autoPlay"));
return new View("/media/jsp/embed.jsp");
} catch (final WebApplicationException ex) {
throw ex;
} catch (final Exception ex) {
throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
}
}
/**
* Verifies the physical file exists and that the type of media is the one expected.
* @param requestedMediaType the requested media type (from the request).
* @param media the media to verify.
* @param mediaResolution the media resolution to get (from the service).
*/
private void checkMediaExistsWithRequestedMimeType(final MediaType requestedMediaType,
final Media media, final MediaResolution mediaResolution) {
final SilverpeasFile file = media.getFile(mediaResolution);
if (!file.exists() || requestedMediaType != media.getType()) {
throw new WebApplicationException(Status.NOT_FOUND);
}
}
/**
* Centralization of getting video media thumbnail.
* @param mediaId the media identifier
* @param thumbnailId the thumbnail identifier
* @param sizeDirective the size directive with pattern (optional)
* @return the response.
*/
Response getMediaThumbnail(final String mediaId,
final String thumbnailId, final String sizeDirective) {
try {
final Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
checkNotFoundStatus(media);
verifyUserMediaAccess(media);
// Verifying the physical file exists
String filename = ThumbnailPeriod.fromIndex(thumbnailId).getFilename();
if (StringUtil.isDefined(sizeDirective)) {
filename = sizeDirective + "/" + filename;
}
final SilverpeasFile thumbFile =
SilverpeasFileProvider.getFile(FileUtils
.getFile(Media.BASE_PATH.getPath(), media.getComponentInstanceId(),
media.getWorkspaceSubFolderName(), filename).getPath());
if (!thumbFile.exists()) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return loadFileContent(thumbFile)
.header("Content-Type", thumbFile.getMimeType())
.header("Content-Length", thumbFile.length())
.header("Content-Disposition", "inline; filename=\"" + thumbFile.getName() + "\"")
.build();
} catch (final WebApplicationException ex) {
throw ex;
} catch (final Exception ex) {
throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
}
}
/**
* Indicates if the current user is a privileged one.
* @return true if user has privileged
*/
private boolean isUserPrivileged() {
Collection<SilverpeasRole> userRoles = getUserRoles();
return EnumSet.of(SilverpeasRole.ADMIN, SilverpeasRole.PUBLISHER, SilverpeasRole.WRITER,
SilverpeasRole.PRIVILEGED_USER).stream().anyMatch(userRoles::contains);
}
/**
* Centralization
* @param object any object
*/
void checkNotFoundStatus(Object object) {
boolean isNotFound = false;
if (object == null) {
isNotFound = true;
} else if (object instanceof Media) {
Media media = (Media) object;
switch (media.getType()) {
case Photo:
isNotFound = media.getPhoto() == null;
break;
case Video:
isNotFound = media.getVideo() == null;
break;
case Sound:
isNotFound = media.getSound() == null;
break;
case Streaming:
isNotFound = media.getStreaming() == null;
break;
default:
isNotFound = true;
}
}
if (isNotFound) {
throw new WebApplicationException(Status.NOT_FOUND);
}
}
/**
* Centralization
* @param media the media to check access
* @return true if user has media access
*/
private boolean hasUserMediaAccess(Media media) {
return media.canBeAccessedBy(getUser());
}
/**
* Verifying that the authenticated user is authorized to view the given media.
* @param media a media for which the access has to be verified.
* @throws javax.ws.rs.WebApplicationException if user is not authorized to view the media
*/
void verifyUserMediaAccess(Media media) {
if (!hasUserMediaAccess(media)) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
}
/**
* @throws javax.ws.rs.WebApplicationException if the given media is not included in the given
* album.
*/
private void verifyMediaIsInAlbum(Media media, AlbumDetail album) {
if (!album.getMedia().contains(media)) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
}
private Response.ResponseBuilder loadFileContent(final File file) {
StreamingOutput streamingOutput = output -> {
try (final InputStream mediaStream = FileUtils.openInputStream(file)) {
IOUtils.copy(mediaStream, output);
} catch (IOException e) {
throw new WebApplicationException(e, Status.NOT_FOUND);
}
};
return Response.ok(streamingOutput);
}
/**
* @return gallery media service layer
*/
GalleryService getMediaService() {
return MediaServiceProvider.getMediaService();
}
}