001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 *     ataillefer
012 */
013package org.nuxeo.ecm.automation.client.jaxrs.spi;
014
015import static org.nuxeo.ecm.automation.client.Constants.CTYPE_AUTOMATION;
016import static org.nuxeo.ecm.automation.client.Constants.CTYPE_ENTITY;
017
018import java.io.IOException;
019
020import org.nuxeo.ecm.automation.client.AdapterFactory;
021import org.nuxeo.ecm.automation.client.AdapterManager;
022import org.nuxeo.ecm.automation.client.AutomationClient;
023import org.nuxeo.ecm.automation.client.LoginCallback;
024import org.nuxeo.ecm.automation.client.LoginInfo;
025import org.nuxeo.ecm.automation.client.Session;
026import org.nuxeo.ecm.automation.client.TokenCallback;
027import org.nuxeo.ecm.automation.client.jaxrs.spi.auth.BasicAuthInterceptor;
028import org.nuxeo.ecm.automation.client.jaxrs.spi.auth.TokenAuthInterceptor;
029import org.nuxeo.ecm.automation.client.jaxrs.spi.marshallers.PojoMarshaller;
030import org.nuxeo.ecm.automation.client.model.OperationRegistry;
031
032/**
033 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
034 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
035 */
036public abstract class AbstractAutomationClient implements AutomationClient {
037
038    private static final Object SHARED_REGISTRY_SYNCHRONIZER = new Object();
039
040    // Use an operation registry shared by the multiple client sessions for
041    // performance reasons
042    private static volatile OperationRegistry sharedRegistry;
043
044    private static long sharedRegistryUpdateTimestamp = 0L;
045
046    private static long sharedRegistryExpirationDelay = 60000L;
047
048    protected final AdapterManager adapterManager = new AdapterManager();
049
050    protected String url;
051
052    protected volatile OperationRegistry registry;
053
054    protected RequestInterceptor requestInterceptor;
055
056    protected AbstractAutomationClient(String url) {
057        this.url = url.endsWith("/") ? url : url + "/";
058    }
059
060    /**
061     * Gets access to this request interceptor
062     */
063    public RequestInterceptor getRequestInterceptor() {
064        return requestInterceptor;
065    }
066
067    /**
068     * Can be used for intercepting requests before they are being sent to the server.
069     */
070    @Override
071    public void setRequestInterceptor(RequestInterceptor interceptor) {
072        requestInterceptor = interceptor;
073    }
074
075    @Override
076    public String getBaseUrl() {
077        return url;
078    }
079
080    public void setBasicAuth(String username, String password) {
081        setRequestInterceptor(new BasicAuthInterceptor(username, password));
082    }
083
084    protected OperationRegistry getRegistry() {
085        return registry;
086    }
087
088    @Override
089    public void registerAdapter(AdapterFactory<?> factory) {
090        adapterManager.registerAdapter(factory);
091    }
092
093    @SuppressWarnings("unchecked")
094    @Override
095    public <T> T getAdapter(Session session, Class<T> adapterType) {
096        return adapterManager.getAdapter(session, adapterType);
097    }
098
099    protected OperationRegistry connect(Connector connector) throws IOException {
100        Request req = new Request(Request.GET, url);
101        req.put("Accept", CTYPE_AUTOMATION);
102        // TODO handle authorization failure
103        return (OperationRegistry) connector.execute(req);
104    }
105
106    public synchronized void shutdown() {
107        adapterManager.clear();
108        url = null;
109        registry = null;
110    }
111
112    public Session getSession() throws IOException {
113        Connector connector = newConnector();
114        if (requestInterceptor != null) {
115            connector = new ConnectorHandler(connector, requestInterceptor);
116        }
117        if (registry == null) { // not yet connected
118            if (System.currentTimeMillis() - sharedRegistryUpdateTimestamp < sharedRegistryExpirationDelay) {
119                registry = sharedRegistry;
120            } else {
121                synchronized (SHARED_REGISTRY_SYNCHRONIZER) {
122                    // duplicate the test to avoid reentrance
123                    if (System.currentTimeMillis() - sharedRegistryUpdateTimestamp < sharedRegistryExpirationDelay) {
124                        registry = sharedRegistry;
125                    } else {
126                        // retrieve the registry
127                        registry = connect(connector);
128                        sharedRegistry = registry;
129                        sharedRegistryUpdateTimestamp = System.currentTimeMillis();
130                    }
131                }
132            }
133        }
134        return login(connector);
135    }
136
137    public Session getSession(final String username, final String password) throws IOException {
138        return getSession(new BasicAuthInterceptor(username, password));
139    }
140
141    public Session getSession(final String token) throws IOException {
142        return getSession(new TokenAuthInterceptor(token));
143    }
144
145    protected Session getSession(RequestInterceptor interceptor) throws IOException {
146        setRequestInterceptor(interceptor);
147        return getSession();
148    }
149
150    public Session getSession(TokenCallback cb) throws IOException {
151        String token = cb.getLocalToken();
152        if (token == null) {
153            token = cb.getRemoteToken(cb.getTokenParams());
154            cb.saveToken(token);
155        }
156        return getSession(token);
157    }
158
159    @Override
160    public Session getSession(LoginCallback loginCb) {
161        throw new UnsupportedOperationException();
162    }
163
164    protected Session login(Connector connector) throws IOException {
165        Request request = new Request(Request.POST, url + getRegistry().getPath("login"));
166        request.put("Accept", CTYPE_ENTITY);
167        LoginInfo login = (LoginInfo) connector.execute(request);
168        return createSession(connector, login);
169    }
170
171    protected Session createSession(final Connector connector, final LoginInfo login) {
172        return new DefaultSession(this, connector, login == null ? LoginInfo.ANONYNMOUS : login);
173    }
174
175    public void asyncExec(Runnable runnable) {
176        throw new UnsupportedOperationException("Async execution not supported");
177    }
178
179    protected abstract Connector newConnector();
180
181    /**
182     * @since 5.7
183     */
184    public void setSharedRegistryExpirationDelay(long delay) {
185        sharedRegistryExpirationDelay = delay;
186    }
187
188    @Override
189    public void registerPojoMarshaller(Class clazz) {
190        JsonMarshalling.addMarshaller(PojoMarshaller.forClass(clazz));
191    }
192}