| /******************************************************************************* |
| * Copyright (c) 2010 BSI Business Systems Integration AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * BSI Business Systems Integration AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.scout.rt.client.services.common.code; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.scout.commons.CompositeObject; |
| import org.eclipse.scout.commons.LocaleThreadLocal; |
| import org.eclipse.scout.commons.annotations.Priority; |
| import org.eclipse.scout.commons.holders.Holder; |
| import org.eclipse.scout.commons.logger.IScoutLogger; |
| import org.eclipse.scout.commons.logger.ScoutLogManager; |
| import org.eclipse.scout.commons.osgi.BundleClassDescriptor; |
| import org.eclipse.scout.rt.client.ClientJob; |
| import org.eclipse.scout.rt.client.ClientSyncJob; |
| import org.eclipse.scout.rt.client.IClientSession; |
| import org.eclipse.scout.rt.client.services.common.clientnotification.ClientNotificationConsumerEvent; |
| import org.eclipse.scout.rt.client.services.common.clientnotification.IClientNotificationConsumerListener; |
| import org.eclipse.scout.rt.client.services.common.clientnotification.IClientNotificationConsumerService; |
| import org.eclipse.scout.rt.servicetunnel.ServiceTunnelUtility; |
| import org.eclipse.scout.rt.shared.services.common.code.CodeTypeChangedNotification; |
| import org.eclipse.scout.rt.shared.services.common.code.ICode; |
| import org.eclipse.scout.rt.shared.services.common.code.ICodeService; |
| import org.eclipse.scout.rt.shared.services.common.code.ICodeType; |
| import org.eclipse.scout.rt.shared.services.common.code.ICodeVisitor; |
| import org.eclipse.scout.service.AbstractService; |
| import org.eclipse.scout.service.SERVICES; |
| import org.osgi.framework.ServiceRegistration; |
| |
| /** |
| * maintains a cache of ICodeType objects that can be (re)loaded using the |
| * methods loadCodeType, loadCodeTypes if getters and finders are called with |
| * partitionId, cache is not used. |
| * <p> |
| * Service state is per [{@link IClientSession}.class,{@link LocaleThreadLocal#get()},partitionId] |
| */ |
| @Priority(-3) |
| public class CodeServiceClientProxy extends AbstractService implements ICodeService { |
| private static final IScoutLogger LOG = ScoutLogManager.getLogger(CodeServiceClientProxy.class); |
| |
| private final Object m_stateLock = new Object(); |
| private final HashMap<CompositeObject, ServiceState> m_stateMap = new HashMap<CompositeObject, ServiceState>(); |
| |
| public CodeServiceClientProxy() { |
| } |
| |
| private ServiceState getServiceState() { |
| return getServiceState(null); |
| } |
| |
| private ServiceState getServiceState(Long partitionId) { |
| IClientSession session = ClientJob.getCurrentSession(); |
| if (session == null) { |
| LOG.warn("could not find a client session"); |
| return null; |
| } |
| if (partitionId == null) { |
| if (session.getSharedVariableMap().containsKey(ICodeType.PROP_PARTITION_ID)) { |
| partitionId = (Long) session.getSharedVariableMap().get(ICodeType.PROP_PARTITION_ID); |
| } |
| } |
| CompositeObject key = new CompositeObject(session.getClass(), LocaleThreadLocal.get(), partitionId); |
| synchronized (m_stateLock) { |
| ServiceState data = (ServiceState) m_stateMap.get(key); |
| if (data == null) { |
| data = new ServiceState(); |
| m_stateMap.put(key, data); |
| } |
| return data; |
| } |
| } |
| |
| @Override |
| public void initializeService(ServiceRegistration registration) { |
| super.initializeService(registration); |
| // add client notification listener |
| SERVICES.getService(IClientNotificationConsumerService.class).addGlobalClientNotificationConsumerListener(new IClientNotificationConsumerListener() { |
| @Override |
| public void handleEvent(final ClientNotificationConsumerEvent e, boolean sync) { |
| if (e.getClientNotification().getClass() == CodeTypeChangedNotification.class) { |
| if (sync) { |
| try { |
| reloadCodeTypes(((CodeTypeChangedNotification) e.getClientNotification()).getCodeTypes()); |
| } |
| catch (Throwable t) { |
| LOG.error("update due to client notification", t); |
| // nop |
| } |
| } |
| else { |
| new ClientSyncJob("Reload code types", ClientSyncJob.getCurrentSession()) { |
| @Override |
| protected void runVoid(IProgressMonitor monitor) throws Throwable { |
| reloadCodeTypes(((CodeTypeChangedNotification) e.getClientNotification()).getCodeTypes()); |
| } |
| }.schedule(); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public <T extends ICodeType> T getCodeType(Class<T> type) { |
| ServiceState state = getServiceState(); |
| synchronized (state.m_cacheLock) { |
| @SuppressWarnings("unchecked") |
| T instance = (T) state.m_cache.get(type); |
| if (instance == null) { |
| instance = getRemoteService().getCodeType(type); |
| if (instance != null) { |
| state.m_cache.put(type, instance); |
| } |
| } |
| return instance; |
| } |
| } |
| |
| @Override |
| public <T extends ICodeType> T getCodeType(Long partitionId, Class<T> type) { |
| T instance = getRemoteService().getCodeType(partitionId, type); |
| return instance; |
| } |
| |
| @Override |
| public ICodeType findCodeTypeById(Object id) { |
| if (id == null) { |
| return null; |
| } |
| ICodeType ct = findCodeTypeByIdInternal(id); |
| if (ct != null) { |
| return ct; |
| } |
| // populate code type cache |
| getAllCodeTypes(""); |
| return findCodeTypeByIdInternal(id); |
| } |
| |
| /** |
| * @return Returns the code type with the given id or <code>null</code> if it is not found in the cache. |
| */ |
| private ICodeType findCodeTypeByIdInternal(Object id) { |
| ServiceState state = getServiceState(); |
| synchronized (state.m_cacheLock) { |
| for (ICodeType ct : state.m_cache.values()) { |
| if (id.equals(ct.getId())) { |
| return ct; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public ICodeType findCodeTypeById(Long partitionId, Object id) { |
| if (id == null) { |
| return null; |
| } |
| ICodeType ct = findCodeTypeByIdInternal(partitionId, id); |
| if (ct != null) { |
| return ct; |
| } |
| // populate code type cache |
| getAllCodeTypes(""); |
| return findCodeTypeByIdInternal(partitionId, id); |
| } |
| |
| /** |
| * @return Returns the code type with the given id and partition or <code>null</code> if it is not found in the cache. |
| */ |
| private ICodeType findCodeTypeByIdInternal(Long partitionId, Object id) { |
| ServiceState state = getServiceState(partitionId); |
| synchronized (state.m_cacheLock) { |
| for (ICodeType ct : state.m_cache.values()) { |
| if (id.equals(ct.getId())) { |
| return ct; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public ICodeType[] getCodeTypes(Class... types) { |
| ArrayList<Class> missingTypes = new ArrayList<Class>(); |
| ICodeType[] instances = new ICodeType[types.length]; |
| ServiceState state = getServiceState(); |
| synchronized (state.m_cacheLock) { |
| for (int i = 0; i < types.length; i++) { |
| instances[i] = state.m_cache.get(types[i]); |
| if (instances[i] == null) { |
| missingTypes.add(types[i]); |
| } |
| } |
| } |
| if (missingTypes.size() > 0) { |
| ICodeType[] newInstances = getRemoteService().getCodeTypes(missingTypes.toArray(new Class[0])); |
| synchronized (state.m_cacheLock) { |
| int k = 0; |
| for (int i = 0; i < types.length; i++) { |
| if (instances[i] == null) { |
| instances[i] = newInstances[k]; |
| if (instances[i] != null) { |
| state.m_cache.put(types[i], instances[i]); |
| } |
| k++; |
| } |
| } |
| } |
| } |
| return instances; |
| } |
| |
| @Override |
| public ICodeType[] getCodeTypes(Long partitionId, Class... types) { |
| ICodeType[] codeTypes = getRemoteService().getCodeTypes(partitionId, types); |
| return codeTypes; |
| } |
| |
| private <T extends ICode> Class getDeclaringCodeTypeClass(final Class<T> type) { |
| if (type == null) { |
| return null; |
| } |
| Class declaringCodeTypeClass = null; |
| if (type.getDeclaringClass() != null) { |
| // code is inner type of code type or another code |
| Class c = type.getDeclaringClass(); |
| while (c != null && !(ICodeType.class.isAssignableFrom(c))) { |
| c = c.getDeclaringClass(); |
| } |
| declaringCodeTypeClass = c; |
| } |
| if (declaringCodeTypeClass == null) { |
| try { |
| declaringCodeTypeClass = type.newInstance().getCodeType().getClass(); |
| } |
| catch (Throwable t) { |
| LOG.error("find code " + type, t); |
| } |
| } |
| return declaringCodeTypeClass; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <T> T findCode(final Class<T> type, ICodeType codeType) { |
| final Holder<ICode> codeHolder = new Holder<ICode>(ICode.class); |
| ICodeVisitor v = new ICodeVisitor() { |
| @Override |
| public boolean visit(ICode code, int treeLevel) { |
| if (code.getClass() == type) { |
| codeHolder.setValue(code); |
| return false; |
| } |
| return true; |
| } |
| }; |
| codeType.visit(v); |
| return (T) codeHolder.getValue(); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T extends ICode> T getCode(final Class<T> type) { |
| Class declaringCodeTypeClass = getDeclaringCodeTypeClass(type); |
| ICodeType codeType = getCodeType(declaringCodeTypeClass); |
| return findCode(type, codeType); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T extends ICode> T getCode(Long partitionId, final Class<T> type) { |
| Class declaringCodeTypeClass = getDeclaringCodeTypeClass(type); |
| ICodeType codeType = getCodeType(partitionId, declaringCodeTypeClass); |
| return findCode(type, codeType); |
| } |
| |
| @Override |
| public <T extends ICodeType> T reloadCodeType(Class<T> type) { |
| unloadCodeType(type); |
| // do NOT call reload on the backend service, clients can not reload codes, |
| // they can just refresh their local cache |
| // In order to reload a code, the call to reload has to be placed on the |
| // server |
| T instance = getRemoteService().getCodeType(type); |
| ServiceState state = getServiceState(); |
| synchronized (state.m_cacheLock) { |
| if (instance != null) { |
| state.m_cache.put(type, instance); |
| } |
| } |
| return instance; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public ICodeType[] reloadCodeTypes(Class... types) { |
| for (int i = 0; i < types.length; i++) { |
| unloadCodeType(types[i]); |
| } |
| // do NOT call reload on the backend service, clients can not reload codes, |
| // they can just refresh their local cache |
| // In order to reload a code, the call to reload has to be placed on the |
| // server |
| ICodeType[] instances = getRemoteService().getCodeTypes(types); |
| ServiceState state = getServiceState(); |
| synchronized (state.m_cacheLock) { |
| for (int i = 0; i < types.length; i++) { |
| if (instances[i] != null) { |
| state.m_cache.put(types[i], instances[i]); |
| } |
| } |
| } |
| return instances; |
| } |
| |
| @Override |
| public BundleClassDescriptor[] getAllCodeTypeClasses(String classPrefix) { |
| if (classPrefix == null) { |
| return new BundleClassDescriptor[0]; |
| } |
| ServiceState state = getServiceState(); |
| synchronized (state.m_codeTypeClassDescriptorMapLock) { |
| BundleClassDescriptor[] a = state.m_codeTypeClassDescriptorMap.get(classPrefix); |
| if (a != null) { |
| return a; |
| } |
| // load code types from server-side |
| HashSet<BundleClassDescriptor> verifiedCodeTypes = new HashSet<BundleClassDescriptor>(); |
| BundleClassDescriptor[] remoteCodeTypes = getRemoteService().getAllCodeTypeClasses(classPrefix); |
| for (BundleClassDescriptor d : remoteCodeTypes) { |
| try { |
| // check whether code type is available on client-side |
| Platform.getBundle(d.getBundleSymbolicName()).loadClass(d.getClassName()); |
| verifiedCodeTypes.add(d); |
| } |
| catch (Throwable t) { |
| LOG.error("Missing code-type in client: " + d.getClassName() + ", defined in server-side bundle " + d.getBundleSymbolicName()); |
| } |
| } |
| // |
| a = verifiedCodeTypes.toArray(new BundleClassDescriptor[verifiedCodeTypes.size()]); |
| state.m_codeTypeClassDescriptorMap.put(classPrefix, a); |
| return a; |
| } |
| } |
| |
| @Override |
| public ICodeType[] getAllCodeTypes(String classPrefix) { |
| ArrayList<Class> list = new ArrayList<Class>(); |
| for (BundleClassDescriptor d : getAllCodeTypeClasses(classPrefix)) { |
| try { |
| list.add(Platform.getBundle(d.getBundleSymbolicName()).loadClass(d.getClassName())); |
| } |
| catch (Throwable t) { |
| LOG.warn("Loading " + d.getClassName() + " of bundle " + d.getBundleSymbolicName(), t); |
| continue; |
| } |
| } |
| return getCodeTypes(list.toArray(new Class[list.size()])); |
| } |
| |
| @Override |
| public ICodeType[] getAllCodeTypes(String classPrefix, Long partitionId) { |
| return getAllCodeTypes(classPrefix); |
| } |
| |
| private <T extends ICodeType> void unloadCodeType(Class<T> type) { |
| ServiceState state = getServiceState(); |
| synchronized (state.m_cacheLock) { |
| state.m_cache.remove(type); |
| } |
| } |
| |
| private ICodeService getRemoteService() { |
| return ServiceTunnelUtility.createProxy(ICodeService.class, ClientSyncJob.getCurrentSession().getServiceTunnel()); |
| } |
| |
| private static class ServiceState { |
| final Object m_cacheLock = new Object(); |
| final HashMap<Class<? extends ICodeType>, ICodeType> m_cache = new HashMap<Class<? extends ICodeType>, ICodeType>(); |
| // |
| final Object m_codeTypeClassDescriptorMapLock = new Object(); |
| final HashMap<String, BundleClassDescriptor[]> m_codeTypeClassDescriptorMap = new HashMap<String, BundleClassDescriptor[]>(); |
| } |
| } |