oracle-support
@@ -97,6 +106,54 @@
+
+
+
+
+ com.google.apis
+ google-api-services-youtube
+ ${project.youtube.version}
+
+
+ com.google.http-client
+ google-http-client-jackson2
+ 1.42.3
+
+
+ com.google.oauth-client
+ google-oauth-client-jetty
+ 1.34.1
+
+
+ com.google.api-client
+ google-api-client
+ 2.2.0
+
+
+ com.google.http-client
+ google-http-client
+ 1.42.3
+
+
+ com.google.oauth-client
+ google-oauth-client
+ 1.34.1
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
+
+ com.google.guava
+ guava
+ 31.1-jre
+
+
+ org.jsoup
+ jsoup
+ 1.15.4
+
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoDeleteTask.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoDeleteTask.java
new file mode 100644
index 000000000000..72a83c3c4da4
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoDeleteTask.java
@@ -0,0 +1,45 @@
+package ar.edu.unlp.sedici.dspace.curation;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.core.ConfigurationManager;
+import org.dspace.core.Context;
+import org.dspace.curate.AbstractCurationTask;
+import org.dspace.curate.Curator;
+import org.dspace.eperson.EPerson;
+import org.dspace.utils.DSpace;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import ar.edu.unlp.sedici.dspace.uploader.VideoUploaderServiceImpl;
+
+public class VideoDeleteTask extends AbstractCurationTask {
+
+ private int status;
+
+ private static final Logger log = Logger.getLogger(VideoDeleteTask.class);
+
+ @Override
+ public int perform(DSpaceObject dso) throws IOException {
+
+ status = Curator.CURATE_SKIP;
+ Item item = (Item) dso;
+
+
+ VideoUploaderServiceImpl vuploader = new DSpace().getSingletonService(VideoUploaderServiceImpl.class);
+ try {
+ vuploader.removeContent(item);
+ status = Curator.CURATE_SUCCESS;
+ } catch (IOException e) {
+ log.error("IO error in the delete of the item with ID "+item.getID()+": "+e.getMessage(),e);
+ throw e;
+ }
+
+ return status;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUpdaterTask.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUpdaterTask.java
new file mode 100644
index 000000000000..5e915f65f4ab
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUpdaterTask.java
@@ -0,0 +1,45 @@
+package ar.edu.unlp.sedici.dspace.curation;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.core.ConfigurationManager;
+import org.dspace.core.Context;
+import org.dspace.curate.AbstractCurationTask;
+import org.dspace.curate.Curator;
+import org.dspace.eperson.EPerson;
+import org.dspace.utils.DSpace;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import ar.edu.unlp.sedici.dspace.uploader.VideoUploaderServiceImpl;
+
+public class VideoUpdaterTask extends AbstractCurationTask {
+
+ private int status;
+
+ private static final Logger log = Logger.getLogger(VideoUpdaterTask.class);
+
+ @Override
+ public int perform(DSpaceObject dso) throws IOException {
+
+ status = Curator.CURATE_SKIP;
+ Item item = (Item) dso;
+
+
+ VideoUploaderServiceImpl vuploader = new DSpace().getSingletonService(VideoUploaderServiceImpl.class);
+ try {
+ vuploader.modifyContent(item);
+ status = Curator.CURATE_SUCCESS;
+ } catch (IOException e) {
+ log.error("IO error in the update of the item with ID "+item.getID()+": "+e.getMessage(),e);
+ throw e;
+ }
+
+ return status;
+ }
+
+}
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUploaderTask.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUploaderTask.java
new file mode 100644
index 000000000000..f720e21c02d1
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUploaderTask.java
@@ -0,0 +1,45 @@
+package ar.edu.unlp.sedici.dspace.curation;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.core.ConfigurationManager;
+import org.dspace.core.Context;
+import org.dspace.curate.AbstractCurationTask;
+import org.dspace.curate.Curator;
+import org.dspace.eperson.EPerson;
+import org.dspace.utils.DSpace;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import ar.edu.unlp.sedici.dspace.uploader.VideoUploaderServiceImpl;
+
+public class VideoUploaderTask extends AbstractCurationTask {
+
+ private int status;
+
+ private static final Logger log = Logger.getLogger(VideoUploaderTask.class);
+
+ @Override
+ public int perform(DSpaceObject dso) throws IOException {
+
+ status = Curator.CURATE_SKIP;
+ Item item = (Item) dso;
+
+
+ VideoUploaderServiceImpl vuploader = new DSpace().getSingletonService(VideoUploaderServiceImpl.class);
+ try {
+ vuploader.uploadContent(item);
+ status = Curator.CURATE_SUCCESS;
+ } catch (IOException e) {
+ log.error("IO error in the upload of the item with ID "+item.getID()+": "+e.getMessage(),e);
+ throw e;
+ }
+
+ return status;
+ }
+
+}
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/ContentUploaderService.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/ContentUploaderService.java
new file mode 100644
index 000000000000..71266ef96323
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/ContentUploaderService.java
@@ -0,0 +1,14 @@
+package ar.edu.unlp.sedici.dspace.uploader;
+
+import java.lang.Throwable;
+import org.dspace.content.Item;
+
+
+public interface ContentUploaderService {
+
+ void uploadContent(Item item) throws Throwable;
+ void removeContent(Item item) throws Throwable;
+ void modifyContent(Item item) throws Throwable;
+
+}
+
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/SolrServiceYoutubeBitstreamPlugin.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/SolrServiceYoutubeBitstreamPlugin.java
new file mode 100644
index 000000000000..bdfedfd61628
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/SolrServiceYoutubeBitstreamPlugin.java
@@ -0,0 +1,82 @@
+
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package ar.edu.unlp.sedici.dspace.uploader;
+
+import org.apache.solr.common.SolrInputDocument;
+import org.dspace.content.Bitstream;
+import org.dspace.content.Bundle;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.content.DSpaceObject;
+import org.dspace.discovery.SolrServiceIndexPlugin;
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * Adds filenames and file descriptions of all files in the ORIGINAL bundle
+ * to the Solr search index.
+ *
+ *
+ * To activate the plugin, add the following line to discovery.xml
+ *
+ * {@code }
+ *
+ *
+ *
+ * After activating the plugin, rebuild the discovery index by executing:
+ *
+ * [dspace]/bin/dspace index-discovery -b
+ *
+ *
+ */
+
+
+public class SolrServiceYoutubeBitstreamPlugin implements SolrServiceIndexPlugin{
+ private static final String BUNDLE_NAME = "ORIGINAL";
+ private static final String SOLR_FIELD_NAME_FOR_YOUTUBEID = "original_bundle_youtubeid";
+ private static final Logger log = Logger.getLogger(VideoUploaderServiceImpl.class);
+
+
+ @Override
+ public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) {
+ try{
+ if (dso instanceof Item) {
+ /*Bitstream bitstream = ((Bitstream) dso);
+ Bundle[] bundlesi = null;
+ Bundle[] bundlesb = bitstream.getBundles();
+ for (Bundle bundle : bundlesb){
+ if((bundle.getName() != null) && bundle.getName().equals(BUNDLE_NAME)){
+ Item[] item = bundle.getItems();
+ bundlesi = item[0].getBundles();
+
+ }
+ }*/
+ Item item= (Item) dso;
+ Bundle[] bundles = item.getBundles();
+ if (bundles != null ){
+ for (Bundle bundle:bundles){
+ if((bundle.getName() != null) && bundle.getName().equals(BUNDLE_NAME)){
+ Bitstream[] bitstreams = bundle.getBitstreams();
+ if (bitstreams != null) {
+ for (Bitstream bstream : bitstreams) {
+ String youtubeId = bstream.getMetadata("sedici.identifier.youtubeId");
+ if (youtubeId != null){
+ document.addField(SOLR_FIELD_NAME_FOR_YOUTUBEID, youtubeId);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }catch(Exception e){
+ log.error(e.getMessage(), e);
+ }
+ }
+}
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/UploadExeption.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/UploadExeption.java
new file mode 100644
index 000000000000..fd389d011c7c
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/UploadExeption.java
@@ -0,0 +1,24 @@
+package ar.edu.unlp.sedici.dspace.uploader;
+
+import java.io.IOException;
+
+public class UploadExeption extends IOException {
+
+ private Boolean resumable = false;
+
+ public UploadExeption(String message, Boolean resumable) {
+ super(message);
+ this.resumable = resumable;
+ }
+
+ public UploadExeption(String message,Boolean resumable, Throwable t) {
+ super(message, t);
+ this.resumable = resumable;
+ }
+
+ public Boolean isResumable() {
+ return resumable;
+ }
+
+
+}
\ No newline at end of file
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderEventConsumer.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderEventConsumer.java
new file mode 100644
index 000000000000..7a7a1ff1ab83
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderEventConsumer.java
@@ -0,0 +1,212 @@
+package ar.edu.unlp.sedici.dspace.uploader;
+
+import static org.dspace.event.Event.ADD;
+import static org.dspace.event.Event.CREATE;
+import static org.dspace.event.Event.DELETE;
+import static org.dspace.event.Event.INSTALL;
+import static org.dspace.event.Event.MODIFY;
+import static org.dspace.event.Event.MODIFY_METADATA;
+import static org.dspace.event.Event.REMOVE;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import org.dspace.core.ConfigurationManager;
+import org.dspace.core.Constants;
+
+
+import org.apache.log4j.Logger;
+import org.dspace.content.Bitstream;
+import org.dspace.content.Bundle;
+import org.dspace.content.Item;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.event.Consumer;
+import org.dspace.event.Event;
+import org.dspace.utils.DSpace;
+import org.dspace.authorize.AuthorizeManager;
+
+
+import com.google.api.services.youtube.YouTube;
+
+import org.dspace.curate.Curator;
+
+/**
+ * Event listener that filters events triggered by items with video bitstreams.
+ * Upload = ADD item to collection(publish the item), or ADD bitstream to bundle(allready published).
+ * Update = MODIFY_METADATA of item.
+ * Delete = REMOVE bitstream of bundle or bundle of item(last bitstream of the bundle)
+*/
+public class VideoUploaderEventConsumer implements Consumer {
+
+ /**
+ * log4j logger
+ */
+ private static Logger log = Logger.getLogger(VideoUploaderEventConsumer.class);
+
+ private final String MPEG_MIME_TYPE = "video/mpeg";
+ private final String QUICKTIME_MIME_TYPE = "video/quicktime";
+ private final String MP4_MIME_TYPE = "video/mp4";
+ private final String QUEUE = "replicateVideo";
+
+ private final String REPLICATION_BUNDLE = "REPLICATION";
+ private final String REPLICATION_METADATA = ConfigurationManager.getProperty("upload","video.identifier.metadata");
+
+ DSpace dspace = new DSpace();
+
+ ContentUploaderService uploader = dspace.getServiceManager().getServiceByName(ContentUploaderService.class.getName(),ContentUploaderService.class);
+
+ @Override
+ public void initialize() throws Exception {
+
+ }
+
+ @Override
+ public void consume(Context ctx, Event event) {
+ int evType = event.getEventType();
+ int st = event.getSubjectType();
+ if (!(st == Constants.ITEM || st == Constants.BUNDLE || st == Constants.COLLECTION || st == Constants.BITSTREAM)) {
+ log.warn("VideoUploaderConsumer should not have been given this kind of Subject in an event, skipping: " + event.toString());
+ return;
+ }
+ try {
+ switch (evType){
+ case ADD:
+ if(st==Constants.COLLECTION){
+ Item item = (Item) event.getObject(ctx);
+ Bundle[] bundles = item.getBundles("ORIGINAL");
+
+ Bitstream[] bitstreams = bundles[0].getBitstreams();
+
+
+ if((item.getHandle() != null)){
+ String mimeType;
+ for (Bitstream bitstream : bitstreams) {
+ mimeType = bitstream.getFormat().getMIMEType();
+ if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))) {
+ if(AuthorizeManager.authorizeActionBoolean(ctx, bitstream, 0, false)) {
+ Curator curator = new Curator();
+ curator.addTask("VideoUploaderTask").queue(ctx,item.getHandle(),QUEUE);
+ break;
+ }else {
+ log.info("El bitstream con id "+bitstream.getID()+" no esta autorizado para subirse");
+ }
+ }
+
+ }
+ }
+
+ }else{
+ if(st==Constants.BUNDLE && ((Bundle) event.getSubject(ctx)).getName().equals("ORIGINAL")){
+ Bundle bundle = (Bundle) event.getSubject(ctx);
+ Item[] items = bundle.getItems();
+
+ String hdl = items[0].getHandle();
+ Bitstream[] bitstreams = bundle.getBitstreams();
+
+ //Checks if published.
+ if(hdl != null){
+ String mimeType;
+ for (Bitstream bitstream : bitstreams) {
+ mimeType = bitstream.getFormat().getMIMEType();
+ //Checks that the bitstream is a video and that it is not uploaded on an external service in order to upload it.
+ if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))&&(bitstream.getMetadata(REPLICATION_METADATA) == null)) {
+ if(AuthorizeManager.authorizeActionBoolean(ctx, bitstream, 0, false)) {
+ Curator curator = new Curator();
+ curator.addTask("VideoUploaderTask").queue(ctx,hdl,QUEUE);
+ break;
+ }else {
+ log.info("The bitstream with ID "+bitstream.getID()+" is not autorized to upload");
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+ case MODIFY_METADATA:
+ if (st==Constants.ITEM) {
+ if(((Item) event.getSubject(ctx)).getHandle() != null){
+ if(shouldUpdateMetadata(event)){
+ if(st==Constants.ITEM){
+ Item item = (Item) event.getSubject(ctx);
+ Bundle[] bundles = item.getBundles("ORIGINAL");
+ Bitstream[] bitstreams = bundles[0].getBitstreams();
+ String mimeType;
+ for (Bitstream bitstream : bitstreams) {
+ mimeType = bitstream.getFormat().getMIMEType();
+ //Checks if the bitstream is a video and is published on an external service.
+ if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))&&(bitstream.getMetadata(REPLICATION_METADATA) != null)) {
+ Curator curator = new Curator();
+ curator.addTask("VideoUpdaterTask").queue(ctx,item.getHandle(),QUEUE);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }else if (st == Constants.BITSTREAM) {
+ Bitstream bitstream = (Bitstream) event.getSubject(ctx);
+ String mimeType = bitstream.getFormat().getMIMEType();
+ //Checks if the bitstream is a video and is published on an external service.
+ if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))&&(bitstream.getMetadata(REPLICATION_METADATA) != null)) {
+ if(event.getDetail().contains("dc.description")) {
+ Curator curator = new Curator();
+ curator.addTask("VideoUpdaterTask").queue(ctx,bitstream.getParentObject().getHandle(),QUEUE);
+ }
+ }
+ }
+ break;
+ case REMOVE:
+ if(st==Constants.BUNDLE ){
+ Bundle bundle = (Bundle) event.getSubject(ctx);
+ //If the bitstream eliminated is not the last one of the bundle, you can get the bundle
+ if(bundle != null) {
+ Item item = (Item) bundle.getParentObject();
+ if(item.getBundles(REPLICATION_BUNDLE).length > 0){
+ Curator curator = new Curator();
+ curator.addTask("VideoDeleteTask").queue(ctx,item.getHandle(),QUEUE);
+ }
+ }
+ //If the bitstream eliminated is the last one of the bundle, you want the event that deletes the bundle from the item
+ }else if (st==Constants.ITEM) {
+ Item item = (Item) event.getSubject(ctx);
+ if(item.getBundles(REPLICATION_BUNDLE).length > 0){
+ Curator curator = new Curator();
+ curator.addTask("VideoDeleteTask").queue(ctx,item.getHandle(),QUEUE);
+ }
+ }
+ break;
+
+ }
+ }catch(SQLException e){
+ log.error("SQLException: "+e.getMessage(),e);
+ } catch (IOException e) {
+ log.error("IOException: "+e.getMessage(),e);
+ }
+
+ //}
+ }
+
+ @Override
+ public void end(Context ctx) throws Exception {
+
+ }
+
+ @Override
+ public void finish(Context ctx) throws Exception {
+
+ }
+
+ /*
+ * Determines if at least one of the metadata used to build the description has been modified
+ */
+ private boolean shouldUpdateMetadata(Event event) {
+ return ((event.getDetail().contains("dc.title")) || (event.getDetail().contains("dc.description.abstract"))
+ || (event.getDetail().contains("sedici.creator.person")) || (event.getDetail().contains("sedici.subtype"))
+ || (event.getDetail().contains("dc.date.available")) || (event.getDetail().contains("dc.identifier.uri"))
+ || (event.getDetail().contains("sedici.rights.license")) || (event.getDetail().contains("dc.subject"))
+ );
+ }
+
+}
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderServiceImpl.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderServiceImpl.java
new file mode 100644
index 000000000000..54296b39ac6d
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderServiceImpl.java
@@ -0,0 +1,337 @@
+package ar.edu.unlp.sedici.dspace.uploader;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.Throwable;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+import org.dspace.content.Bitstream;
+import org.dspace.content.Bundle;
+import org.dspace.content.Item;
+import org.dspace.content.Metadatum;
+import org.dspace.core.ConfigurationManager;
+import org.dspace.core.Context;
+import org.dspace.curate.Curator;
+import org.dspace.eperson.EPerson;
+import org.dspace.utils.DSpace;
+import org.jsoup.Jsoup;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.stereotype.Service;
+
+import ar.edu.unlp.sedici.dspace.uploader.adapter.Adapter;
+import ar.edu.unlp.sedici.util.MailReporter;
+
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.authorize.AuthorizeManager;
+
+
+@Service
+public class VideoUploaderServiceImpl implements ContentUploaderService{
+
+ private static final Logger log = Logger.getLogger(VideoUploaderServiceImpl.class);
+
+ private final String MPEG_MIME_TYPE = "video/mpeg";
+ private final String QUICKTIME_MIME_TYPE = "video/quicktime";
+ private final String MP4_MIME_TYPE = "video/mp4";
+
+ private final String REPLICATION_BUNDLE = "REPLICATION";
+
+ private Adapter adapter;
+
+ public VideoUploaderServiceImpl(Adapter adapter) {
+ super();
+ this.adapter = adapter;
+ }
+
+ @Override
+ public void uploadContent(Item item) throws IOException {
+
+ String handle = item.getHandle();
+ log.info("Starting the upload for the item with handle " + handle +" to external service");
+ String itemTitle= Jsoup.parse(item.getMetadata("dc.title")).text();
+ try {
+ //Create the Bundle if necessary
+ if (item.getBundles(REPLICATION_BUNDLE).length==0) {
+ item.createBundle(REPLICATION_BUNDLE);
+ }
+
+ Bitstream[] bitstreams = item.getBundles("ORIGINAL")[0].getBitstreams();
+
+ for (Bitstream bitstream : bitstreams) {
+
+ if (autorizarSubida(bitstream)) {
+ String title;
+ if(bitstream.getMetadata("dc.description").isEmpty()) {
+ title = itemTitle;
+ log.warn("The bitstream with ID "+bitstream.getID()+" is going to be uploaded to an external service, but it has no description, and the title will be "+title);
+ } else {
+ title = itemTitle+" - "+bitstream.getMetadata("dc.description");
+ }
+ String videoID = adapter.uploadVideo(bitstream.retrieve(), title, this.buildMetadata(item), this.buildTags(item));
+ if(videoID != null) {
+ log.info("The upload for the bitstream with ID "+bitstream.getID()+",contained in the item with handle "+handle+", finished successfully, uploading the video with new ID "+videoID);
+ persistirId(videoID,item,bitstream);
+ }
+ }
+ }
+ }catch(UploadExeption e){
+ //log.error(e.getMessage()); the Adapter is responsible for the error log in this case
+ this.resolveExeption(e,"upload",item);
+ } catch (SQLException e) {
+ log.error("SQLException: "+e.getMessage(),e);
+ throw new IOException(e);
+ } catch (AuthorizeException e) {
+ log.error("AuthorizeException: "+e.getMessage(),e);
+ throw new IOException(e);
+ }
+
+ }
+
+ private boolean autorizarSubida(Bitstream bitstream) throws UploadExeption, SQLException{
+ Context ctx = new Context();
+ String[] schemaL = this.getReplicationMetadataName().split("\\.");
+ if(AuthorizeManager.authorizeActionBoolean(ctx, bitstream, 0, false)) {
+ ctx.complete();
+ String mimeType = bitstream.getFormat().getMIMEType();
+ if (mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE)) {
+ List replicationId = bitstream.getMetadata(schemaL[0],schemaL[1],schemaL[2],Item.ANY,Item.ANY);
+ if (replicationId.size() == 0) {
+ return true;
+ }else {
+ log.info("The bitstream with ID "+bitstream.getID()+" is allready replicated in the external service "+bitstream.getMetadata(this.getReplicationMetadataName()));
+ }
+ }
+ } else {
+ ctx.complete();
+ log.info("The bitstream with ID "+bitstream.getID()+" shouldn't be replicated to an external service due to it not being accessible to anonymous user");
+ }
+ return false;
+ }
+
+ private void persistirId(String id, Item item,Bitstream bitstream) {
+ String[] schemaL = this.getReplicationMetadataName().split("\\.");
+ String lang = null;
+ bitstream.addMetadata(schemaL[0],schemaL[1],schemaL[2],lang,id);
+ try {
+ bitstream.updateMetadata();
+ item.updateLastModified();
+ String initialString = bitstream.getID()+";"+id;
+ InputStream targetStream = new ByteArrayInputStream(initialString.getBytes());
+ Bundle bundle = item.getBundles(REPLICATION_BUNDLE)[0];
+ bundle.createBitstream(targetStream).setName("Map bitstream - external service");
+ bundle.update();
+ } catch (SQLException e) {
+ log.error("SQLException: " + e.getMessage());
+ } catch (AuthorizeException e) {
+ log.error("AuthorizeException: " + e.getMessage());
+ } catch (IOException e) {
+ log.error("IOException: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void removeContent(Item item) throws IOException {
+ try {
+ if(item.getBundles(REPLICATION_BUNDLE).length != 0) {
+ Bitstream[] mapsReplication = item.getBundles(REPLICATION_BUNDLE)[0].getBitstreams();
+ if(item.getBundles("ORIGINAL").length>0) {
+ for (Bitstream map : mapsReplication) {
+ String idVideo = determinarBorradoDeBitstream(map,item);
+ if (idVideo != null) {
+ adapter.deleteVideo(idVideo);
+ item.getBundles(REPLICATION_BUNDLE)[0].removeBitstream(map);
+ }
+ }
+ }else {
+ /**
+ * If you remove the last item in the bundle ORIGINAL, it removes the bundle from the item.
+ * In this case, if the bundle REPLICATION exists, you have to eliminate every video in this bundle
+ */
+ for (Bitstream map : mapsReplication) {
+ String[] mapeo = parsearBitstream(map);
+ adapter.deleteVideo(mapeo[1]);
+ item.getBundles(REPLICATION_BUNDLE)[0].removeBitstream(map);
+ }
+
+ }
+ }
+ }catch(UploadExeption e){
+ //log.error(e.getMessage()); Adapter is responsible for the error log in this case
+ this.resolveExeption(e,"delete",item);
+ } catch (SQLException e) {
+ log.error("SQLException: "+e.getMessage(),e);
+ throw new IOException(e);
+ } catch (AuthorizeException e) {
+ log.error("AuthorizeException: "+e.getMessage(),e);
+ throw new IOException(e);
+ }
+
+ }
+
+ /**
+ * Transform the relation between bitstream ID and the external service ID, to a list of Strings
+ * @return String list [0] Bitstream ID [1] external service ID
+ */
+ private String[] parsearBitstream(Bitstream bitstream) throws IOException, SQLException, AuthorizeException {
+ String data = IOUtils.toString(bitstream.retrieve(), StandardCharsets.UTF_8);
+ return data.toString().split(";");
+ }
+
+ private String determinarBorradoDeBitstream(Bitstream relacionBY, Item item) throws IOException, SQLException, AuthorizeException {
+ Bitstream[] bitstreams = item.getBundles("ORIGINAL")[0].getBitstreams();
+ String[] listaIDs = parsearBitstream(relacionBY);
+ Boolean existe = false;
+ for (Bitstream bitstream : bitstreams) {
+ //Checks if the bitstream replicated in an external site has been deleted
+ if(listaIDs[0].equals(Integer.toString(bitstream.getID())) ) {
+ existe = true;
+ }
+ }
+ if (!existe){
+ return listaIDs[1];
+ }
+ else {
+ return null;
+ }
+ }
+
+ @Override
+ public void modifyContent(Item item) throws IOException {
+ String handle = item.getHandle();
+ log.info("Update of item " + handle +" to an external site");
+ String itemTitle= Jsoup.parse(item.getMetadata("dc.title")).text();
+ try {
+
+ Bitstream[] bitstreams = item.getBundles("ORIGINAL")[0].getBitstreams();
+
+ for (Bitstream bitstream : bitstreams) {
+ if(autorizarModificacion(bitstream)) {
+ String title = itemTitle+" - "+bitstream.getMetadata("dc.description");
+ String videoID = adapter.updateMetadata(bitstream.getMetadata(this.getReplicationMetadataName()), title, this.buildMetadata(item), this.buildTags(item));
+ log.info("The video whit ID "+videoID+" and title '"+title+"' has been updated");
+ }
+ }
+ }catch(UploadExeption e){
+ //log.error(e.getMessage()); Adapter is responsible for the error log in this case
+ this.resolveExeption(e,"update",item);
+ }catch(SQLException e) {
+ log.error("SQLException: "+e.getMessage(),e);
+ throw new IOException(e);
+ }
+
+ }
+
+ private boolean autorizarModificacion(Bitstream bitstream) {
+ String mimeType = bitstream.getFormat().getMIMEType();
+ if (mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE)) {
+ if (bitstream.getMetadata(this.getReplicationMetadataName()) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Map buildMetadata(Item item) {
+
+ Map metadata = new HashMap();
+
+ //metadata.put("title", Jsoup.parse(item.getMetadata("dc.title")).text());
+ metadata.put("creators", item.getMetadata("sedici","creator","person",Item.ANY,Item.ANY));
+ metadata.put("subtype", item.getMetadata("sedici.subtype"));
+ //metadata.put("dateAvailable", item.getMetadata("dc.date.available"));
+ metadata.put("iUri", item.getMetadata("dc.identifier.uri"));
+ metadata.put("language", item.getMetadata("dc.language"));
+ metadata.put("subjects", item.getMetadata("dc","subject",Item.ANY,Item.ANY,Item.ANY));
+ if(item.getMetadata("dc.description.abstract") != null){
+ metadata.put("abstract", Jsoup.parse(item.getMetadata("dc.description.abstract")).text());
+ }
+ metadata.put("license", item.getMetadata("sedici.rights.license"));
+ return metadata;
+ }
+
+
+ private List buildTags(Item item){
+ List tags = new ArrayList();
+ tags.add("UNLP");
+
+ ListIterator palabras = item.getMetadata("dc","subject",Item.ANY,Item.ANY,Item.ANY).listIterator();
+ while (palabras.hasNext()) {
+ tags.add(palabras.next().value);
+ }
+
+ ListIterator origen = item.getMetadata("mods","originInfo","place",Item.ANY,Item.ANY).listIterator();
+ while (origen.hasNext()) {
+ tags.add(origen.next().value);
+ }
+
+ ListIterator materias = item.getMetadata("sedici","subject","materias",Item.ANY,Item.ANY).listIterator();
+ while (materias.hasNext()) {
+ tags.add(materias.next().value);
+ }
+
+ ListIterator catedras = item.getMetadata("sedici","description","catedra",Item.ANY,Item.ANY).listIterator();
+ while (catedras.hasNext()) {
+ tags.add(catedras.next().value);
+ }
+ return tags;
+ }
+
+ private void resolveExeption(UploadExeption e, String contexto, Item item){
+ try {
+ if(e.isResumable()) {
+ Curator curator = new Curator();
+ Context ctx = new Context();
+ ctx.turnOffAuthorisationSystem();
+ ctx.setCurrentUser(EPerson.findByEmail(ctx, ConfigurationManager.getProperty("upload","upload.user")));//Crear y usar el usuario info+video@sedici.unlp.edu.ar
+ String task;
+ if (contexto.equals("upload")) {
+ task = "VideoUploaderTask";
+ }else if(contexto.equals("update")){
+ task = "VideoUpdaterTask";
+ }else {
+ task = "VideoDeleteTask";
+ }
+ curator.addTask(task).queue(ctx,item.getHandle(),"replicateVideo");
+ ctx.complete();
+ }
+ if(!e.isResumable()) {
+ MailReporter.reportUnknownException("Ocurrio un error no reasumible en el "+ contexto +" del item con handle "+item.getHandle(), e, "http://sedici.unlp.edu.ar/handle/"+item.getHandle());
+ }else if (e.getMessage().equals("The daily quota of Youtube has exeded")){
+ MailReporter.reportUnknownException("Ocurrio un error reasumible en el "+ contexto +" del item con handle "+item.getHandle()+". Se agoto la quota de Youtube", e, "http://sedici.unlp.edu.ar/handle/"+item.getHandle());
+ }else if (e.getMessage().equals("No quota")){
+ /**
+ * In this case, you don't have to send an email, as the email indicating
+ * that the quota limit has already been reached has been sent
+ * This case exist to re-queue the curation tasks
+ */
+ }else {
+ MailReporter.reportUnknownException("An unhandled error as ocurred in the "+ contexto +" del item con handle "+item.getHandle(), e, "http://sedici.unlp.edu.ar/handle/"+item.getHandle());
+ }
+ } catch (Throwable t) {
+ log.error("Error during exeption management in the VideoUploaderService");
+ log.error("Throwable: " + t.getMessage());
+ }
+ }
+
+ private String getReplicationMetadataName() {
+ return ConfigurationManager.getProperty("upload","video.identifier.metadata");
+ }
+}
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/Adapter.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/Adapter.java
new file mode 100644
index 000000000000..cd97a62341ad
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/Adapter.java
@@ -0,0 +1,17 @@
+package ar.edu.unlp.sedici.dspace.uploader.adapter;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import ar.edu.unlp.sedici.dspace.uploader.UploadExeption;
+
+public interface Adapter {
+
+ public String uploadVideo(InputStream videoFile, final String title, Map metadata, List tags) throws UploadExeption;
+
+ public String updateMetadata(String videoId, String title, Map metadata, List tags) throws UploadExeption;
+
+ public String deleteVideo(String videoId) throws UploadExeption;
+
+}
diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/YoutubeAdapter.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/YoutubeAdapter.java
new file mode 100644
index 000000000000..0a7f4ce986ea
--- /dev/null
+++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/YoutubeAdapter.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2012 Google Inc.
+ *
+ * Licensed 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 ar.edu.unlp.sedici.dspace.uploader.adapter;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.HashMap;
+
+import org.apache.log4j.Logger;
+import org.json.JSONObject;
+
+import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.auth.oauth2.TokenResponse;
+import com.google.api.client.auth.oauth2.TokenResponseException;
+import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
+import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore;
+import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
+import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
+import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
+import com.google.api.client.googleapis.json.GoogleJsonResponseException;
+import com.google.api.client.googleapis.media.MediaHttpUploader;
+import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.InputStreamContent;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.youtube.YouTube;
+import com.google.api.services.youtube.YouTube.VideoCategories;
+import com.google.api.services.youtube.model.Video;
+import com.google.api.services.youtube.model.VideoCategory;
+import com.google.api.services.youtube.model.VideoListResponse;
+import com.google.api.services.youtube.model.VideoSnippet;
+import com.google.api.services.youtube.model.VideoStatus;
+import com.google.common.collect.Lists;
+
+import ar.edu.unlp.sedici.dspace.uploader.UploadExeption;
+
+import org.dspace.content.Metadatum;
+import org.dspace.core.ConfigurationManager;
+import org.springframework.stereotype.Service;
+
+/**
+ * The service responsible for communication with YouTube, specifically handling the uploading,
+ * updating, and deletion of videos. For all these operations, it also takes care of authorization.
+ * Any code related to YouTube should be found here and abstracted from DSpace
+ * regarding the communication details
+ */
+
+@Service
+public class YoutubeAdapter implements Adapter{
+
+ public YoutubeAdapter() {
+ super();
+ }
+
+ static final Logger logger = Logger.getLogger(YoutubeAdapter.class);
+
+ /** Global instance of the HTTP transport. */
+ private final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
+
+ /** Global instance of the JSON factory. */
+ private final JsonFactory JSON_FACTORY = new JacksonFactory();
+
+ /** Global instance of Youtube object to make all API requests. */
+ private YouTube youtube;
+
+ /*
+ * Global instance of the format used for the video being uploaded (MIME type).
+ */
+ private String VIDEO_FILE_FORMAT = "video/*";
+
+ private Credential CREDENTIAL;
+
+ private boolean noQuota = false;
+
+ /**
+ * Authorizes the installed application to access user's protected data.
+ *
+ * @param scopes list of scopes needed to run youtube upload.
+ * @throws IOException
+ */
+ private void authorize(List scopes) throws IOException {
+
+ // Load client secrets.
+ Reader reader = new InputStreamReader(new FileInputStream(new File(ConfigurationManager.getProperty("youtube.upload","youtube.upload.secrets"))));
+ GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, reader);
+
+ // Set up file credential store.
+ FileCredentialStore credentialStore = new FileCredentialStore(
+ new File(ConfigurationManager.getProperty("upload","youtube.upload.refresh")), JSON_FACTORY);
+
+ // Set up authorization code flow.
+ GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
+ clientSecrets, scopes).setCredentialStore(credentialStore).setAccessType("offline").build();
+
+ this.CREDENTIAL = flow.loadCredential(clientSecrets.getDetails().getClientId());
+ /**
+ * Se verifica que si se tiene el refresh token y no esta expirado el acces token, se lo devuelve
+ * Caso contrario se debe de volver a autorizar y refrescar el acces token
+ */
+ if (CREDENTIAL != null
+ && (CREDENTIAL.getRefreshToken() != null
+ || CREDENTIAL.getExpiresInSeconds() == null
+ || CREDENTIAL.getExpiresInSeconds() > 60)) {
+ return;
+ }
+
+ // open in browser
+ String redirectUri = ConfigurationManager.getProperty("upload.redirect_uri");
+ AuthorizationCodeRequestUrl authorizationUrl =
+ flow.newAuthorizationUrl().setRedirectUri(redirectUri);
+
+ // receive authorization code and exchange it for an access token
+ System.out.println(authorizationUrl.build());
+ System.out.print("Please enter code: ");
+ String code = new Scanner(System.in).nextLine();
+ TokenResponse response = flow.newTokenRequest(code).setRedirectUri(redirectUri).execute();
+
+ // store credential and acces token
+ CREDENTIAL = flow.createAndStoreCredential(response, clientSecrets.getDetails().getClientId());
+
+ }
+
+ /**
+ * Uploads video to the user's YouTube account using OAuth2 for authentication.
+ *
+ * @param videoFile
+ * @return String Youtube ID
+ */
+ public String uploadVideo(InputStream videoFile, final String title, Map metadata, List tags) throws UploadExeption {
+ if (noQuota) {
+ throw new UploadExeption("No quota",true);
+ }
+ // Scope required to upload to YouTube.
+ List scopes = Lists.newArrayList("https://www.googleapis.com/auth/youtube.force-ssl");
+ try {
+ // Authorization.
+ if ((CREDENTIAL == null)||
+ (CREDENTIAL.getAccessToken() == null)){
+ authorize(scopes);
+ }
+ //Credential credential = authorize(scopes);
+
+ // YouTube object used to make all API requests.
+ youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, this.CREDENTIAL)
+ .setApplicationName("DSpace SEDICI").build();
+
+ List categories = new ArrayList();
+ categories.add("snippet");
+ // Add extra information to the video before uploading.
+ Video videoObjectDefiningMetadata = new Video();
+
+ /**
+ * Set the video to public, so it is available to everyone (what most people
+ * want). This is actually the default, but I wanted you to see what it looked
+ * like in case you need to set it to "unlisted" or "private" via API.
+ */
+ VideoStatus status = new VideoStatus();
+ status.setLicense("creativeCommon");
+ status.setSelfDeclaredMadeForKids(false);
+ status.setMadeForKids(false);
+ status.setPrivacyStatus(ConfigurationManager.getProperty("upload","youtube.upload.video.state"));
+ videoObjectDefiningMetadata.setStatus(status);
+
+ // We set a majority of the metadata with the VideoSnippet object.
+ VideoSnippet snippet = new VideoSnippet();
+
+ snippet.setTitle(title);
+ String description = buildDescription(metadata);
+ snippet.setDescription(description);
+
+ // Set the category of your video (allways Education)
+ snippet.setCategoryId(this.getEducationId());
+
+ // Set your keywords.
+ snippet.setTags(tags);
+ snippet.setDefaultLanguage((String)metadata.get("language"));
+
+ // Set completed snippet to the video object.
+ videoObjectDefiningMetadata.setSnippet(snippet);
+
+ InputStreamContent mediaContent = new InputStreamContent(VIDEO_FILE_FORMAT,
+ new BufferedInputStream(videoFile));
+
+ /**
+ * The upload command includes: 1. Information we want returned after file is
+ * successfully uploaded. 2. Metadata we want associated with the uploaded
+ * video. 3. Video file itself.
+ */
+ List list = new ArrayList();
+ list.add("snippet");
+ list.add("statistics");
+ list.add("status");
+ YouTube.Videos.Insert videoInsert = youtube.videos().insert(list,
+ videoObjectDefiningMetadata, mediaContent);
+
+ // Set the upload type and add event listener.
+ MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
+
+ /**
+ * Sets whether direct media upload is enabled or disabled. True = whole media
+ * content is uploaded in a single request. False (default) = resumable media
+ * upload protocol to upload in data chunks.
+ */
+ uploader.setDirectUploadEnabled(false);
+
+ MediaHttpUploaderProgressListener progressListener = new MediaHttpUploaderProgressListener() {
+ public void progressChanged(MediaHttpUploader uploader) throws IOException {
+ switch (uploader.getUploadState()) {
+ case INITIATION_STARTED:
+ logger.trace("Starting the upload for the video with the title '"+ title+"'");
+ break;
+ case INITIATION_COMPLETE:
+ logger.trace("Initiation Completed");
+ break;
+ case MEDIA_COMPLETE:
+ logger.trace("The upload with title: '"+ title +"' has finished");
+ break;
+ case MEDIA_IN_PROGRESS:
+ logger.trace("Upload in progress");
+ logger.trace("Upload percentage: " + uploader.getProgress());
+ break;
+ case NOT_STARTED:
+ logger.trace("Upload Not Started!");
+ break;
+ }
+ }
+ };
+ uploader.setProgressListener(progressListener);
+
+ // Execute upload.
+ Video returnedVideo = videoInsert.execute();
+ return returnedVideo.getId();
+ }catch (IOException e) {
+ throw manageException(e);
+ }
+ }
+
+ public String updateMetadata(String videoId, String title, Map metadata, List tags) throws UploadExeption{
+ if (noQuota) {
+ throw new UploadExeption("No quota",true);
+ }
+ List scopes = Lists.newArrayList("https://www.googleapis.com/auth/youtube.force-ssl");
+ try {
+ // Authorization.
+ if ((CREDENTIAL == null)||
+ (CREDENTIAL.getAccessToken() == null)){
+ authorize(scopes);
+ }
+
+ // YouTube object used to make all API requests.
+ youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY,this.CREDENTIAL).
+ setApplicationName("DSpace SEDICI").build();
+
+ List parts = new ArrayList();
+ parts.add("snippet");
+ List lvideoId = new ArrayList();
+ lvideoId.add(videoId);
+
+ // Create the video list request
+ YouTube.Videos.List listVideosRequest = youtube.videos().list(parts).setId(lvideoId);
+
+ // Request is executed and video list response is returned
+ VideoListResponse listResponse = listVideosRequest.execute();
+
+ List