001/**
002 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
003 *   This file is part of the LDP4j Project:
004 *     http://www.ldp4j.org/
005 *
006 *   Center for Open Middleware
007 *     http://www.centeropenmiddleware.com/
008 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
009 *   Copyright (C) 2014-2016 Center for Open Middleware.
010 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
011 *   Licensed under the Apache License, Version 2.0 (the "License");
012 *   you may not use this file except in compliance with the License.
013 *   You may obtain a copy of the License at
014 *
015 *             http://www.apache.org/licenses/LICENSE-2.0
016 *
017 *   Unless required by applicable law or agreed to in writing, software
018 *   distributed under the License is distributed on an "AS IS" BASIS,
019 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 *   See the License for the specific language governing permissions and
021 *   limitations under the License.
022 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
023 *   Artifact    : org.ldp4j.framework:ldp4j-application-api:0.2.2
024 *   Bundle      : ldp4j-application-api-0.2.2.jar
025 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
026 */
027package org.ldp4j.application;
028
029import java.lang.ref.ReferenceQueue;
030import java.util.Map;
031
032import org.ldp4j.application.session.WriteSession;
033import org.ldp4j.application.spi.RuntimeDelegate;
034import org.ldp4j.application.spi.ShutdownListener;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import com.google.common.base.MoreObjects;
039import com.google.common.collect.Maps;
040
041
042/**
043 * A proxy class that acts as a facade for interacting with the Application
044 * Engine proactively.
045 */
046public final class ApplicationContext {
047
048  private final class CleanerContextWriteSessionStateListener implements ContextWriteSessionStateListener {
049    @Override
050    public void onDispose(ContextWriteSessionState state) {
051      untrackSession(state);
052    }
053  }
054
055  private static final class ApplicationEngineSingleton {
056
057    private static final ApplicationContext SINGLETON=new ApplicationContext();
058
059    private ApplicationEngineSingleton() {
060    }
061
062  }
063
064  private static final Logger LOGGER=LoggerFactory.getLogger(ApplicationContext.class);
065
066  private final RuntimeDelegate delegate;
067  private final Map<Long,ContextWriteSessionReference> references;
068  private final Map<Long,Long> sessionOwner;
069  private final Map<Long,Long> threadSession;
070  private final ReferenceQueue<ContextWriteSession> referenceQueue;
071
072  private ApplicationContext() {
073    this.delegate=RuntimeDelegate.getInstance();
074    this.references=Maps.newLinkedHashMap();
075    this.sessionOwner=Maps.newLinkedHashMap();
076    this.threadSession=Maps.newLinkedHashMap();
077    this.referenceQueue=new ReferenceQueue<ContextWriteSession>();
078    LOGGER.info("Initialized Application Context");
079  }
080
081  private ApplicationContextException failure(String fmt, Object... args) {
082    String message=String.format(fmt,args);
083    LOGGER.error(message);
084    return new ApplicationContextException(message);
085  }
086
087  private void setUpWriteSessionCleaner() {
088    if(WriteSessionCleaner.isActive()) {
089      return;
090    }
091    WriteSessionCleaner.launch(this.referenceQueue);
092    this.delegate.registerShutdownListener(
093      new ShutdownListener(){
094        @Override
095        public void engineShutdown() {
096          WriteSessionCleaner.terminate();
097        }
098      }
099    );
100  }
101
102  private ContextWriteSession trackSession(ContextWriteSessionState state) {
103    ContextWriteSession leakedSession =
104      new ContextWriteSession(state);
105    ContextWriteSessionReference reference =
106      new ContextWriteSessionReference(
107        leakedSession,
108        state,
109        this.referenceQueue);
110    this.references.put(state.id(),reference);
111    this.sessionOwner.put(state.id(),Thread.currentThread().getId());
112    this.threadSession.put(Thread.currentThread().getId(),state.id());
113    return leakedSession;
114  }
115
116  private synchronized void untrackSession(ContextWriteSessionState session) {
117    long sessionId=session.id();
118    long ownerId=this.sessionOwner.get(sessionId);
119    this.references.remove(sessionId);
120    this.sessionOwner.remove(sessionId);
121    this.threadSession.remove(ownerId);
122  }
123
124  /**
125   * Create a {@code WriteSession}. Only one write session can be active per
126   * thread. Sessions should not be shared among threads.
127   *
128   * @return the write session
129   * @throws ApplicationContextException
130   *             if no write session can be created for whichever reason,
131   *             e.g., the Application Engine is off-line or is not available.
132   */
133  public synchronized WriteSession createSession() throws ApplicationContextException {
134    if(this.threadSession.containsKey(Thread.currentThread().getId())) {
135      throw failure("Thread already has an active session");
136    }
137
138    if(this.delegate.isOffline()) {
139      throw failure("The Application Engine is off-line");
140    }
141
142    WriteSession nativeSession=this.delegate.createSession();
143    if(nativeSession==null) {
144      throw failure("Could not create native write session");
145    }
146
147    setUpWriteSessionCleaner();
148
149    return
150      trackSession(
151        new ContextWriteSessionState(
152          nativeSession,
153          new CleanerContextWriteSessionStateListener()));
154  }
155
156  /**
157   * {@inheritDoc}
158   */
159  @Override
160  public synchronized String toString() {
161    return
162      MoreObjects.
163        toStringHelper(getClass()).
164          omitNullValues().
165          add("delegate",this.delegate).
166          add("references",this.references).
167          add("sessionOwner",this.sessionOwner).
168          add("threadSession",this.threadSession).
169          toString();
170  }
171
172  /**
173   * Get the Application context.
174   *
175   * @return the application context.
176   */
177  public static ApplicationContext getInstance() {
178    return ApplicationEngineSingleton.SINGLETON;
179  }
180
181}