001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 * 
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.file.CmsUser;
035import org.opencms.lock.CmsLock;
036import org.opencms.lock.CmsLockActionRecord;
037import org.opencms.lock.CmsLockActionRecord.LockChange;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.security.CmsRole;
042import org.opencms.security.CmsRoleViolationException;
043import org.opencms.util.CmsUUID;
044
045import java.io.IOException;
046import java.util.HashMap;
047import java.util.List;
048import java.util.Map;
049
050import javax.servlet.ServletException;
051import javax.servlet.ServletRequest;
052import javax.servlet.ServletResponse;
053import javax.servlet.http.HttpServletRequest;
054import javax.servlet.http.HttpServletResponse;
055
056import org.apache.commons.logging.Log;
057
058import com.google.gwt.user.server.rpc.RemoteServiceServlet;
059import com.google.gwt.user.server.rpc.SerializationPolicy;
060
061/**
062 * Wrapper for GWT services served through OpenCms.<p>
063 * 
064 * @since 8.0.0
065 */
066public class CmsGwtService extends RemoteServiceServlet {
067
068    /** The static log object for this class. */
069    private static final Log LOG = CmsLog.getLog(CmsGwtService.class);
070
071    /** Serialization id. */
072    private static final long serialVersionUID = 8119684308154724518L;
073
074    /** The service class context. */
075    private CmsGwtServiceContext m_context;
076
077    /** The current CMS context. */
078    private ThreadLocal<CmsObject> m_perThreadCmsObject;
079
080    /**
081     * Constructor.<p>
082     */
083    public CmsGwtService() {
084
085        super();
086    }
087
088    /**
089     * Checks the permissions of the current user to match the required security level.<p> 
090     * 
091     * Note that the current request and response are not available yet.<p>
092     * 
093     * Override if needed.<p>
094     * 
095     * @param cms the current cms object 
096     * 
097     * @throws CmsRoleViolationException if the security level can not be satisfied
098     */
099    public void checkPermissions(CmsObject cms) throws CmsRoleViolationException {
100
101        OpenCms.getRoleManager().checkRole(cms, CmsRole.WORKPLACE_USER);
102    }
103
104    /**
105     * Logs and re-throws the given exception for RPC responses.<p>
106     * 
107     * @param t the exception
108     * 
109     * @throws CmsRpcException the converted exception 
110     */
111    public void error(Throwable t) throws CmsRpcException {
112
113        logError(t);
114        throw new CmsRpcException(t);
115    }
116
117    /**
118     * Returns the current cms context.<p>
119     *
120     * @return the current cms context
121     */
122    public CmsObject getCmsObject() {
123
124        return m_perThreadCmsObject.get();
125    }
126
127    /**
128     * Returns the current request.<p>
129     * 
130     * @return the current request
131     * 
132     * @see #getThreadLocalRequest()
133     */
134    public HttpServletRequest getRequest() {
135
136        return getThreadLocalRequest();
137    }
138
139    /**
140     * Returns the current response.<p>
141     * 
142     * @return the current response
143     * 
144     * @see #getThreadLocalResponse()
145     */
146    public HttpServletResponse getResponse() {
147
148        return getThreadLocalResponse();
149    }
150
151    /**
152     * @see javax.servlet.GenericServlet#log(java.lang.String)
153     */
154    @Override
155    public void log(String msg) {
156
157        if (getResponse() != null) {
158            super.log(msg);
159        }
160        // also log to opencms.log
161        LOG.info(msg);
162    }
163
164    /**
165     * @see javax.servlet.GenericServlet#log(java.lang.String, java.lang.Throwable)
166     */
167    @Override
168    public void log(String message, Throwable t) {
169
170        if (getResponse() != null) {
171            super.log(message, t);
172        }
173        // also log to opencms.log
174        LOG.info(message, t);
175    }
176
177    /**
178     * Logs the given exception.<p>
179     * 
180     * @param t the exception to log
181     */
182    public void logError(Throwable t) {
183
184        LOG.error(t.getLocalizedMessage(), t);
185    }
186
187    /**
188     * @see javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
189     */
190    @Override
191    public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
192
193        try {
194            arg1.setCharacterEncoding(arg0.getCharacterEncoding());
195            super.service(arg0, arg1);
196        } finally {
197            clearThreadStorage();
198        }
199    }
200
201    /**
202     * Sets the current cms context.<p>
203     *
204     * @param cms the current cms context to set
205     */
206    public synchronized void setCms(CmsObject cms) {
207
208        if (m_perThreadCmsObject == null) {
209            m_perThreadCmsObject = new ThreadLocal<CmsObject>();
210        }
211        m_perThreadCmsObject.set(cms);
212    }
213
214    /**
215     * Sets the service context.<p>
216     * 
217     * @param context the new service context 
218     */
219    public synchronized void setContext(CmsGwtServiceContext context) {
220
221        m_context = context;
222    }
223
224    /**
225     * Sets the current request.<p>
226     * 
227     * @param request the request to set
228     */
229    public synchronized void setRequest(HttpServletRequest request) {
230
231        if (perThreadRequest == null) {
232            perThreadRequest = new ThreadLocal<HttpServletRequest>();
233        }
234        perThreadRequest.set(request);
235    }
236
237    /**
238     * Sets the current response.<p>
239     * 
240     * @param response the response to set
241     */
242    public synchronized void setResponse(HttpServletResponse response) {
243
244        if (perThreadResponse == null) {
245            perThreadResponse = new ThreadLocal<HttpServletResponse>();
246        }
247        perThreadResponse.set(response);
248    }
249
250    /**
251     * We do not want that the server goes to fetch files from the servlet context.<p>
252     * 
253     * @see com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)
254     */
255    @Override
256    protected SerializationPolicy doGetSerializationPolicy(
257        HttpServletRequest request,
258        String moduleBaseURL,
259        String strongName) {
260
261        return m_context.getSerializationPolicy(getCmsObject(), moduleBaseURL, strongName);
262    }
263
264    /**
265     * @see com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet#doUnexpectedFailure(java.lang.Throwable)
266     */
267    @Override
268    protected void doUnexpectedFailure(Throwable e) {
269
270        LOG.error(String.valueOf(System.currentTimeMillis()), e);
271        super.doUnexpectedFailure(e);
272    }
273
274    /**
275     * Locks the given resource with a temporary, if not already locked by the current user.
276     * Will throw an exception if the resource could not be locked for the current user.<p>
277     * 
278     * @param resource the resource to lock
279     * 
280     * @return the assigned lock
281     * 
282     * @throws CmsException if the resource could not be locked
283     */
284    protected CmsLockActionRecord ensureLock(CmsResource resource) throws CmsException {
285
286        CmsObject cms = getCmsObject();
287        LockChange change = LockChange.unchanged;
288        List<CmsResource> blockingResources = cms.getBlockingLockedResources(resource);
289        if ((blockingResources != null) && !blockingResources.isEmpty()) {
290            throw new CmsException(Messages.get().container(
291                Messages.ERR_RESOURCE_HAS_BLOCKING_LOCKED_CHILDREN_1,
292                cms.getSitePath(resource)));
293        }
294        CmsUser user = cms.getRequestContext().getCurrentUser();
295        CmsLock lock = cms.getLock(resource);
296        if (!lock.isOwnedBy(user)) {
297            cms.lockResourceTemporary(resource);
298            change = LockChange.locked;
299            lock = cms.getLock(resource);
300        } else if (!lock.isOwnedInProjectBy(user, cms.getRequestContext().getCurrentProject())) {
301            cms.changeLock(resource);
302            change = LockChange.changed;
303            lock = cms.getLock(resource);
304        }
305        return new CmsLockActionRecord(lock, change);
306    }
307
308    /**
309     * 
310     * Locks the given resource with a temporary, if not already locked by the current user.
311     * Will throw an exception if the resource could not be locked for the current user.<p>
312     * 
313     * @param structureId the structure id of the resource 
314     * 
315     * @return the assigned lock
316     * 
317     * @throws CmsException if something goes wrong 
318     */
319    protected CmsLockActionRecord ensureLock(CmsUUID structureId) throws CmsException {
320
321        return ensureLock(getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION));
322
323    }
324
325    /**
326     * Locks the given resource with a temporary, if not already locked by the current user.
327     * Will throw an exception if the resource could not be locked for the current user.<p>
328     * 
329     * @param sitepath the site-path of the resource to lock
330     * 
331     * @return the assigned lock
332     * 
333     * @throws CmsException if the resource could not be locked
334     */
335    protected CmsLockActionRecord ensureLock(String sitepath) throws CmsException {
336
337        return ensureLock(getCmsObject().readResource(sitepath, CmsResourceFilter.IGNORE_EXPIRATION));
338    }
339
340    /**
341     * Ensures that the user session is still valid.<p>
342     * 
343     * @throws CmsException if the current user is the guest user
344     */
345    protected void ensureSession() throws CmsException {
346
347        CmsUser user = getCmsObject().getRequestContext().getCurrentUser();
348        if (user.isGuestUser()) {
349            throw new CmsException(Messages.get().container(Messages.ERR_SESSION_EXPIRED_0));
350        }
351    }
352
353    /**
354     * Converts a list of properties to a map.<p>
355     * 
356     * @param properties the list of properties 
357     * 
358     * @return a map from property names to properties 
359     */
360    protected Map<String, CmsProperty> getPropertiesByName(List<CmsProperty> properties) {
361
362        Map<String, CmsProperty> result = new HashMap<String, CmsProperty>();
363        for (CmsProperty property : properties) {
364            String key = property.getName();
365            result.put(key, property.clone());
366        }
367        return result;
368    }
369
370    /**
371     * Tries to unlock a resource.<p>
372     * 
373     * @param resource the resource to unlock
374     */
375    protected void tryUnlock(CmsResource resource) {
376
377        try {
378            getCmsObject().unlockResource(resource);
379        } catch (CmsException e) {
380            LOG.debug("Unable to unlock " + resource.getRootPath(), e);
381        }
382    }
383
384    /**
385     * Clears the objects stored in thread local.<p>
386     */
387    protected void clearThreadStorage() {
388
389        if (m_perThreadCmsObject != null) {
390            m_perThreadCmsObject.remove();
391        }
392        if (perThreadRequest != null) {
393            perThreadRequest.remove();
394        }
395        if (perThreadResponse != null) {
396            perThreadResponse.remove();
397        }
398    }
399}