- build get recordings api response

This commit is contained in:
Richard Alam 2017-03-13 21:43:39 +00:00
parent 73b16db384
commit 72c42f9d70
15 changed files with 415 additions and 76 deletions

View File

@ -18,4 +18,27 @@ public class Breakout {
@JacksonXmlProperty(isAttribute = true)
private String meetingId;
public void setParentMeetingId(String parentMeetingId) {
this.parentMeetingId = parentMeetingId;
}
public String getParentMeetingId() {
return parentMeetingId;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public int getSequence() {
return sequence;
}
public void setMeetingId(String meetingId) {
this.meetingId = meetingId;
}
public String getMeetingId() {
return meetingId;
}
}

View File

@ -0,0 +1,53 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public class MeetingInfo {
/**
* <meeting id="random-2810069" name="random-2810069" breakout="false"/>
*/
@JacksonXmlProperty(isAttribute = true)
private String id;
@JacksonXmlProperty(isAttribute = true)
private String externalId;
@JacksonXmlProperty(isAttribute = true)
private String name;
@JacksonXmlProperty(isAttribute = true)
private boolean breakout;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public String getExternalId() {
return externalId;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setBreakout(boolean breakout) {
this.breakout = breakout;
}
public boolean isBreakout() {
return breakout;
}
}

View File

@ -8,11 +8,11 @@ public class Preview {
@JacksonXmlProperty(localName = "image")
private Image[] images;
// public void setImages(Image[] images) {
// this.images = images;
// }
public void setImages(Image[] images) {
this.images = images;
}
// public Image[] getImages() {
// return images;
// }
public Image[] getImages() {
return images;
}
}

View File

@ -48,6 +48,12 @@ public class RecordingMetadata {
@JacksonXmlProperty(localName = "end_time")
private String endTime;
@JacksonXmlProperty(localName = "participants")
private int participants;
@JacksonXmlProperty(localName = "meeting")
private MeetingInfo meetingInfo;
private Breakout breakout;
@JacksonXmlElementWrapper(localName = "breakoutRooms")
@ -98,6 +104,22 @@ public class RecordingMetadata {
return endTime;
}
public void setParticipants(int participants) {
this.participants = participants;
}
public int getParticipants() {
return participants;
}
public void setMeeting(MeetingInfo meetingInfo) {
this.meetingInfo = meetingInfo;
}
public MeetingInfo getMeeting() {
return meetingInfo;
}
public void setBreakout(Breakout breakout) {
this.breakout = breakout;
}
@ -106,6 +128,14 @@ public class RecordingMetadata {
return breakout;
}
public void setBreakoutRooms(BreakoutRoom[] breakoutRooms) {
this.breakoutRooms = breakoutRooms;
}
public BreakoutRoom[] getBreakoutRooms() {
return breakoutRooms;
}
public void setMeta(Metadata meta) {
this.meta = meta;
}

View File

@ -1,10 +1,14 @@
package org.bigbluebutton.api.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.bigbluebutton.api.domain.RecordingMetadata;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
public class RecordingMetadataReaderHelper {
@ -18,4 +22,30 @@ public class RecordingMetadataReaderHelper {
br.close();
return sb.toString();
}
public static RecordingMetadata getRecordingMetadata(File metadataXml) {
XMLInputFactory factory = XMLInputFactory.newInstance();
JacksonXmlModule module = new JacksonXmlModule();
// and then configure, for example:
module.setDefaultUseWrapper(false);
XmlMapper mapper = new XmlMapper(module);
//Reading from xml file and creating XMLStreamReader
XMLStreamReader reader = null;
RecordingMetadata recMeta = null;
try {
reader = factory.createXMLStreamReader(new FileInputStream(metadataXml));
recMeta = mapper.readValue(reader, RecordingMetadata.class);
} catch (XMLStreamException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return recMeta;
}
}

View File

@ -9,6 +9,7 @@ import java.io.StringWriter;
import java.util.*;
import freemarker.template.*;
import org.bigbluebutton.api.domain.RecordingMetadata;
public class ResponseBuilder {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
@ -94,4 +95,30 @@ public class ResponseBuilder {
return xmlText.toString();
}
public String buildGetRecordingsResponse(List<RecordingMetadata> recordings, String returnCode) {
Template ftl = null;
try {
ftl = cfg.getTemplate("get-recordings.ftl");
} catch (IOException e) {
e.printStackTrace();
}
StringWriter xmlText = new StringWriter();
Map root = new HashMap();
root.put("returnCode", returnCode);
root.put("recordings", recordings);
try {
ftl.process(root, xmlText);
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return xmlText.toString();
}
}

View File

@ -4,6 +4,8 @@
<published>true</published>
<start_time>1489173065780</start_time>
<end_time>1489173199386</end_time>
<participants>10</participants>
<meeting id="f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374" externalId="random-2810069" name="random-2810069" breakout="false"/>
<breakout parentMeetingId="f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374" sequence="2" meetingId="f2041d123b6a4b994e7ad87ee9d348496a73472c-1489173065780"/>
<breakoutRooms>
<breakoutRoom>32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780</breakoutRoom>

View File

@ -0,0 +1,68 @@
<#-- GET_RECORDINGS FreeMarker XML template -->
<#compress>
<response>
<#-- Where code is a 'SUCCESS' or 'FAILED' String -->
<returncode>${returnCode}</returncode>
<recordings>
<#list recordings as r>
<recording>
<recordID>${r.getId()}</recordID>
<meetingID>${r.getMeeting().getId()?html}</meetingID>
<externalMeetingID>${r.getMeeting().getExternalId()?html}</externalMeetingID>
<name><![CDATA[${r.getMeeting().getName()}]]></name>
<isBreakout>${r.getMeeting().isBreakout()?c}</isBreakout>
<published>${r.getPublished()?string}</published>
<state>${r.getState()?string}</state>
<startTime><#if r.getStartTime()?? && r.getStartTime() != "">${r.getStartTime()}</#if></startTime>
<endTime><#if r.getEndTime()?? && r.getEndTime() != "">${r.getEndTime()}</#if></endTime>
<participants><#if r.getParticipants()??>${r.getParticipants()}</#if></participants>
<#if r.getBreakout()??>
<#assign breakout = r.getBreakout()>
<breakout>
<parentId>${breakout.getParentMeetingId()}</parentId>
<sequence>${breakout.getSequence()?c}</sequence>
</breakout>
</#if>
<#if r.getBreakoutRooms()??>
<#list r.getBreakoutRooms()>
<breakoutRooms>
<#items as broom>
<breakoutRoom>${broom.getValue()}</breakoutRoom>
</#items>
</breakoutRooms>
</#list>
</#if>
<#assign m = r.getMeta().get()>
<metadata>
<#list m?keys as prop>
<${prop}><![CDATA[${m[prop]}]]></${prop}>
</#list>
</metadata>
<#assign pb = r.getPlayback()>
<playback>
<format>${pb.getFormat()}</format>
<link>${pb.getLink()}</link>
<processingTime>${pb.getProcessingTime()?c}</processingTime>
<duration>${pb.getDuration()?c}</duration>
<#if pb.getExtensions()??>
<extensions>
<#if pb.getExtensions().getPreview()??>
<#assign prev = pb.getExtensions().getPreview()>
<preview>
<#list prev.getImages()>
<images>
<#items as image>
<image width="${image.getWidth()}" height="${image.getHeight()}" alt="${image.getAlt()}">${image.getValue()}</image>
</#items>
</images>
</#list>
</preview>
</#if>
</extensions>
</#if>
</playback>
</recording>
</#list>
</recordings>
</response>
</#compress>

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.api.util
import java.io.File
import java.util
import org.bigbluebutton.api.domain.{Meeting, User}
import org.bigbluebutton.api.domain.{Meeting, RecordingMetadata, User}
import org.scalatest._
class ResponseBuilderTest extends UnitSpec {
@ -182,4 +182,18 @@ class ResponseBuilderTest extends UnitSpec {
assert(templateLoc.exists())
}
it should "reply to getRecordings api call" in {
val templateLoc = new File("src/test/resources")
val builder = new ResponseBuilder(templateLoc)
val metadataXml = new File("src/test/resources/breakout-room-metadata.xml")
val recMeta = RecordingMetadataReaderHelper.getRecordingMetadata(metadataXml);
val recList = new util.ArrayList[RecordingMetadata]()
recList.add(recMeta)
def response = builder.buildGetRecordingsResponse(recList, "success")
println(response)
assert(templateLoc.exists())
}
}

View File

@ -19,6 +19,7 @@
package org.bigbluebutton.web.controllers
import com.google.gson.Gson
import org.bigbluebutton.api.domain.RecordingMetadata
import org.bigbluebutton.api.util.ResponseBuilder
import javax.servlet.ServletRequest;
@ -1735,6 +1736,8 @@ class ApiController {
return
}
log.debug request.getQueryString()
// Do we agree on the checksum? If not, complain.
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
@ -1764,8 +1767,12 @@ class ApiController {
internalRecordIds = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingIds);
}
Map<String,Recording> recs = meetingService.getRecordings(internalRecordIds, states);
recs = meetingService.filterRecordingsByMetadata(recs, ParamsProcessorUtil.processMetaParam(params));
for(String intRecId : internalRecordIds){
log.debug intRecId
}
List<RecordingMetadata> recsList = meetingService.getRecordingsMetadata(internalRecordIds, states);
List<RecordingMetadata>recs = meetingService.filterRecordingsByMetadata(recsList, ParamsProcessorUtil.processMetaParam(params));
if (recs.isEmpty()) {
response.addHeader("Cache-Control", "no-cache")
@ -1784,20 +1791,13 @@ class ApiController {
return;
}
def templateLoc = getServletContext().getRealPath("/WEB-INF/freemarker")
ResponseBuilder responseBuilder = new ResponseBuilder(new File(templateLoc))
def cfg = new Configuration()
// Load the XML template
// TODO: Maybe there is a better way to define the templates path
def wtl = new WebappTemplateLoader(getServletContext(), "/WEB-INF/freemarker")
cfg.setTemplateLoader(wtl)
def ftl = cfg.getTemplate("get-recordings.ftl")
def xmlText = new StringWriter()
ftl.process([code:RESP_CODE_SUCCESS, recs:recs.values()], xmlText)
def xmlText = responseBuilder.buildGetRecordingsResponse(recsList, "success")
withFormat {
xml {
render(text: xmlText.toString(), contentType: "text/xml")
render(text: xmlText, contentType: "text/xml")
}
}
}

View File

@ -37,11 +37,8 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.Playback;
import org.bigbluebutton.api.domain.Recording;
import org.bigbluebutton.api.domain.User;
import org.bigbluebutton.api.domain.UserSession;
import org.bigbluebutton.api.domain.*;
import org.bigbluebutton.api.messaging.MessageListener;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.api.messaging.messages.CreateBreakoutRoom;
@ -425,18 +422,30 @@ public class MeetingService implements MessageListener {
return null;
}
public List<RecordingMetadata> getRecordingsMetadata(List<String> idList, List<String> states) {
List<RecordingMetadata> recsList = recordingService.getRecordingsMetadata(idList, states);
return recsList;
}
public Map<String, Recording> getRecordings(List<String> idList, List<String> states) {
List<Recording> recsList = recordingService.getRecordings(idList, states);
Map<String, Recording> recs = reorderRecordings(recsList);
return recs;
}
public Map<String, Recording> filterRecordingsByMetadata(
Map<String, Recording> recordings,
Map<String, String> metadataFilters) {
public List<RecordingMetadata> filterRecordingsByMetadata(List<RecordingMetadata> recsList,
Map<String, String> metadataFilters) {
return recordingService.filterRecordingsByMetadata(recsList, metadataFilters);
}
public Map<String, Recording> filterRecordingsByMetadata(Map<String, Recording> recordings,
Map<String, String> metadataFilters) {
return recordingService.filterRecordingsByMetadata(recordings, metadataFilters);
}
public Map<String, Recording> reorderRecordings(List<Recording> olds) {
Map<String, Recording> map = new HashMap<String, Recording>();
for (Recording r : olds) {

View File

@ -34,6 +34,8 @@ import java.util.Map;
import java.util.Set;
import org.bigbluebutton.api.domain.Recording;
import org.bigbluebutton.api.domain.RecordingMetadata;
import org.bigbluebutton.api.util.RecordingMetadataReaderHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -64,6 +66,38 @@ public class RecordingService {
}
}
public List<RecordingMetadata> getRecordingsMetadata(List<String> recordIDs, List<String> states) {
List<RecordingMetadata> recs = new ArrayList<RecordingMetadata>();
Map<String, List<File>> allDirectories = getAllDirectories(states);
if (recordIDs.isEmpty()) {
for (Map.Entry<String, List<File>> entry : allDirectories.entrySet()) {
recordIDs.addAll(getAllRecordingIds(entry.getValue()));
}
}
for (String recordID : recordIDs) {
for (Map.Entry<String, List<File>> entry : allDirectories.entrySet()) {
List<File> _recs = getRecordingsForPath(recordID, entry.getValue());
Iterator<File> iterator = _recs.iterator();
while (iterator.hasNext()) {
RecordingMetadata r = getRecordingMetadata(iterator.next());
if (r != null) {
recs.add(r);
}
}
}
}
return recs;
}
private RecordingMetadata getRecordingMetadata(File dir) {
File file = new File(dir.getPath() + File.separatorChar + "metadata.xml");
RecordingMetadata rec = RecordingMetadataReaderHelper.getRecordingMetadata(file);
return rec;
}
public List<Recording> getRecordings(List<String> recordIDs, List<String> states) {
List<Recording> recs = new ArrayList<Recording>();
@ -119,6 +153,45 @@ public class RecordingService {
return matchesMetadata;
}
public boolean recordingMatchesMetadata(RecordingMetadata recording, Map<String, String> metadataFilters) {
boolean matchesMetadata = true;
for (Map.Entry<String, String> filter : metadataFilters.entrySet()) {
String metadataValue = recording.getMeta().get().get(filter.getKey());
if ( metadataValue == null ) {
// The recording doesn't have metadata specified
matchesMetadata = false;
} else {
String filterValue = filter.getValue();
if( filterValue.charAt(0) == '%' && filterValue.charAt(filterValue.length()-1) == '%' && metadataValue.contains(filterValue.substring(1, filterValue.length()-1)) ){
// Filter value embraced by two wild cards
// AND the filter value is part of the metadata value
} else if( filterValue.charAt(0) == '%' && metadataValue.endsWith(filterValue.substring(1, filterValue.length())) ) {
// Filter value starts with a wild cards
// AND the filter value ends with the metadata value
} else if( filterValue.charAt(filterValue.length()-1) == '%' && metadataValue.startsWith(filterValue.substring(0, filterValue.length()-1)) ) {
// Filter value ends with a wild cards
// AND the filter value starts with the metadata value
} else if( metadataValue.equals(filterValue) ) {
// Filter value doesnt have wildcards
// AND the filter value is the same as metadata value
} else {
matchesMetadata = false;
}
}
}
return matchesMetadata;
}
public List<RecordingMetadata> filterRecordingsByMetadata(List<RecordingMetadata> recordings, Map<String, String> metadataFilters) {
List<RecordingMetadata> resultRecordings = new ArrayList<RecordingMetadata>();
for (RecordingMetadata entry : recordings) {
if (recordingMatchesMetadata(entry, metadataFilters))
resultRecordings.add(entry);
}
return resultRecordings;
}
public Map<String, Recording> filterRecordingsByMetadata(Map<String, Recording> recordings, Map<String, String> metadataFilters) {
Map<String, Recording> resultRecordings = new HashMap<String, Recording>();
for (Map.Entry<String, Recording> entry : recordings.entrySet()) {

View File

@ -2,62 +2,64 @@
<#compress>
<response>
<#-- Where code is a 'SUCCESS' or 'FAILED' String -->
<returncode>${code}</returncode>
<returncode>${returnCode}</returncode>
<recordings>
<#-- Where recs is a String -> Recording HashMap -->
<#list recs as r>
<#list recordings as r>
<recording>
<recordID>${r.getId()}</recordID>
<meetingID><#if r.getMeetingID()?? && r.getMeetingID() != "">${r.getMeetingID()?html}</#if></meetingID>
<name><#if r.getName()?? && r.getName() != ""><![CDATA[${r.getName()}]]></#if></name>
<published>${r.isPublished()?string}</published>
<meetingID>${r.getMeeting().getId()?html}</meetingID>
<externalMeetingID>${r.getMeeting().getExternalId()?html}</externalMeetingID>
<name><![CDATA[${r.getMeeting().getName()}]]></name>
<isBreakout>${r.getMeeting().isBreakout()?c}</isBreakout>
<published>${r.getPublished()?string}</published>
<state>${r.getState()?string}</state>
<startTime><#if r.getStartTime()?? && r.getStartTime() != "">${r.getStartTime()}</#if></startTime>
<endTime><#if r.getEndTime()?? && r.getEndTime() != "">${r.getEndTime()}</#if></endTime>
<participants><#if r.getNumParticipants()??>${r.getNumParticipants()}</#if></participants>
<#assign m = r.getMetadata()>
<participants><#if r.getParticipants()??>${r.getParticipants()}</#if></participants>
<#if r.getBreakout()??>
<#assign breakout = r.getBreakout()>
<breakout>
<parentId>${breakout.getParentMeetingId()}</parentId>
<sequence>${breakout.getSequence()?c}</sequence>
</breakout>
</#if>
<#if r.getBreakoutRooms()??>
<#list r.getBreakoutRooms()>
<breakoutRooms>
<#items as broom>
<breakoutRoom>${broom.getValue()}</breakoutRoom>
</#items>
</breakoutRooms>
</#list>
</#if>
<#assign m = r.getMeta().get()>
<metadata>
<#list m?keys as prop>
<${prop}><![CDATA[${m[prop]}]]></${prop}>
</#list>
</metadata>
<#assign pb = r.getPlayback()>
<playback>
<#if r.getPlaybacks()??>
<#list r.getPlaybacks() as p>
<#if p?? && p.getFormat()??>
<format>
<type>${p.getFormat()}</type>
<url>${p.getUrl()}</url>
<length>${p.getLength()}</length>
<#if p.getExtensions()??>
<#list p.getExtensions() as extension>
<${extension.getType()}>
<#assign properties = extension.getProperties()>
<#if extension.getType() == "preview">
<#list properties?keys as property>
<#if property == "images">
<${property}>
<#if properties[property]["image"]?? && properties[property]["image"]?is_hash>
<#assign image = properties[property]["image"]>
<image <#if image["attributes"]?? && image["attributes"]["width"]??>width="${image["attributes"]["width"]}"</#if> <#if image["attributes"]?? && image["attributes"]["height"]??>height="${image["attributes"]["height"]}"</#if> <#if image["attributes"]?? && image["attributes"]["alt"]??>alt="<#escape x as x?xml>${image["attributes"]["alt"]}</#escape>"</#if>>${image["text"]}</image>
<#elseif properties[property]["image"]?? && properties[property]["image"]?is_enumerable>
<#list properties[property]["image"] as image>
<image <#if image["attributes"]?? && image["attributes"]["width"]??>width="${image["attributes"]["width"]}"</#if> <#if image["attributes"]?? && image["attributes"]["height"]??>height="${image["attributes"]["height"]}"</#if> <#if image["attributes"]?? && image["attributes"]["alt"]??>alt="<#escape x as x?xml>${image["attributes"]["alt"]}</#escape>"</#if>>${image["text"]}</image>
</#list>
</#if>
</${property}>
<#else>
<${property} />
</#if>
</#list>
</#if>
</${extension.getType()}>
</#list>
</#if>
</format>
</#if>
</#list>
</#if>
<format>${pb.getFormat()}</format>
<link>${pb.getLink()}</link>
<processingTime>${pb.getProcessingTime()?c}</processingTime>
<duration>${pb.getDuration()?c}</duration>
<#if pb.getExtensions()??>
<extensions>
<#if pb.getExtensions().getPreview()??>
<#assign prev = pb.getExtensions().getPreview()>
<preview>
<#list prev.getImages()>
<images>
<#items as image>
<image width="${image.getWidth()}" height="${image.getHeight()}" alt="${image.getAlt()}">${image.getValue()}</image>
</#items>
</images>
</#list>
</preview>
</#if>
</extensions>
</#if>
</playback>
</recording>
</#list>

View File

@ -191,6 +191,8 @@ module BigBlueButton
MODULE = 'module'
EVENTNAME = 'eventName'
MEETINGID = 'meetingId'
MEETINGNAME = 'meetingName'
ISBREAKOUT = 'isBreakout'
def initialize(redis)
@redis = redis
@ -206,6 +208,7 @@ module BigBlueButton
if (meeting_metadata != nil)
xml.recording(:meeting_id => meeting_id, :bbb_version => version) {
xml.meeting(:id => meeting_id, :externalId => meeting_metadata[MEETINGID], :name => meeting_metadata[MEETINGNAME], :breakout => meeting_metadata[ISBREAKOUT])
xml.metadata(meeting_metadata)
if (@redis.has_breakout_metadata_for(meeting_id))

View File

@ -109,10 +109,15 @@ if not FileTest.directory?(target_dir)
end_time = recording.at_xpath("end_time")
end_time.content = real_end_time
if (meeting_xpath != nil)
recording << meeting_xpath
end
## Copy the breakout and breakout rooms node from
## events.xml if present.
breakout_xpath = @doc.xpath("//breakout")
breakout_rooms_xpath = @doc.xpath("//breakoutRooms")
meeting_xpath = @doc.xpath("//meeting")
if (breakout_xpath != nil)
recording << breakout_xpath