[kune-commits] r1175 - in trunk/src/main: java/cc/kune/core/client/sitebar/logo java/org/ourproject/kune/app/server/wave resources
Vicente J. Ruiz Jurado
vjrj_ at ourproject.org
Wed Dec 29 04:37:13 CET 2010
Author: vjrj_
Date: 2010-12-29 04:37:12 +0100 (Wed, 29 Dec 2010)
New Revision: 1175
Added:
trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoUiHandlers.java
trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoViewImpl.ui.xml
trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomAttachmentServlet.java
trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomFetchServlet.java
trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomServerModule.java
trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSessionManagerImpl.java
trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSettingsBinder.java
trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomUserRegistrationServlet.java
trunk/src/main/resources/wave-server.properties
Log:
NEW - # 10: Wave server code update
http://kune.ourproject.org/issues/ticket/10
NEW - # 9: Roo partial use/integration
http://kune.ourproject.org/issues/ticket/9
Added: trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoUiHandlers.java
===================================================================
--- trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoUiHandlers.java (rev 0)
+++ trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoUiHandlers.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,7 @@
+package cc.kune.core.client.sitebar.logo;
+
+import com.gwtplatform.mvp.client.UiHandlers;
+
+public interface SiteLogoUiHandlers extends UiHandlers {
+ void onClick();
+}
\ No newline at end of file
Added: trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoViewImpl.ui.xml
===================================================================
--- trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoViewImpl.ui.xml (rev 0)
+++ trunk/src/main/java/cc/kune/core/client/sitebar/logo/SiteLogoViewImpl.ui.xml 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,10 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style>
+ .important {
+ font-weight: bold;
+ }
+ </ui:style>
+ <g:Image ui:field="logo" addStyleNames="k-floatright k-pointer" />
+</ui:UiBinder>
\ No newline at end of file
Added: trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomAttachmentServlet.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomAttachmentServlet.java (rev 0)
+++ trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomAttachmentServlet.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2010 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 org.ourproject.kune.app.server.wave;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.waveprotocol.box.server.persistence.AttachmentStore;
+import org.waveprotocol.box.server.persistence.AttachmentStore.AttachmentData;
+import org.waveprotocol.wave.util.logging.Log;
+
+import com.google.inject.Inject;
+
+/**
+ * An attachment servlet is a simple servlet that serves up attachments from a
+ * provided store.
+ */
+public class CustomAttachmentServlet extends HttpServlet {
+ private static final Log LOG = Log.get(CustomAttachmentServlet.class);
+
+ private final AttachmentStore store;
+
+ @Inject
+ public CustomAttachmentServlet(AttachmentStore store) {
+ this.store = store;
+ }
+
+ /**
+ * Get the attachment id from the URL in the request.
+ *
+ * @param request
+ * @return the id of the referenced attachment.
+ */
+ private static String getAttachmentIdFromRequest(HttpServletRequest request) {
+ if (request.getPathInfo().length() == 0) {
+ return "";
+ }
+
+ // Discard the leading '/' in the pathinfo. Whats left will be the
+ // attachment id.
+ return request.getPathInfo().substring(1);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ // TODO: Authenticate that the user has permission to access the
+ // attachment.
+
+ String attachmentId = getAttachmentIdFromRequest(request);
+ if (attachmentId.length() == 0) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ AttachmentData data = store.getAttachment(attachmentId);
+
+ if (data == null) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ response.setContentType("text/html");
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentLength((int) data.getContentSize());
+ response.setDateHeader("Last-Modified", data.getLastModifiedDate().getTime());
+ data.writeDataTo(response.getOutputStream());
+
+ LOG.info("Fetched attachment with id '" + attachmentId + "'");
+ }
+
+ @Override
+ protected void doPut(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
+ // TODO: Authenticate the attachment data
+
+ String attachmentId = getAttachmentIdFromRequest(request);
+ if (attachmentId.length() == 0) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ store.storeAttachment(attachmentId, request.getInputStream());
+
+ response.setContentType("text/html");
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.getWriter().write("<html><body><h1>Data written</h1></body></html>");
+
+ LOG.info("Added attachment with id '" + attachmentId + "'");
+ }
+}
Added: trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomFetchServlet.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomFetchServlet.java (rev 0)
+++ trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomFetchServlet.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,186 @@
+/**
+ * Copyright 2010 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 org.ourproject.kune.app.server.wave;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.waveprotocol.box.common.comms.WaveClientRpc.DocumentSnapshot;
+import org.waveprotocol.box.common.comms.WaveClientRpc.WaveViewSnapshot;
+import org.waveprotocol.box.common.comms.WaveClientRpc.WaveletSnapshot;
+import org.waveprotocol.box.server.authentication.SessionManager;
+import org.waveprotocol.box.server.frontend.WaveletSnapshotAndVersion;
+import org.waveprotocol.box.server.rpc.ProtoSerializer;
+import org.waveprotocol.box.server.waveserver.WaveletProvider;
+import org.waveprotocol.box.server.waveserver.WaveletStateException;
+import org.waveprotocol.wave.model.id.WaveletId;
+import org.waveprotocol.wave.model.id.WaveletName;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.model.waveref.InvalidWaveRefException;
+import org.waveprotocol.wave.model.waveref.WaveRef;
+import org.waveprotocol.wave.util.escapers.jvm.JavaWaverefEncoder;
+import org.waveprotocol.wave.util.logging.Log;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.Inject;
+import com.google.protobuf.MessageLite;
+
+/**
+ * A servlet for static fetching of wave data. Typically, the servlet will be
+ * hosted on /fetch/*. A document, a wavelet, or a whole wave can be specified
+ * in the URL.
+ *
+ * Valid request formats are: Fetch a wave: GET /fetch/wavedomain.com/waveid
+ * Fetch a wavelet: GET /fetch/wavedomain.com/waveid/waveletdomain.com/waveletid
+ * Fetch a document: GET
+ * /fetch/wavedomain.com/waveid/waveletdomain.com/waveletid/b+abc123
+ *
+ * The format of the returned information is the protobuf-JSON format used by
+ * the websocket interface.
+ */
+public class CustomFetchServlet extends HttpServlet {
+ private static final Log LOG = Log.get(CustomFetchServlet.class);
+
+ @Inject
+ public CustomFetchServlet(WaveletProvider waveletProvider, ProtoSerializer serializer, SessionManager sessionManager) {
+ this.waveletProvider = waveletProvider;
+ this.serializer = serializer;
+ this.sessionManager = sessionManager;
+ }
+
+ private final ProtoSerializer serializer;
+ private final WaveletProvider waveletProvider;
+ private final SessionManager sessionManager;
+
+ /**
+ * Create an http response to the fetch query. Main entrypoint for this
+ * class.
+ */
+ @Override
+ @VisibleForTesting
+ protected void doGet(HttpServletRequest req, HttpServletResponse response) throws IOException {
+ ParticipantId user = sessionManager.getLoggedInUser(req.getSession(false));
+
+ // This path will look like "/example.com/w+abc123/foo.com/conv+root
+ // Strip off the leading '/'.
+ String urlPath = req.getPathInfo().substring(1);
+
+ // Extract the name of the wavelet from the URL
+ WaveRef waveref;
+ try {
+ waveref = JavaWaverefEncoder.decodeWaveRefFromPath(urlPath);
+ } catch (InvalidWaveRefException e) {
+ // The URL contains an invalid waveref. There's no document at this
+ // path.
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ renderSnapshot(waveref, user, response);
+ }
+
+ private void serializeObjectToServlet(MessageLite message, HttpServletResponse dest) throws IOException {
+ if (message == null) {
+ // Snapshot is null. It would be nice to 404 here, but we can't let
+ // clients guess valid wavelet ids that they're not authorized to
+ // access.
+ dest.sendError(HttpServletResponse.SC_FORBIDDEN);
+ } else {
+ dest.setStatus(HttpServletResponse.SC_OK);
+ dest.setContentType("application/json");
+
+ // This is a hack to make sure the fetched data is fresh.
+ // TODO(josephg): Change this so that browsers can cache wave
+ // snapshots. Probably need:
+ // 'Cache-Control: must-revalidate, private' and an ETag with the
+ // wave[let]'s version.
+ dest.setHeader("Cache-Control", "no-store");
+
+ serializer.writeTo(dest.getWriter(), message);
+ }
+ }
+
+ /**
+ * Render the requested waveref out to the HttpServletResponse dest.
+ *
+ * @param waveref
+ * The referenced wave. Could be a whole wave, a wavelet or just
+ * a document.
+ * @param dest
+ * The servlet response to render the snapshot out to.
+ * @throws IOException
+ */
+ private void renderSnapshot(WaveRef waveref, ParticipantId requester, HttpServletResponse dest) throws IOException {
+ // TODO(josephg): Its currently impossible to fetch all wavelets inside
+ // a
+ // wave that are visible to the user. Until this is fixed, if no wavelet
+ // is
+ // specified we'll just return the conv+root.
+ WaveletId waveletId = waveref.hasWaveletId() ? waveref.getWaveletId() : WaveletId.of(
+ waveref.getWaveId().getDomain(), "conv+root");
+
+ WaveletName waveletName = WaveletName.of(waveref.getWaveId(), waveletId);
+
+ try {
+ if (!waveletProvider.checkAccessPermission(waveletName, requester)) {
+ dest.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ } catch (WaveletStateException e) {
+ throw new IOException(e);
+ }
+
+ LOG.info("Fetching snapshot of wavelet " + waveletName);
+ WaveletSnapshotAndVersion snapshotAndV = waveletProvider.getSnapshot(waveletName);
+
+ if (snapshotAndV != null) {
+ WaveletSnapshot snapshot = snapshotAndV.snapshot;
+ if (waveref.hasDocumentId()) {
+ // We have a wavelet id and document id. Find the document in
+ // the
+ // snapshot
+ // and return it.
+ DocumentSnapshot docSnapshot = null;
+ for (DocumentSnapshot ds : snapshot.getDocumentList()) {
+ if (ds.getDocumentId().equals(waveref.getDocumentId())) {
+ docSnapshot = ds;
+ break;
+ }
+ }
+ serializeObjectToServlet(docSnapshot, dest);
+ } else if (waveref.hasWaveletId()) {
+ // We have a wavelet id. Pull up the wavelet snapshot and return
+ // it.
+ serializeObjectToServlet(snapshot, dest);
+ } else {
+ // Wrap the conv+root we fetched earlier in a WaveSnapshot
+ // object and
+ // send it.
+ WaveViewSnapshot waveSnapshot = WaveViewSnapshot.newBuilder().setWaveId(waveref.getWaveId().serialise()).addWavelet(
+ snapshot).build();
+
+ serializeObjectToServlet(waveSnapshot, dest);
+ }
+ } else {
+ dest.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ }
+}
Added: trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomServerModule.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomServerModule.java (rev 0)
+++ trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomServerModule.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,119 @@
+package org.ourproject.kune.app.server.wave;
+
+/**
+ * Copyright 2009 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.
+ *
+ */
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.security.auth.login.Configuration;
+
+import org.eclipse.jetty.server.session.HashSessionManager;
+import org.waveprotocol.box.server.CoreSettings;
+import org.waveprotocol.box.server.authentication.SessionManager;
+import org.waveprotocol.box.server.rpc.ProtoSerializer;
+import org.waveprotocol.box.server.rpc.ServerRpcProvider;
+import org.waveprotocol.box.server.waveserver.WaveServerImpl;
+import org.waveprotocol.box.server.waveserver.WaveServerModule;
+import org.waveprotocol.wave.federation.FederationHostBridge;
+import org.waveprotocol.wave.federation.FederationRemoteBridge;
+import org.waveprotocol.wave.federation.WaveletFederationListener;
+import org.waveprotocol.wave.federation.WaveletFederationProvider;
+import org.waveprotocol.wave.model.id.IdGenerator;
+import org.waveprotocol.wave.model.id.IdGeneratorImpl;
+import org.waveprotocol.wave.model.id.IdGeneratorImpl.Seed;
+import org.waveprotocol.wave.model.id.TokenGenerator;
+import org.waveprotocol.wave.model.id.TokenGeneratorImpl;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+/**
+ * Guice Module for the prototype Server.
+ *
+ *
+ */
+public class CustomServerModule extends AbstractModule {
+ private final boolean enableFederation;
+
+ public CustomServerModule(boolean enableFederation) {
+ this.enableFederation = enableFederation;
+ }
+
+ @Override
+ protected void configure() {
+ // Receive updates from the outside world, and push them into our local
+ // Wave
+ // Server.
+ bind(WaveletFederationListener.Factory.class).annotatedWith(FederationRemoteBridge.class).to(
+ WaveServerImpl.class);
+
+ // Provide history and respond to submits about our own local waves.
+ bind(WaveletFederationProvider.class).annotatedWith(FederationHostBridge.class).to(WaveServerImpl.class);
+
+ install(new WaveServerModule(enableFederation));
+ TypeLiteral<List<String>> certs = new TypeLiteral<List<String>>() {
+ };
+ bind(certs).annotatedWith(Names.named("certs")).toInstance(Arrays.<String> asList());
+
+ bind(ProtoSerializer.class).in(Singleton.class);
+
+ bind(Configuration.class).toInstance(Configuration.getConfiguration());
+ bind(SessionManager.class).to(CustomSessionManagerImpl.class).in(Singleton.class);
+
+ bind(org.eclipse.jetty.server.SessionManager.class).to(HashSessionManager.class).in(Singleton.class);
+
+ bind(ServerRpcProvider.class).in(Singleton.class);
+ }
+
+ @Provides
+ @Singleton
+ @Inject
+ public IdGenerator provideIdGenerator(@Named(CoreSettings.WAVE_SERVER_DOMAIN) String domain, Seed seed) {
+ return new IdGeneratorImpl(domain, seed);
+ }
+
+ @Provides
+ @Singleton
+ public SecureRandom provideSecureRandom() {
+ return new SecureRandom();
+ }
+
+ @Provides
+ @Singleton
+ @Inject
+ public TokenGenerator provideTokenGenerator(SecureRandom random) {
+ return new TokenGeneratorImpl(random);
+ }
+
+ @Provides
+ @Singleton
+ @Inject
+ public Seed provideSeed(final SecureRandom random) {
+ return new Seed() {
+ @Override
+ public String get() {
+ return Long.toString(Math.abs(random.nextLong()), 36);
+ }
+ };
+ }
+}
\ No newline at end of file
Added: trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSessionManagerImpl.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSessionManagerImpl.java (rev 0)
+++ trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSessionManagerImpl.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,116 @@
+/**
+ * Copyright 2010 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 org.ourproject.kune.app.server.wave;
+
+import javax.servlet.http.HttpSession;
+
+import org.waveprotocol.box.server.account.AccountData;
+import org.waveprotocol.box.server.authentication.SessionManager;
+import org.waveprotocol.box.server.authentication.SessionManagerImpl;
+import org.waveprotocol.box.server.persistence.AccountStore;
+import org.waveprotocol.box.server.persistence.PersistenceException;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.util.escapers.PercentEscaper;
+import org.waveprotocol.wave.util.logging.Log;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.inject.Inject;
+
+/**
+ * Utility class for managing the session's authentication status.
+ *
+ * @author josephg at gmail.com (Joseph Gentle)
+ */
+
+public class CustomSessionManagerImpl implements SessionManager {
+ private static final String USER_FIELD = "user";
+
+ private final AccountStore accountStore;
+ private final org.eclipse.jetty.server.SessionManager jettySessionManager;
+
+ private static final Log LOG = Log.get(SessionManagerImpl.class);
+
+ @Inject
+ public CustomSessionManagerImpl(AccountStore accountStore,
+ org.eclipse.jetty.server.SessionManager jettySessionManager) {
+ Preconditions.checkNotNull(accountStore, "Null account store");
+ Preconditions.checkNotNull(jettySessionManager, "Null jetty session manager");
+ this.accountStore = accountStore;
+ this.jettySessionManager = jettySessionManager;
+ }
+
+ @Override
+ public ParticipantId getLoggedInUser(HttpSession session) {
+ if (session != null) {
+ return (ParticipantId) session.getAttribute(USER_FIELD);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public AccountData getLoggedInAccount(HttpSession session) {
+ // Consider caching the account data in the session object.
+ ParticipantId user = getLoggedInUser(session);
+ if (user != null) {
+ try {
+ return accountStore.getAccount(user);
+ } catch (PersistenceException e) {
+ LOG.warning("Failed to retrieve account data for " + user, e);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void setLoggedInUser(HttpSession session, ParticipantId id) {
+ Preconditions.checkNotNull(session, "Session is null");
+ Preconditions.checkNotNull(id, "Participant id is null");
+ session.setAttribute(USER_FIELD, id);
+ }
+
+ @Override
+ public void logout(HttpSession session) {
+ if (session != null) {
+ // This function should also remove any other bound fields in the
+ // session
+ // object.
+ session.removeAttribute(USER_FIELD);
+ }
+ }
+
+ @Override
+ public String getLoginUrl(String redirect) {
+ if (Strings.isNullOrEmpty(redirect)) {
+ return SIGN_IN_URL;
+ } else {
+ PercentEscaper escaper = new PercentEscaper(PercentEscaper.SAFEQUERYSTRINGCHARS_URLENCODER, false);
+ String queryStr = "?r=" + escaper.escape(redirect);
+ return SIGN_IN_URL + queryStr;
+ }
+ }
+
+ @Override
+ public HttpSession getSessionFromToken(String token) {
+ Preconditions.checkNotNull(token);
+ return jettySessionManager.getHttpSession(token);
+ }
+}
Added: trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSettingsBinder.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSettingsBinder.java (rev 0)
+++ trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomSettingsBinder.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2009 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 org.ourproject.kune.app.server.wave;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.SystemConfiguration;
+import org.waveprotocol.wave.util.settings.Setting;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+/**
+ * A property file parsing system that converts a given settings class into a
+ * Guice module with injectable
+ *
+ * @Named parameters.
+ *
+ * Originally based on some CLI work by arb at google.com (Anthony Baxter).
+ * Refactored by tad.glines at gmail.com (Tad Glines) to use Commons
+ * Configuration and add support for List<String>.
+ */
+public class CustomSettingsBinder {
+
+ /**
+ * Used to validate that a type is supported. Some types may have generic
+ * parameters that need to be checked.
+ */
+ private interface SettingTypeValidator {
+ boolean check(Type type);
+ }
+
+ private static final Map<Type, SettingTypeValidator> supportedSettingTypes;
+
+ /**
+ * This default validator just returns true.
+ */
+ private static final SettingTypeValidator DEFAULT_TYPE_VALIDATOR = new SettingTypeValidator() {
+ @Override
+ public boolean check(Type type) {
+ return true;
+ }
+ };
+
+ /**
+ * This validator checks to make sure the {@link List}'s generic parameter
+ * is also supported.
+ */
+ private static final SettingTypeValidator LIST_TYPE_VALIDATOR = new SettingTypeValidator() {
+ @Override
+ public boolean check(Type type) {
+ if (type instanceof ParameterizedType) {
+ Type[] args = ((ParameterizedType) type).getActualTypeArguments();
+ if (args.length == 1) {
+ // At the moment only List<String> is supported.
+ if (args[0] == String.class) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ };
+
+ static {
+ ImmutableMap.Builder<Type, SettingTypeValidator> builder = ImmutableMap.builder();
+ builder.put(int.class, DEFAULT_TYPE_VALIDATOR);
+ builder.put(boolean.class, DEFAULT_TYPE_VALIDATOR);
+ builder.put(String.class, DEFAULT_TYPE_VALIDATOR);
+ builder.put(List.class, LIST_TYPE_VALIDATOR);
+ supportedSettingTypes = builder.build();
+ }
+
+ /**
+ * Bind configuration parameters into Guice Module.
+ *
+ * @return a Guice module configured with setting support.
+ * @throws ConfigurationException
+ * on configuration error
+ */
+ public static Module bindSettings(String propertyFile, Class<?>... settingsArg) throws ConfigurationException {
+ final CompositeConfiguration config = new CompositeConfiguration();
+ config.addConfiguration(new SystemConfiguration());
+ config.addConfiguration(new PropertiesConfiguration(propertyFile));
+
+ List<Field> fields = new ArrayList<Field>();
+ for (Class<?> settings : settingsArg) {
+ fields.addAll(Arrays.asList(settings.getDeclaredFields()));
+ }
+
+ // Reflect on settings class and absorb settings
+ final Map<Setting, Field> settings = new LinkedHashMap<Setting, Field>();
+ for (Field field : fields) {
+ if (!field.isAnnotationPresent(Setting.class)) {
+ continue;
+ }
+
+ // Validate target type
+ SettingTypeValidator typeHelper = supportedSettingTypes.get(field.getType());
+ if (typeHelper == null || !typeHelper.check(field.getGenericType())) {
+ throw new IllegalArgumentException(field.getType() + " is not one of the supported setting types");
+ }
+
+ Setting setting = field.getAnnotation(Setting.class);
+ settings.put(setting, field);
+ }
+
+ // Now validate them
+ List<String> missingProperties = new ArrayList<String>();
+ for (Setting setting : settings.keySet()) {
+ if (setting.defaultValue().isEmpty()) {
+ if (!config.containsKey(setting.name())) {
+ missingProperties.add(setting.name());
+ }
+ }
+ }
+ if (missingProperties.size() > 0) {
+ StringBuilder error = new StringBuilder();
+ error.append("The following required properties are missing from the server configuration: ");
+ error.append(Joiner.on(", ").join(missingProperties));
+ throw new ConfigurationException(error.toString());
+ }
+
+ // bundle everything up in an injectable guice module
+ return new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ // We must iterate the settings a third time when binding.
+ // Note: do not collapse these loops as that will damage
+ // early error detection. The runtime is still O(n) in setting
+ // count.
+ for (Map.Entry<Setting, Field> entry : settings.entrySet()) {
+ Class<?> type = entry.getValue().getType();
+ Setting setting = entry.getKey();
+
+ if (int.class.equals(type)) {
+ Integer defaultValue = null;
+ if (!setting.defaultValue().isEmpty()) {
+ defaultValue = Integer.parseInt(setting.defaultValue());
+ }
+ bindConstant().annotatedWith(Names.named(setting.name())).to(
+ config.getInteger(setting.name(), defaultValue));
+ } else if (boolean.class.equals(type)) {
+ Boolean defaultValue = null;
+ if (!setting.defaultValue().isEmpty()) {
+ defaultValue = Boolean.parseBoolean(setting.defaultValue());
+ }
+ bindConstant().annotatedWith(Names.named(setting.name())).to(
+ config.getBoolean(setting.name(), defaultValue));
+ } else if (String.class.equals(type)) {
+ bindConstant().annotatedWith(Names.named(setting.name())).to(
+ config.getString(setting.name(), setting.defaultValue()));
+ } else {
+ String[] value = config.getStringArray(setting.name());
+ if (value.length == 0 && !setting.defaultValue().isEmpty()) {
+ value = setting.defaultValue().split(",");
+ }
+ bind(new TypeLiteral<List<String>>() {
+ }).annotatedWith(Names.named(setting.name())).toInstance(ImmutableList.copyOf(value));
+ }
+ }
+ }
+ };
+ }
+}
Added: trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomUserRegistrationServlet.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomUserRegistrationServlet.java (rev 0)
+++ trunk/src/main/java/org/ourproject/kune/app/server/wave/CustomUserRegistrationServlet.java 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,147 @@
+/**
+ * Copyright 2010 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 org.ourproject.kune.app.server.wave;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.waveprotocol.box.server.CoreSettings;
+import org.waveprotocol.box.server.account.HumanAccountDataImpl;
+import org.waveprotocol.box.server.authentication.HttpRequestBasedCallbackHandler;
+import org.waveprotocol.box.server.authentication.PasswordDigest;
+import org.waveprotocol.box.server.gxp.UserRegistrationPage;
+import org.waveprotocol.box.server.persistence.AccountStore;
+import org.waveprotocol.box.server.persistence.PersistenceException;
+import org.waveprotocol.box.server.rpc.AuthenticationServlet;
+import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.util.logging.Log;
+
+import com.google.gxp.base.GxpContext;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+/**
+ * The user registration servlet allows new users to register accounts.
+ *
+ * @author josephg at gmail.com (Joseph Gentle)
+ */
+public class CustomUserRegistrationServlet extends HttpServlet {
+ private final AccountStore accountStore;
+ private final String domain;
+
+ private final Log LOG = Log.get(CustomUserRegistrationServlet.class);
+
+ @Inject
+ public CustomUserRegistrationServlet(AccountStore accountStore,
+ @Named(CoreSettings.WAVE_SERVER_DOMAIN) String domain) {
+ this.accountStore = accountStore;
+ this.domain = domain;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ writeRegistrationPage("", AuthenticationServlet.RESPONSE_STATUS_NONE, req.getLocale(), resp);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String message = tryCreateUser(req.getParameter(HttpRequestBasedCallbackHandler.ADDRESS_FIELD),
+ req.getParameter(HttpRequestBasedCallbackHandler.PASSWORD_FIELD));
+ String responseType = AuthenticationServlet.RESPONSE_STATUS_SUCCESS;
+
+ if (message != null) {
+ resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ responseType = AuthenticationServlet.RESPONSE_STATUS_FAILED;
+ } else {
+ message = "Registration complete.";
+ resp.setStatus(HttpServletResponse.SC_OK);
+ }
+
+ writeRegistrationPage(message, responseType, req.getLocale(), resp);
+ }
+
+ /**
+ * Try to create a user with the provided username and password. On error,
+ * returns a string containing an error message. On success, returns null.
+ */
+ private String tryCreateUser(String username, String password) {
+ String message = null;
+ ParticipantId id = null;
+
+ try {
+ // First, some cleanup on the parameters.
+ if (username == null) {
+ return "Username portion of address cannot be less than 2 characters";
+ }
+ username = username.trim().toLowerCase();
+ if (username.contains(ParticipantId.DOMAIN_PREFIX)) {
+ id = ParticipantId.of(username);
+ } else {
+ id = ParticipantId.of(username + ParticipantId.DOMAIN_PREFIX + domain);
+ }
+ if (id.getAddress().indexOf("@") < 2) {
+ return "Username portion of address cannot be less than 2 characters";
+ }
+ String[] usernameSplit = id.getAddress().split("@");
+ if (usernameSplit.length != 2 || !usernameSplit[0].matches("[\\w\\.]+")) {
+ return "Only letters (a-z), numbers (0-9), and periods (.) are allowed in Username";
+ }
+ if (!id.getDomain().equals(domain)) {
+ return "You can only create users at the " + domain + " domain";
+ }
+ } catch (InvalidParticipantAddress e) {
+ return "Invalid username";
+ }
+
+ try {
+ if (accountStore.getAccount(id) != null) {
+ return "Account already exists";
+ }
+ } catch (PersistenceException e) {
+ LOG.severe("Failed to retreive account data for " + id, e);
+ return "An unexpected error occured while trying to retrieve account status";
+ }
+
+ if (password == null) {
+ // Register the user with an empty password.
+ password = "";
+ }
+
+ HumanAccountDataImpl account = new HumanAccountDataImpl(id, new PasswordDigest(password.toCharArray()));
+ try {
+ accountStore.putAccount(account);
+ } catch (PersistenceException e) {
+ LOG.severe("Failed to create new account for " + id, e);
+ return "An unexpected error occured while trying to create the account";
+ }
+
+ return null;
+ }
+
+ private void writeRegistrationPage(String message, String responseType, Locale locale, HttpServletResponse dest)
+ throws IOException {
+ dest.setContentType("text/html");
+ UserRegistrationPage.write(dest.getWriter(), new GxpContext(locale), domain, message, responseType);
+ }
+}
Added: trunk/src/main/resources/wave-server.properties
===================================================================
--- trunk/src/main/resources/wave-server.properties (rev 0)
+++ trunk/src/main/resources/wave-server.properties 2010-12-29 03:37:12 UTC (rev 1175)
@@ -0,0 +1,61 @@
+# Core Configuration for the Wave in a Box server
+#
+
+# Domain name of the wave server
+wave_server_domain = local
+
+# A comma separated list of address on which to listen for connections.
+# Each address is a comma separated host:port pair.
+http_frontend_public_address = localhost:9898
+http_frontend_addresses = ${http_frontend_public_address}
+
+### Server-specific variables
+###
+
+# Settings for the different persistence stores. Currently supported: memory, file, mongodb
+signer_info_store_type = memory
+
+# The location where signer info certificate data is stored on disk. This should be changed.
+# Note: This is only used when using the file signer info store. It is ignored
+# for other data store types.
+signer_info_store_directory = _certificates
+
+# Currently supported attachment types: mongodb, disk
+attachment_store_type = disk
+
+# The location where attachments are stored on disk. This should be changed.
+# Note: This is only used when using the disk attachment store. It is ignored
+# for other data store types.
+attachment_store_directory = _attachments
+
+# Currently supported account store types: fake, memory, file, mongodb
+account_store_type = memory
+
+# The location where accounts are stored on disk. This should be changed.
+# Note: This is only used when using the file account store. It is ignored
+# for other data store types.
+account_store_directory = _accounts
+
+# Currently supported delta store types: memory, file
+delta_store_type = memory
+
+# The location where deltas are stored on disk. This should be changed.
+# Note: This is only used when using the file delta store. It is ignored
+# for other data store types.
+delta_store_directory = _deltas
+
+# Set true to use Socket.IO instead of raw WebSockets in the webclient.
+use_socketio = false
+
+# To enable federation, edit the server.federation.config file and uncomment the line below
+#include = server.federation.config
+
+# These two parameters MUST appear in this file AFTER the above include of the
+# federation config file. This is necesary so that the federation config file
+# can override these two values.
+
+# Set true to disable the verification of signed deltas
+waveserver_disable_verification = true
+
+# Set true to disable the verification of signers (certificates)
+waveserver_disable_signer_verification = true
More information about the kune-commits
mailing list