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.1 024 * Bundle : ldp4j-application-api-0.2.1.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}