001/*
002 *  Copyright 2010 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.plugins.repository.provider;
018
019import java.io.File;
020import java.util.HashMap;
021import java.util.Map;
022
023import javax.jcr.LoginException;
024import javax.jcr.NoSuchWorkspaceException;
025import javax.jcr.RepositoryException;
026import javax.jcr.Session;
027import javax.jcr.SimpleCredentials;
028
029import org.apache.avalon.framework.CascadingRuntimeException;
030import org.apache.avalon.framework.activity.Disposable;
031import org.apache.avalon.framework.activity.Initializable;
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.context.ContextException;
034import org.apache.avalon.framework.context.Contextualizable;
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.cocoon.Constants;
038import org.apache.cocoon.components.ContextHelper;
039import org.apache.cocoon.environment.Context;
040import org.apache.cocoon.environment.Request;
041import org.apache.jackrabbit.core.cache.CacheManager;
042import org.apache.jackrabbit.core.config.ConfigurationException;
043import org.apache.jackrabbit.core.config.RepositoryConfig;
044
045import org.ametys.core.datasource.ConnectionHelper;
046import org.ametys.plugins.repository.RepositoryConstants;
047import org.ametys.plugins.repository.provider.RepositoryStatus.Status;
048import org.ametys.runtime.config.Config;
049import org.ametys.runtime.util.AmetysHomeHelper;
050
051/**
052 * JackrabbitRepository is a JCR repository component based on Jackrabbit
053 */
054public class JackrabbitRepository extends AbstractRepository implements LogoutManager, Initializable, Contextualizable, Disposable, Component
055{
056    private org.apache.avalon.framework.context.Context _avalonContext;
057    private Context _context;
058
059    @Override
060    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
061    {
062        _avalonContext = context;
063        _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
064    }
065    
066    @Override
067    public void service(ServiceManager smanager) throws ServiceException
068    {
069        super.service(smanager);
070        // Make sure the ConnectionHelper is initialized first, so the SQLDataSourceManager can be used in AmetysPersistenceManager
071        smanager.lookup(ConnectionHelper.ROLE);
072    }
073    
074    
075    public void initialize() throws Exception
076    {
077        RepositoryConfig repositoryConfig = createRepositoryConfig();
078        AmetysRepository repo = new AmetysRepository(repositoryConfig);
079        
080        _delegate = repo;
081        ((AmetysRepository) _delegate).setLogoutManager(this);
082        
083        Long cacheSize = Config.getInstance().getValue("org.ametys.plugins.repository.cache");  
084        if (cacheSize == null || cacheSize <= 0)
085        {
086            cacheSize = 16777216L; // default value;
087        }
088        
089        CacheManager cacheManager = repo.getCacheManager();
090        cacheManager.setMaxMemory(cacheSize);
091        cacheManager.setMaxMemoryPerCache(cacheSize / 4);
092        cacheManager.setMinMemoryPerCache(cacheSize / 128);
093        
094        // Register the delegate repository in the context for the repository workspace.
095        _context.setAttribute(CONTEXT_REPOSITORY_KEY, _delegate);
096        _context.setAttribute(CONTEXT_CREDENTIALS_KEY, new SimpleCredentials("ametys", new char[]{}));
097        _context.setAttribute(CONTEXT_IS_JNDI_KEY, false);
098        
099        if (getLogger().isDebugEnabled())
100        {
101            getLogger().debug("JCR Repository running");
102        }
103    }
104
105    public void dispose()
106    {
107        AmetysRepository repo = (AmetysRepository) _delegate;
108        repo.setLogoutManager(null);
109        repo.shutdown();
110    }    
111    
112    /**
113     * Create the repository configuration from the configuration file
114     * @return The repository configuration
115     * @throws ConfigurationException if an error occurred
116     */
117    RepositoryConfig createRepositoryConfig() throws ConfigurationException
118    {
119        String config = _context.getRealPath("/WEB-INF/param/repository.xml");
120        
121        File homeFile = new File(AmetysHomeHelper.getAmetysHomeData(), "repository");
122        RepositoryStatus.setStatus(homeFile.exists() ? Status.EXISTING : Status.NEW);
123        
124        if (getLogger().isDebugEnabled())
125        {
126            getLogger().debug("Creating JCR Repository config at: " + homeFile.getAbsolutePath());
127        }
128        
129        return RepositoryConfig.create(config, homeFile.getAbsolutePath());
130    }
131
132    int getSessionCount()
133    {
134        return ((AmetysRepository) _delegate).getSessionCount();
135    }
136    
137    public Session login(String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException
138    {
139        try
140        {
141            if (getLogger().isDebugEnabled())
142            {
143                getLogger().debug("Getting JCR Session");
144            }
145            
146            try
147            {
148                Request request = ContextHelper.getRequest(_avalonContext);
149                
150                @SuppressWarnings("unchecked")
151                Map<String, Session> sessions = (Map<String, Session>) request.getAttribute(RepositoryConstants.JCR_SESSION_REQUEST_ATTRIBUTE);
152                
153                if (sessions == null)
154                {
155                    sessions = new HashMap<>();
156                    request.setAttribute(RepositoryConstants.JCR_SESSION_REQUEST_ATTRIBUTE, sessions);
157                }
158                
159                Session session = sessions.get(workspace);
160                
161                if (session == null || !session.isLive())
162                {
163                    session = _delegate.login(new SimpleCredentials("ametys", new char[]{}), workspace);
164                    sessions.put(workspace, session);
165                }
166                
167                return session;
168            }
169            catch (CascadingRuntimeException e)
170            {
171                if (e.getCause() instanceof ContextException)
172                {
173                    // Unable to get request. Must be in another thread or at init time.
174                    Session session = _delegate.login(new SimpleCredentials("ametys", new char[]{}), workspace);
175                    return session;
176                }
177                else
178                {
179                    throw e;
180                }
181            }
182        }
183        catch (RepositoryException e)
184        {
185            throw e;
186        }
187        catch (Exception e)
188        {
189            throw new RuntimeException("Unable to get Session", e);
190        }
191    }
192    
193    public void logout(Session session)
194    {
195        if (!(session instanceof AmetysSession))
196        {
197            throw new IllegalArgumentException("JCR Session should be an instance of AmetysSession");
198        }
199        
200        AmetysSession ametysSession = (AmetysSession) session;
201        
202        if (getLogger().isDebugEnabled())
203        {
204            getLogger().debug("Logging out AmetysSession");
205        }
206        
207        try
208        {
209            // the following statement will fail if we are not processing a request.
210            ContextHelper.getRequest(_avalonContext);
211            
212            // does nothing as the session will be actually logged out at the end of the request.
213            if (getLogger().isDebugEnabled())
214            {
215                getLogger().debug("AmetysSession logout delayed until the end of the HTTP request.");
216            }
217            
218        }
219        catch (Exception e)
220        {
221            // unable to get request. Must be in another thread or at init time.
222            if (getLogger().isDebugEnabled())
223            {
224                getLogger().debug("Not in a request. AmetysSession will be actually logged out.");
225            }
226            
227            ametysSession.forceLogout();
228        }
229    }
230}