1 package org
.argeo
.cms
.jcr
.internal
.servlet
;
3 import static javax
.jcr
.Property
.JCR_DESCRIPTION
;
4 import static javax
.jcr
.Property
.JCR_LAST_MODIFIED
;
5 import static javax
.jcr
.Property
.JCR_TITLE
;
7 import java
.io
.IOException
;
8 import java
.io
.PrintWriter
;
9 import java
.net
.MalformedURLException
;
11 import java
.security
.PrivilegedExceptionAction
;
12 import java
.util
.Calendar
;
13 import java
.util
.Collection
;
15 import javax
.jcr
.Node
;
16 import javax
.jcr
.NodeIterator
;
17 import javax
.jcr
.Repository
;
18 import javax
.jcr
.RepositoryException
;
19 import javax
.jcr
.Session
;
20 import javax
.security
.auth
.Subject
;
21 import javax
.security
.auth
.login
.LoginContext
;
22 import javax
.security
.auth
.login
.LoginException
;
23 import javax
.servlet
.ServletException
;
24 import javax
.servlet
.http
.HttpServlet
;
25 import javax
.servlet
.http
.HttpServletRequest
;
26 import javax
.servlet
.http
.HttpServletResponse
;
28 import org
.argeo
.api
.cms
.CmsAuth
;
29 import org
.argeo
.api
.cms
.CmsConstants
;
30 import org
.argeo
.cms
.CmsException
;
31 import org
.argeo
.cms
.jcr
.CmsJcrUtils
;
32 import org
.argeo
.jcr
.JcrUtils
;
33 import org
.osgi
.framework
.BundleContext
;
34 import org
.osgi
.framework
.FrameworkUtil
;
35 import org
.osgi
.framework
.ServiceReference
;
37 public class LinkServlet
extends HttpServlet
{
38 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
40 private static final long serialVersionUID
= 3749990143146845708L;
43 protected void service(HttpServletRequest request
, HttpServletResponse response
)
44 throws ServletException
, IOException
{
45 String path
= request
.getPathInfo();
46 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
47 boolean isBot
= false;
48 // boolean isCompatibleBrowser = false;
49 if (userAgent
.contains("bot") || userAgent
.contains("facebook") || userAgent
.contains("twitter")) {
52 // else if (userAgent.contains("webkit") ||
53 // userAgent.contains("gecko") || userAgent.contains("firefox")
54 // || userAgent.contains("msie") || userAgent.contains("chrome") ||
55 // userAgent.contains("chromium")
56 // || userAgent.contains("opera") || userAgent.contains("browser"))
58 // isCompatibleBrowser = true;
62 // log.warn("# BOT " + request.getHeader("User-Agent"));
63 canonicalAnswer(request
, response
, path
);
67 // if (isCompatibleBrowser && log.isTraceEnabled())
68 // log.trace("# BWS " + request.getHeader("User-Agent"));
69 redirectTo(response
, "/#" + path
);
72 private void redirectTo(HttpServletResponse response
, String location
) {
73 response
.setHeader("Location", location
);
74 response
.setStatus(HttpServletResponse
.SC_FOUND
);
77 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
78 // String userAgent = request.getHeader("User-Agent").toLowerCase();
79 // return userAgent.startsWith("facebookexternalhit/");
82 /** For bots which don't understand RWT. */
83 private void canonicalAnswer(HttpServletRequest request
, HttpServletResponse response
, String path
) {
84 Session session
= null;
86 PrintWriter writer
= response
.getWriter();
87 session
= Subject
.doAs(anonymousLogin(), new PrivilegedExceptionAction
<Session
>() {
90 public Session
run() throws Exception
{
91 Collection
<ServiceReference
<Repository
>> srs
= bc
.getServiceReferences(Repository
.class,
92 "(" + CmsConstants
.CN
+ "=" + CmsConstants
.EGO_REPOSITORY
+ ")");
93 Repository repository
= bc
.getService(srs
.iterator().next());
94 return repository
.login();
98 Node node
= session
.getNode(path
);
99 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(JCR_TITLE
).getString() : node
.getName();
100 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
.getProperty(JCR_DESCRIPTION
).getString() : null;
101 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
) ? node
.getProperty(JCR_LAST_MODIFIED
).getDate()
103 String url
= getCanonicalUrl(node
, request
);
104 String imgUrl
= null;
105 // TODO support images
106 // loop: for (NodeIterator it = node.getNodes(); it.hasNext();) {
107 // // Takes the first found cms:image
108 // Node child = it.nextNode();
109 // if (child.isNodeType(CMS_IMAGE)) {
110 // imgUrl = getDataUrl(child, request);
114 StringBuilder buf
= new StringBuilder();
115 buf
.append("<html>");
116 buf
.append("<head>");
117 writeMeta(buf
, "og:title", escapeHTML(title
));
118 writeMeta(buf
, "og:type", "website");
119 buf
.append("<meta name='twitter:card' content='summary' />");
120 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
121 writeMeta(buf
, "og:url", url
);
123 writeMeta(buf
, "og:description", escapeHTML(desc
));
125 writeMeta(buf
, "og:image", imgUrl
);
126 if (lastUpdate
!= null)
127 writeMeta(buf
, "og:updated_time", Long
.toString(lastUpdate
.getTime().getTime()));
128 buf
.append("</head>");
129 buf
.append("<body>");
130 buf
.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
131 .append(path
).append("'>").append(escapeHTML(title
)).append("</a> instead.</b></p>");
132 writeCanonical(buf
, node
);
133 buf
.append("</body>");
134 buf
.append("</html>");
135 writer
.print(buf
.toString());
137 response
.setHeader("Content-Type", "text/html");
139 } catch (Exception e
) {
140 throw new CmsException("Cannot write canonical answer", e
);
142 JcrUtils
.logoutQuietly(session
);
147 * From http://stackoverflow.com/questions/1265282/recommended-method-for-
148 * escaping-html-in-java (+ escaping '). TODO Use
149 * org.apache.commons.lang.StringEscapeUtils
151 private String
escapeHTML(String s
) {
152 StringBuilder out
= new StringBuilder(Math
.max(16, s
.length()));
153 for (int i
= 0; i
< s
.length(); i
++) {
154 char c
= s
.charAt(i
);
155 if (c
> 127 || c
== '\'' || c
== '"' || c
== '<' || c
== '>' || c
== '&') {
163 return out
.toString();
166 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
167 buf
.append("<meta property='").append(tag
).append("' content='").append(value
).append("'/>");
170 private void writeCanonical(StringBuilder buf
, Node node
) throws RepositoryException
{
172 if (node
.hasProperty(JCR_TITLE
))
173 buf
.append("<p>").append(node
.getProperty(JCR_TITLE
).getString()).append("</p>");
174 if (node
.hasProperty(JCR_DESCRIPTION
))
175 buf
.append("<p>").append(node
.getProperty(JCR_DESCRIPTION
).getString()).append("</p>");
176 NodeIterator children
= node
.getNodes();
177 while (children
.hasNext()) {
178 writeCanonical(buf
, children
.nextNode());
180 buf
.append("</div>");
184 private StringBuilder
getServerBaseUrl(HttpServletRequest request
) {
186 URL url
= new URL(request
.getRequestURL().toString());
187 StringBuilder buf
= new StringBuilder();
188 buf
.append(url
.getProtocol()).append("://").append(url
.getHost());
189 if (url
.getPort() != -1)
190 buf
.append(':').append(url
.getPort());
192 } catch (MalformedURLException e
) {
193 throw new CmsException("Cannot extract server base URL from " + request
.getRequestURL(), e
);
197 private String
getDataUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
199 StringBuilder buf
= getServerBaseUrl(request
);
200 buf
.append(CmsJcrUtils
.getDataPath(CmsConstants
.EGO_REPOSITORY
, node
));
201 return new URL(buf
.toString()).toString();
202 } catch (MalformedURLException e
) {
203 throw new CmsException("Cannot build data URL for " + node
, e
);
207 // public static String getDataPath(Node node) throws
208 // RepositoryException {
209 // assert node != null;
210 // String userId = node.getSession().getUserID();
211 //// if (log.isTraceEnabled())
212 //// log.trace(userId + " : " + node.getPath());
213 // StringBuilder buf = new StringBuilder();
214 // boolean isAnonymous =
215 // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
217 // buf.append(WEBDAV_PUBLIC);
219 // buf.append(WEBDAV_PRIVATE);
220 // Session session = node.getSession();
221 // Repository repository = session.getRepository();
223 // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
224 // cn = repository.getDescriptor(NodeConstants.CN);
226 //// log.warn("No cn defined in repository, using " +
227 // NodeConstants.NODE);
228 // cn = NodeConstants.NODE;
231 // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
235 private String
getCanonicalUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
237 StringBuilder buf
= getServerBaseUrl(request
);
238 buf
.append('/').append('!').append(node
.getPath());
239 return new URL(buf
.toString()).toString();
240 } catch (MalformedURLException e
) {
241 throw new CmsException("Cannot build data URL for " + node
, e
);
243 // return request.getRequestURL().append('!').append(node.getPath())
247 private Subject
anonymousLogin() {
248 Subject subject
= new Subject();
251 lc
= new LoginContext(CmsAuth
.LOGIN_CONTEXT_ANONYMOUS
, subject
);
254 } catch (LoginException e
) {
255 throw new CmsException("Cannot login as anonymous", e
);