| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License") + you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openmeetings.web.util; |
| |
| import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey; |
| import static org.apache.openmeetings.web.app.Application.getBean; |
| import static org.apache.openmeetings.web.app.WebSession.getExternalType; |
| import static org.apache.openmeetings.web.app.WebSession.getRecordingId; |
| import static org.apache.openmeetings.web.app.WebSession.getUserId; |
| import static org.red5.logging.Red5LoggerFactory.getLogger; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.commons.io.input.BoundedInputStream; |
| import org.apache.openmeetings.db.dao.record.FlvRecordingDao; |
| import org.apache.openmeetings.db.dao.user.OrganisationUserDao; |
| import org.apache.openmeetings.db.entity.record.FlvRecording; |
| import org.apache.openmeetings.web.app.WebSession; |
| import org.apache.wicket.protocol.http.servlet.ResponseIOException; |
| import org.apache.wicket.request.Response; |
| import org.apache.wicket.request.http.WebResponse; |
| import org.apache.wicket.request.mapper.parameter.PageParameters; |
| import org.apache.wicket.request.resource.AbstractResource; |
| import org.apache.wicket.request.resource.ContentDisposition; |
| import org.apache.wicket.request.resource.IResource; |
| import org.apache.wicket.request.resource.IResource.Attributes; |
| import org.apache.wicket.request.resource.ResourceReference; |
| import org.apache.wicket.util.io.Streams; |
| import org.apache.wicket.util.lang.Bytes; |
| import org.apache.wicket.util.resource.FileResourceStream; |
| import org.apache.wicket.util.resource.IResourceStream; |
| import org.apache.wicket.util.resource.ResourceStreamNotFoundException; |
| import org.apache.wicket.util.string.StringValue; |
| import org.apache.wicket.util.time.Time; |
| import org.slf4j.Logger; |
| |
| public abstract class RecordingResourceReference extends ResourceReference { |
| private static final long serialVersionUID = 1L; |
| private static final Logger log = getLogger(RecordingResourceReference.class, webAppRootKey); |
| |
| public RecordingResourceReference(Class<? extends RecordingResourceReference> clazz) { |
| super(clazz, "recordings"); |
| } |
| |
| @Override |
| public IResource getResource() { |
| return new AbstractResource() { |
| private static final long serialVersionUID = 1L; |
| private final static String ACCEPT_RANGES_HEADER = "Accept-Ranges"; |
| private final static String RANGE_HEADER = "Range"; |
| private final static String CONTENT_RANGE_HEADER = "Content-Range"; |
| private final static String RANGES_BYTES = "bytes"; |
| private File file; |
| private boolean isRange = false; |
| private long start = 0; |
| private long end = 0; |
| |
| private long getChunkLength() { |
| return isRange ? end - start + 1 : (file == null ? -1 : file.length()); |
| } |
| |
| private IResourceStream getResourceStream() { |
| return file == null ? null : new FileResourceStream(file) { |
| private static final long serialVersionUID = 2546785247219805747L; |
| private transient BoundedInputStream bi; |
| |
| @Override |
| public InputStream getInputStream() throws ResourceStreamNotFoundException { |
| if (bi == null) { |
| //bi = new BoundedInputStream(super.getInputStream(), end + 1); |
| bi = new BoundedInputStream(super.getInputStream(), isRange ? end + 1 : (file == null ? -1 : file.length())); |
| try { |
| bi.skip(start); |
| } catch (IOException e) { |
| throw new ResourceStreamNotFoundException(e); |
| } |
| } |
| return bi; |
| } |
| |
| @Override |
| public Bytes length() { |
| return Bytes.bytes(getChunkLength()); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if (bi != null) { |
| bi.close(); //also will close original stream |
| bi = null; |
| } |
| } |
| |
| @Override |
| public String getContentType() { |
| return RecordingResourceReference.this.getContentType(); |
| } |
| }; |
| } |
| |
| @Override |
| protected void setResponseHeaders(ResourceResponse data, Attributes attributes) { |
| Response response = attributes.getResponse(); |
| if (response instanceof WebResponse) { |
| WebResponse webResponse = (WebResponse)response; |
| webResponse.setStatus(isRange ? HttpServletResponse.SC_PARTIAL_CONTENT : HttpServletResponse.SC_OK); |
| } |
| super.setResponseHeaders(data, attributes); |
| } |
| |
| @Override |
| protected ResourceResponse newResourceResponse(Attributes attributes) { |
| ResourceResponse rr = new ResourceResponse(); |
| FlvRecording r = getRecording(attributes); |
| if (r != null) { |
| isRange = false; |
| file = getFile(r); |
| rr.setFileName(getFileName(r)); |
| rr.setContentType(RecordingResourceReference.this.getContentType()); |
| rr.setContentDisposition(ContentDisposition.INLINE); |
| rr.setLastModified(Time.millis(file.lastModified())); |
| rr.getHeaders().addHeader(ACCEPT_RANGES_HEADER, RANGES_BYTES); |
| String range = ((HttpServletRequest)attributes.getRequest().getContainerRequest()).getHeader(RANGE_HEADER); |
| if (range != null && range.startsWith(RANGES_BYTES)) { |
| String[] bounds = range.substring(RANGES_BYTES.length() + 1).split("-"); |
| if (bounds != null && bounds.length > 0) { |
| long length = file.length(); |
| isRange = true; |
| start = Long.parseLong(bounds[0]); |
| end = bounds.length > 1 ? Long.parseLong(bounds[1]) : length - 1; |
| //Content-Range: bytes 229376-232468/232469 |
| rr.getHeaders().addHeader(CONTENT_RANGE_HEADER, String.format("%s %d-%d/%d", RANGES_BYTES, start, end, length)); |
| } |
| } |
| rr.setContentLength(getChunkLength()); |
| rr.setWriteCallback(new WriteCallback() { |
| @Override |
| public void writeData(Attributes attributes) throws IOException { |
| IResourceStream rStream = getResourceStream(); |
| try { |
| final Response response = attributes.getResponse(); |
| Streams.copy(rStream.getInputStream(), response.getOutputStream()); |
| } catch (ResourceStreamNotFoundException e1) { |
| } catch (ResponseIOException e) { |
| // in case of range operations we expecting such exceptions |
| if (!isRange) { |
| log.error("Error while playing the stream", e); |
| } |
| } finally { |
| rStream.close(); |
| } |
| } |
| }); |
| } else { |
| rr.setError(HttpServletResponse.SC_NOT_FOUND); |
| } |
| return rr; |
| } |
| }; |
| } |
| |
| abstract String getContentType(); |
| abstract String getFileName(FlvRecording r); |
| abstract File getFile(FlvRecording r); |
| |
| private Long getLong(StringValue id) { |
| Long result = null; |
| try { |
| result = id.toLongObject(); |
| } catch(Exception e) { |
| //no-op |
| } |
| return result; |
| } |
| |
| private FlvRecording getRecording(Long id) { |
| FlvRecording r = getBean(FlvRecordingDao.class).get(id); |
| // TODO should we process public? |
| // || r.getOwnerId() == 0 || r.getParentFileExplorerItemId() == null || r.getParentFileExplorerItemId() == 0 |
| if (r == null) { |
| return r; |
| } |
| if (r.getOwnerId() == null || getUserId() == r.getOwnerId()) { |
| return r; |
| } |
| if (r.getOrganization_id() == null || getBean(OrganisationUserDao.class).isUserInOrganization(r.getOrganization_id(), getUserId())) { |
| return r; |
| } |
| //TODO external group check was added for plugin recording download |
| String extType = getExternalType(); |
| if (extType != null && extType.equals(r.getCreator().getExternalUserType())) { |
| return r; |
| } |
| return null; |
| } |
| |
| private FlvRecording getRecording(Attributes attributes) { |
| PageParameters params = attributes.getParameters(); |
| StringValue idStr = params.get("id"); |
| Long id = getLong(idStr); |
| WebSession ws = WebSession.get(); |
| if (id != null && ws.isSignedIn()) { |
| return getRecording(id); |
| } else { |
| ws.invalidate(); |
| if (ws.signIn(idStr.toString())) { |
| return getRecording(getRecordingId()); |
| } |
| } |
| return null; |
| } |
| } |