diff --git a/src/main/java/de/thm/arsnova/ImageUtils.java b/src/main/java/de/thm/arsnova/ImageUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..0af116547c229c335ee88ca292528365c927cf7c --- /dev/null +++ b/src/main/java/de/thm/arsnova/ImageUtils.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 THM webMedia + * + * This file is part of ARSnova. + * + * ARSnova is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.thm.arsnova; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.imageio.ImageIO; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Util class for image operations. + * + * @author Daniel Vogel (daniel.vogel@mni.thm.de) + * + */ +public class ImageUtils { + public static final Logger LOGGER = LoggerFactory.getLogger(ImageUtils.class); + + /** + * Converts an image to an Base64 String. + * + * @param imageUrl The image url as a {@link String} + * @return The Base64 {@link String} of the image on success, otherwise <code>null</code>. + */ + public static String encodeImageToString(String imageUrl) { + + String[] urlParts = imageUrl.split("\\."); + StringBuilder result = new StringBuilder(); + + // get format + // + // The format is read dynamically. We have to take control + // in the frontend that no unsupported formats are transmitted! + if ( urlParts.length > 0 ) { + + String extension = urlParts[urlParts.length-1]; + + result.append("data:image/" + extension + ";base64,"); + result.append(Base64.encodeBase64String(convertFileToByteArray(imageUrl))); + + return result.toString(); + } + + return null; + } + + /** + * Gets the bytestream of an image url. + * s + * @param imageUrl The image url as a {@link String} + * @return The <code>byte[]</code> of the image on success, otherwise <code>null</code>. + */ + public static byte[] convertImageToByteArray(String imageUrl, String extension) { + + try { + URL url = new URL(imageUrl); + BufferedImage image = ImageIO.read(url); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + ImageIO.write(image, extension, baos); + + baos.flush(); + baos.close(); + return baos.toByteArray(); + + } catch (MalformedURLException e) { + LOGGER.error(e.getLocalizedMessage()); + } catch (IOException e) { + LOGGER.error(e.getLocalizedMessage()); + } + + return null; + } + + /** + * Gets the bytestream of an image url. + * s + * @param imageUrl The image url as a {@link String} + * @return The <code>byte[]</code> of the image on success, otherwise <code>null</code>. + */ + public static byte[] convertFileToByteArray(String imageUrl) { + + try { + URL url = new URL(imageUrl); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + InputStream is = url.openStream(); + byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time. + int n; + + while ( (n = is.read(byteChunk)) > 0 ) { + baos.write(byteChunk, 0, n); + } + + baos.flush(); + baos.close(); + + return baos.toByteArray(); + + } catch (MalformedURLException e) { + LOGGER.error(e.getLocalizedMessage()); + } catch (IOException e) { + LOGGER.error(e.getLocalizedMessage()); + } + + return null; + } +} + diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index fec9faff500a66c70c9f66b9458d512ff554607b..1e3915e9c3d0adec08d012b4351c10b5c648c695 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -288,6 +288,12 @@ public class CouchDBDao implements IDatabaseDao { q.put("showStatistic", question.isShowStatistic()); q.put("showAnswer", question.isShowAnswer()); q.put("abstention", question.isAbstention()); + q.put("image", question.getImage()); + q.put("gridSize", question.getGridSize()); + q.put("offsetX", question.getOffsetX()); + q.put("offsetY", question.getOffsetY()); + q.put("zoomLvl", question.getZoomLvl()); + return q; } @@ -305,6 +311,11 @@ public class CouchDBDao implements IDatabaseDao { q.put("showStatistic", question.isShowStatistic()); q.put("showAnswer", question.isShowAnswer()); q.put("abstention", question.isAbstention()); + q.put("image", question.getImage()); + q.put("gridSize", question.getGridSize()); + q.put("offsetX", question.getOffsetX()); + q.put("offsetY", question.getOffsetY()); + q.put("zoomLvl", question.getZoomLvl()); this.database.saveDocument(q); question.set_rev(q.getRev()); @@ -353,7 +364,8 @@ public class CouchDBDao implements IDatabaseDao { results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), Question.class ); - JSONArray possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value") + JSONArray possibleAnswers = new JSONArray(); + possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value") .getJSONArray("possibleAnswers"); Collection<PossibleAnswer> answers = JSONArray.toCollection( possibleAnswers, diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Question.java index 33c2e59a14ac3385e6f4e44cf695a80a3c0b8d47..8b2d36e30c75c655eec2f7fdc76b95bffe0060fe 100644 --- a/src/main/java/de/thm/arsnova/entities/Question.java +++ b/src/main/java/de/thm/arsnova/entities/Question.java @@ -44,6 +44,12 @@ public class Question { private String _id; private String _rev; + private String image; + private int gridSize; + private int offsetX; + private int offsetY; + private int zoomLvl; + public final String getType() { return type; } @@ -212,6 +218,46 @@ public class Question { this._rev = _rev; } + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public int getGridSize() { + return gridSize; + } + + public void setGridSize(int gridSize) { + this.gridSize = gridSize; + } + + public int getOffsetX() { + return offsetX; + } + + public void setOffsetX(int offsetX) { + this.offsetX = offsetX; + } + + public int getOffsetY() { + return offsetY; + } + + public void setOffsetY(int offsetY) { + this.offsetY = offsetY; + } + + public int getZoomLvl() { + return zoomLvl; + } + + public void setZoomLvl(int zoomLvl) { + this.zoomLvl = zoomLvl; + } + @Override public final String toString() { return "Question type '" + this.type + "': " + this.subject + ";\n" + this.text + this.possibleAnswers; diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index 0467de78e4aa89783df70616385df70ea61b6aab..f91da75e999fb8f84ae0589072373c8325b2a9a1 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -24,9 +24,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import de.thm.arsnova.ImageUtils; import de.thm.arsnova.annotation.Authenticated; import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Answer; @@ -35,6 +39,7 @@ import de.thm.arsnova.entities.InterposedReadingCount; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; +import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; @@ -51,6 +56,11 @@ public class QuestionService implements IQuestionService { @Autowired private ARSnovaSocketIOServer socketIoServer; + + @Value("${upload.filesize_b}") + private int uploadFileSizeByte; + + public static final Logger LOGGER = LoggerFactory.getLogger(QuestionService.class); public void setDatabaseDao(IDatabaseDao databaseDao) { this.databaseDao = databaseDao; @@ -86,6 +96,25 @@ public class QuestionService implements IQuestionService { } else if (question.getPiRound() < 1 || question.getPiRound() > 2) { question.setPiRound(1); } + + // convert imageurl to base64 if neccessary + if ("grid".equals(question.getQuestionType())) { + org.slf4j.Logger logger = LoggerFactory.getLogger(QuestionService.class); + if (question.getImage().startsWith("http")) { + String base64ImageString = ImageUtils.encodeImageToString(question.getImage()); + if (base64ImageString == null) { + throw new BadRequestException(); + } + question.setImage(base64ImageString); + } + + // base64 adds offset to filesize, formular taken from: http://en.wikipedia.org/wiki/Base64#MIME + int fileSize = (int)((question.getImage().length()-814)/1.37); + if ( fileSize > this.uploadFileSizeByte ) { + LOGGER.error("Could not save file. File is too large with "+ fileSize + " Byte."); + throw new BadRequestException(); + } + } Question result = this.databaseDao.saveQuestion(session, question); socketIoServer.reportLecturerQuestionAvailable(result.getSessionKeyword(), result.get_id()); diff --git a/src/main/webapp/WEB-INF/spring/spring-main.xml b/src/main/webapp/WEB-INF/spring/spring-main.xml index 76d70ca2348a8535c92050ca21ec19bc3c17e178..a33b6f930f7b83b4f659119af9a128722a017698 100644 --- a/src/main/webapp/WEB-INF/spring/spring-main.xml +++ b/src/main/webapp/WEB-INF/spring/spring-main.xml @@ -20,7 +20,7 @@ <property name="locations"> <list> <value>arsnova.properties.example</value> - <value>file:///etc/arsnova/arsnova.properties</value> + <value>file:///C:/_Dev/WS1314/MSP/arsnova/arsnova-war/src/main/webapp/arsnova.properties</value> </list> </property> </bean> diff --git a/src/main/webapp/arsnova.properties b/src/main/webapp/arsnova.properties new file mode 100644 index 0000000000000000000000000000000000000000..65a05595afd0c948b295205002aa87b98bce2c3e --- /dev/null +++ b/src/main/webapp/arsnova.properties @@ -0,0 +1,34 @@ +security.arsnova-url=http://localhost:8080 +security.cas-server-url=https://cas.thm.de/cas + +security.facebook.key=318531508227494 +security.facebook.secret=e3f38cfc72bb63e35641b637081a6177 + +security.twitter.key=PEVtidSG0HzSrxVRPpsCXw +security.twitter.secret=mC0HOvxiEgqwdDWCcDoy3q75nUQPu1bYRp1ncHWGd0 + +security.google.key=110959746118.apps.googleusercontent.com +security.google.secret=CkzUJZswY8rjWCCYnHVovyGA + +security.ssl=false +security.keystore=/etc/arsnova.thm.de.jks +security.storepass=arsnova + +# minutes, after which the feedback is deleted +feedback.cleanup=10 + +# maximal filesize in bytes +upload.filesize_b = 1048576 + +couchdb.host=localhost +couchdb.port=5984 +couchdb.name=arsnova +couchdb.username=root +couchdb.password=root + +socketio.ip=0.0.0.0 +socketio.port=10443 + +connector.uri=http://localhost:8080/connector-service +connector.username=test +connector.password=test