| /***************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.cayenne.remote.service; |
| |
| import org.apache.cayenne.CayenneRuntimeException; |
| import org.apache.cayenne.DataChannel; |
| import org.apache.cayenne.access.ClientServerChannel; |
| import org.apache.cayenne.access.DataContext; |
| import org.apache.cayenne.configuration.Constants; |
| import org.apache.cayenne.configuration.ObjectContextFactory; |
| import org.apache.cayenne.remote.ClientMessage; |
| import org.apache.cayenne.remote.RemoteService; |
| import org.apache.cayenne.remote.RemoteSession; |
| import org.apache.cayenne.util.Util; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import java.rmi.RemoteException; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * A generic implementation of an RemoteService. Can be subclassed to work with |
| * different remoting mechanisms, such as Hessian or JAXRPC. |
| * |
| * @since 1.2 |
| */ |
| public abstract class BaseRemoteService implements RemoteService { |
| |
| // keep logger non-static so that it could be garbage collected with this |
| // instance. |
| protected final Log logger; |
| |
| protected ObjectContextFactory contextFactory; |
| protected String eventBridgeFactoryName; |
| protected Map<String, String> eventBridgeParameters; |
| |
| /** |
| * @since 3.1 |
| */ |
| public BaseRemoteService(ObjectContextFactory contextFactory, Map<String, String> eventBridgeProperties) { |
| |
| logger = LogFactory.getLog(getClass()); |
| |
| // start Cayenne service |
| logger.debug("ROP service is starting"); |
| |
| this.contextFactory = contextFactory; |
| initEventBridgeParameters(eventBridgeProperties); |
| |
| logger.debug(getClass().getName() + " started"); |
| } |
| |
| public String getEventBridgeFactoryName() { |
| return eventBridgeFactoryName; |
| } |
| |
| public Map<String, String> getEventBridgeParameters() { |
| return eventBridgeParameters != null ? Collections.unmodifiableMap(eventBridgeParameters) |
| : Collections.EMPTY_MAP; |
| } |
| |
| /** |
| * Creates a new ServerSession with a dedicated DataChannel. |
| */ |
| protected abstract ServerSession createServerSession(); |
| |
| /** |
| * Creates a new ServerSession based on a shared DataChannel. |
| * |
| * @param name |
| * shared session name used to lookup a shared DataChannel. |
| */ |
| protected abstract ServerSession createServerSession(String name); |
| |
| /** |
| * Returns a ServerSession object that represents Cayenne-related state |
| * associated with the current session. If ServerSession hasn't been |
| * previously saved, returns null. |
| */ |
| protected abstract ServerSession getServerSession(); |
| |
| @Override |
| public RemoteSession establishSession() { |
| logger.debug("Session requested by client"); |
| |
| RemoteSession session = createServerSession().getSession(); |
| |
| logger.debug("Established client session: " + session); |
| return session; |
| } |
| |
| @Override |
| public RemoteSession establishSharedSession(String name) { |
| logger.debug("Shared session requested by client. Group name: " + name); |
| |
| if (name == null) { |
| throw new CayenneRuntimeException("Invalid null shared session name"); |
| } |
| |
| return createServerSession(name).getSession(); |
| } |
| |
| @Override |
| public Object processMessage(ClientMessage message) throws Throwable { |
| |
| if (message == null) { |
| throw new IllegalArgumentException("Null client message."); |
| } |
| |
| ServerSession handler = getServerSession(); |
| |
| if (handler == null) { |
| throw new MissingSessionException("No session associated with request."); |
| } |
| |
| logger.debug("processMessage, sessionId: " + handler.getSession().getSessionId()); |
| |
| // intercept and log exceptions |
| try { |
| return DispatchHelper.dispatch(handler.getChannel(), message); |
| } catch (Throwable th) { |
| |
| StringBuilder wrapperMessage = new StringBuilder(); |
| wrapperMessage.append("Exception processing message ").append(message.getClass().getName()) |
| .append(" of type ").append(message); |
| |
| String wrapperMessageString = wrapperMessage.toString(); |
| logger.info(wrapperMessageString, th); |
| |
| // This exception will probably be propagated to the client. |
| // Recast the exception to a serializable form. |
| Exception cause = new Exception(Util.unwindException(th).getLocalizedMessage()); |
| |
| throw new CayenneRuntimeException(wrapperMessageString, cause); |
| } |
| } |
| |
| @Override |
| public void close() throws RemoteException { |
| } |
| |
| protected RemoteSession createRemoteSession(String sessionId, String name, boolean enableEvents) { |
| RemoteSession session = (enableEvents) ? new RemoteSession(sessionId, eventBridgeFactoryName, |
| eventBridgeParameters) : new RemoteSession(sessionId); |
| |
| session.setName(name); |
| return session; |
| } |
| |
| /** |
| * Creates a server-side channel that will handle all client requests. For |
| * shared sessions the same channel instance is reused for the entire group |
| * of clients. For dedicated sessions, one channel per client is created. |
| * <p> |
| * This implementation returns {@link ClientServerChannel} instance wrapping |
| * a DataContext. Subclasses may override the method to customize channel |
| * creation. For instance they may wrap channel in the custom interceptors |
| * to handle transactions or security. |
| */ |
| protected DataChannel createChannel() { |
| return new ClientServerChannel((DataContext) contextFactory.createContext()); |
| } |
| |
| /** |
| * Initializes EventBridge parameters for remote clients peer-to-peer |
| * communications. |
| */ |
| protected void initEventBridgeParameters(Map<String, String> properties) { |
| String eventBridgeFactoryName = properties.get(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY); |
| |
| if (eventBridgeFactoryName != null) { |
| |
| Map<String, String> eventBridgeParameters = new HashMap<>(properties); |
| eventBridgeParameters.remove(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY); |
| |
| this.eventBridgeFactoryName = eventBridgeFactoryName; |
| this.eventBridgeParameters = eventBridgeParameters; |
| } |
| } |
| } |