r1175 - in trunk/src/main: java/cc/kune/core/client/sitebar/logo java/org/ourproject/kune/app/server/wave resources
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 [email protected] (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 [email protected] (Anthony Baxter). + * Refactored by [email protected] (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 [email protected] (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
participants (1)
-
Vicente J. Ruiz Jurado