diff --git a/.classpath b/.classpath
index c079bf4..e536da1 100644
--- a/.classpath
+++ b/.classpath
@@ -13,9 +13,9 @@
+
-
@@ -28,13 +28,12 @@
-
+
-
-
+
+
+
-
-
diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component
index 73815f6..964827a 100644
--- a/.settings/org.eclipse.wst.common.component
+++ b/.settings/org.eclipse.wst.common.component
@@ -1,9 +1,11 @@
-
-
+
+
-
+
+
+
diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml
index 7c7ce15..f1ff4b5 100644
--- a/.settings/org.eclipse.wst.common.project.facet.core.xml
+++ b/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -3,6 +3,6 @@
-
+
diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs
new file mode 100644
index 0000000..04cad8c
--- /dev/null
+++ b/.settings/org.eclipse.wst.validation.prefs
@@ -0,0 +1,2 @@
+disabled=06target
+eclipse.preferences.version=1
diff --git a/pom.xml b/pom.xml
index 358064b..c00c9d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,18 +24,20 @@
UTF-8
1.8
8.4.4
+ 1.23.0
+ v4-rev488-1.23.0
+ v3-rev271-1.23.0
- org.springframework.boot
- spring-boot-starter-jdbc
+ org.springframework.boot
+ spring-boot-starter-jdbc
-
org.springframework.boot
spring-boot-starter-data-jpa
@@ -67,6 +69,37 @@
spring-security-test
test
+
+ org.vaadin.blackbluegl
+ calendar-component
+ 2.0-BETA4
+
+
+
+ com.google.apis
+ google-api-services-calendar
+ ${google-api-calendar-version}
+
+
+ com.google.api-client
+ google-api-client-appengine
+ ${google-api-version}
+
+
+ com.google.api-client
+ google-api-client-gson
+ ${google-api-version}
+
+
+ com.google.oauth-client
+ google-oauth-client-java6
+ ${google-api-version}
+
+
+ com.google.oauth-client
+ google-oauth-client-jetty
+ ${google-api-version}
+
@@ -87,8 +120,34 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+ com.vaadin
+ vaadin-maven-plugin
+ ${vaadin.version}
+
+
+
+ update-theme
+ update-widgetset
+ compile
+
+ compile-theme
+
+
+
+
+ true
+
+
+
+
+ vaadin-addons
+ http://maven.vaadin.com/vaadin-addons
+
+
diff --git a/src/main/java/de/kreth/clubhelperbackend/google/GoogleBaseAdapter.java b/src/main/java/de/kreth/clubhelperbackend/google/GoogleBaseAdapter.java
new file mode 100644
index 0000000..c46c4d2
--- /dev/null
+++ b/src/main/java/de/kreth/clubhelperbackend/google/GoogleBaseAdapter.java
@@ -0,0 +1,147 @@
+package de.kreth.clubhelperbackend.google;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
+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.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.client.util.store.FileDataStoreFactory;
+import com.google.api.services.calendar.CalendarScopes;
+
+public abstract class GoogleBaseAdapter {
+
+ private static final int GOOGLE_SECRET_PORT = 59431;
+
+ /** Application name. */
+ protected static final String APPLICATION_NAME = "ClubHelperVaadin";
+ /** Directory to store user credentials for this application. */
+ protected static final File DATA_STORE_DIR = new File(
+ System.getProperty("catalina.base"), ".credentials");
+ /** Global instance of the JSON factory. */
+ protected static final JsonFactory JSON_FACTORY = JacksonFactory
+ .getDefaultInstance();
+ /**
+ * Global instance of the scopes required by this quickstart.
+ *
+ * If modifying these scopes, delete your previously saved credentials
+ */
+ static final List SCOPES = Arrays.asList(CalendarScopes.CALENDAR);
+
+ protected static Credential credential;
+
+ protected static final Logger log = LoggerFactory
+ .getLogger(GoogleBaseAdapter.class);
+ /** Global instance of the {@link FileDataStoreFactory}. */
+ protected final FileDataStoreFactory DATA_STORE_FACTORY;
+ /** Global instance of the HTTP transport. */
+ protected final HttpTransport HTTP_TRANSPORT;
+
+ public GoogleBaseAdapter() throws GeneralSecurityException, IOException {
+ super();
+ HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
+ DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
+ DATA_STORE_DIR.mkdirs();
+ }
+
+ protected void checkRefreshToken(String serverName) throws IOException {
+
+ if (credential == null) {
+ credential = authorize(serverName);
+ }
+
+ if ((credential.getExpiresInSeconds() != null
+ && credential.getExpiresInSeconds() < 3600)) {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Security needs refresh, trying.");
+ }
+ boolean result = credential.refreshToken();
+ if (log.isDebugEnabled()) {
+ log.debug("Token refresh "
+ + (result ? "successfull." : "failed."));
+ }
+ }
+ }
+
+ /**
+ * Creates an authorized Credential object.
+ *
+ * @param request
+ *
+ * @return an authorized Credential object.
+ * @throws IOException
+ */
+ private synchronized Credential authorize(String serverName)
+ throws IOException {
+ if (credential != null && (credential.getExpiresInSeconds() != null
+ && credential.getExpiresInSeconds() < 3600)) {
+ credential.refreshToken();
+ return credential;
+ }
+ // Load client secrets.
+ InputStream in = getClass().getResourceAsStream("/client_secret.json");
+ if (in == null) {
+ File inHome = new File(System.getProperty("user.home"),
+ "client_secret.json");
+ if (inHome.exists()) {
+ if (log.isInfoEnabled()) {
+ log.info(
+ "Google secret not found as Resource, using user Home file.");
+ }
+ in = new FileInputStream(inHome);
+ } else {
+ log.error(
+ "Failed to load client_secret.json. Download from google console.");
+ return null;
+ }
+ }
+ GoogleClientSecrets clientSecrets = GoogleClientSecrets
+ .load(JSON_FACTORY, new InputStreamReader(in));
+ if (log.isTraceEnabled()) {
+ log.trace("client secret json resource loaded.");
+ }
+
+ // Build flow and trigger user authorization request.
+ GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
+ HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
+ .setDataStoreFactory(DATA_STORE_FACTORY)
+ .setAccessType("offline").setApprovalPrompt("force")
+ .build();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Configuring google LocalServerReceiver on " + serverName
+ + ":" + GOOGLE_SECRET_PORT);
+ }
+
+ LocalServerReceiver localServerReceiver = new LocalServerReceiver.Builder()
+ .setHost(serverName).setPort(GOOGLE_SECRET_PORT).build();
+
+ Credential credential = new AuthorizationCodeInstalledApp(flow,
+ localServerReceiver).authorize("user");
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
+ }
+
+ credential.setExpiresInSeconds(Long.valueOf(691200L));
+
+ return credential;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/de/kreth/clubhelperbackend/google/calendar/CalendarAdapter.java b/src/main/java/de/kreth/clubhelperbackend/google/calendar/CalendarAdapter.java
new file mode 100644
index 0000000..2102c08
--- /dev/null
+++ b/src/main/java/de/kreth/clubhelperbackend/google/calendar/CalendarAdapter.java
@@ -0,0 +1,182 @@
+package de.kreth.clubhelperbackend.google.calendar;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.google.api.client.util.DateTime;
+import com.google.api.services.calendar.model.Calendar;
+import com.google.api.services.calendar.model.CalendarList;
+import com.google.api.services.calendar.model.CalendarListEntry;
+import com.google.api.services.calendar.model.Event;
+import com.google.api.services.calendar.model.EventAttendee;
+
+import de.kreth.clubhelperbackend.google.GoogleBaseAdapter;
+import de.kreth.clubhelperbackend.google.calendar.CalendarResource.CalendarKonfig;
+
+public class CalendarAdapter extends GoogleBaseAdapter {
+
+ com.google.api.services.calendar.Calendar service;
+ private Lock lock = new ReentrantLock();
+ private CalendarResource res;
+
+ public CalendarAdapter() throws GeneralSecurityException, IOException {
+ super();
+ res = new CalendarResource();
+
+ }
+
+ @Override
+ protected void checkRefreshToken(String serverName) throws IOException {
+ try {
+ if (lock.tryLock(10, TimeUnit.SECONDS)) {
+ try {
+ super.checkRefreshToken(serverName);
+ if (service == null && credential != null) {
+ service = new com.google.api.services.calendar.Calendar.Builder(
+ HTTP_TRANSPORT, JSON_FACTORY, credential)
+ .setApplicationName(APPLICATION_NAME)
+ .build();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ } catch (InterruptedException e) {
+ if (log.isWarnEnabled()) {
+ log.warn("Lock interrupted", e);
+ }
+ }
+ if (service == null) {
+ throw new IllegalStateException(
+ "Calendar Service not instanciated!");
+ }
+ }
+
+ Calendar getCalendarBySummaryName(List items,
+ String calendarSummary) throws IOException {
+
+ String calendarId = null;
+ for (CalendarListEntry e : items) {
+ if (calendarSummary.equals(e.getSummary())) {
+ calendarId = e.getId();
+ break;
+ }
+ }
+ if (calendarId == null) {
+ throw new IllegalStateException(
+ "Calendar " + calendarSummary + " not found!");
+ }
+ Calendar cal = service.calendars().get(calendarId).execute();
+ return cal;
+ }
+
+ public List getAllEvents(String serverName)
+ throws IOException, InterruptedException {
+
+ final List events = new ArrayList<>();
+
+ List items = getCalendarList(serverName);
+ final long oldest = getOldest();
+
+ List configs = res.getConfigs();
+ ExecutorService exec = Executors.newFixedThreadPool(configs.size());
+ for (CalendarKonfig c : configs) {
+ exec.execute(new FetchEventsRunner(items, c.getName(), events,
+ oldest, c.getColor()));
+ }
+
+ exec.shutdown();
+ try {
+ exec.awaitTermination(20, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.error("Thread terminated - event list may be incomplete.", e);
+ }
+ return events;
+ }
+
+ private long getOldest() {
+ GregorianCalendar oldestCal = new GregorianCalendar();
+ oldestCal.set(java.util.Calendar.DAY_OF_MONTH, 1);
+ oldestCal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+ oldestCal.set(java.util.Calendar.MINUTE, 0);
+ oldestCal.add(java.util.Calendar.MONTH, -1);
+ oldestCal.add(java.util.Calendar.YEAR, -1);
+ oldestCal.add(java.util.Calendar.HOUR_OF_DAY, -1);
+ final long oldest = oldestCal.getTimeInMillis();
+ return oldest;
+ }
+
+ List getCalendarList(String serverName)
+ throws IOException {
+ checkRefreshToken(serverName);
+ CalendarList calendarList;
+ try {
+ calendarList = service.calendarList().list().execute();
+ } catch (IOException e) {
+ if (log.isWarnEnabled()) {
+ log.warn("Error fetching Calendar List, trying token refresh",
+ e);
+ }
+ credential.refreshToken();
+ if (log.isInfoEnabled()) {
+ log.info("Successfully refreshed Google Security Token.");
+ }
+ calendarList = service.calendarList().list().execute();
+ }
+ return calendarList.getItems();
+ }
+
+ private final class FetchEventsRunner implements Runnable {
+ private final List events;
+ private final long oldest;
+ private String colorClass;
+ private List items;
+ private String summary;
+
+ private FetchEventsRunner(List items, String summary,
+ List events, long oldest, String colorClass) {
+ this.events = events;
+ this.oldest = oldest;
+ this.items = items;
+ this.summary = summary;
+ this.colorClass = colorClass;
+ }
+
+ @Override
+ public void run() {
+
+ try {
+ log.debug("Fetching events of calendar \"" + summary + "\"");
+ Calendar calendar = getCalendarBySummaryName(items, summary);
+ DateTime timeMin = new DateTime(oldest);
+ List items = service.events().list(calendar.getId())
+ .setTimeMin(timeMin).execute().getItems();
+ items.forEach(item -> item.set("colorClass", colorClass));
+ events.addAll(items);
+ log.debug("Added " + items.size() + " Events for \"" + summary
+ + "\"");
+
+ } catch (IOException e) {
+ log.error("Unable to fetch Events from " + summary, e);
+ }
+ }
+ }
+
+ static Event createDefaultEvent(String summary) {
+ Event ev = new Event().setGuestsCanInviteOthers(false)
+ .setGuestsCanModify(false).setGuestsCanSeeOtherGuests(true)
+ .setSummary(summary);
+ List attendees = new ArrayList<>();
+ ev.setAttendees(attendees);
+ return ev;
+ }
+
+}
diff --git a/src/main/java/de/kreth/clubhelperbackend/google/calendar/CalendarResource.java b/src/main/java/de/kreth/clubhelperbackend/google/calendar/CalendarResource.java
new file mode 100644
index 0000000..5789257
--- /dev/null
+++ b/src/main/java/de/kreth/clubhelperbackend/google/calendar/CalendarResource.java
@@ -0,0 +1,81 @@
+package de.kreth.clubhelperbackend.google.calendar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+public class CalendarResource {
+
+ private Map configs;
+
+ public CalendarResource() throws IOException {
+ configs = new HashMap<>();
+
+ InputStream resStream = getClass()
+ .getResourceAsStream("/calendars.properties");
+ Properties prop = new Properties();
+ prop.load(resStream);
+
+ Enumeration