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
.jcr
.CmsJcrUtils
;
31 import org
.argeo
.jcr
.JcrUtils
;
32 import org
.osgi
.framework
.BundleContext
;
33 import org
.osgi
.framework
.FrameworkUtil
;
34 import org
.osgi
.framework
.ServiceReference
;
36 public class LinkServlet
extends HttpServlet
{
37 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
39 private static final long serialVersionUID
= 3749990143146845708L;
42 protected void service(HttpServletRequest request
, HttpServletResponse response
)
43 throws ServletException
, IOException
{
44 String path
= request
.getPathInfo();
45 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
46 boolean isBot
= false;
47 // boolean isCompatibleBrowser = false;
48 if (userAgent
.contains("bot") || userAgent
.contains("facebook") || userAgent
.contains("twitter")) {
51 // else if (userAgent.contains("webkit") ||
52 // userAgent.contains("gecko") || userAgent.contains("firefox")
53 // || userAgent.contains("msie") || userAgent.contains("chrome") ||
54 // userAgent.contains("chromium")
55 // || userAgent.contains("opera") || userAgent.contains("browser"))
57 // isCompatibleBrowser = true;
61 // log.warn("# BOT " + request.getHeader("User-Agent"));
62 canonicalAnswer(request
, response
, path
);
66 // if (isCompatibleBrowser && log.isTraceEnabled())
67 // log.trace("# BWS " + request.getHeader("User-Agent"));
68 redirectTo(response
, "/#" + path
);
71 private void redirectTo(HttpServletResponse response
, String location
) {
72 response
.setHeader("Location", location
);
73 response
.setStatus(HttpServletResponse
.SC_FOUND
);
76 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
77 // String userAgent = request.getHeader("User-Agent").toLowerCase();
78 // return userAgent.startsWith("facebookexternalhit/");
81 /** For bots which don't understand RWT. */
82 private void canonicalAnswer(HttpServletRequest request
, HttpServletResponse response
, String path
) {
83 Session session
= null;
85 PrintWriter writer
= response
.getWriter();
86 session
= Subject
.doAs(anonymousLogin(), new PrivilegedExceptionAction
<Session
>() {
89 public Session
run() throws Exception
{
90 Collection
<ServiceReference
<Repository
>> srs
= bc
.getServiceReferences(Repository
.class,
91 "(" + CmsConstants
.CN
+ "=" + CmsConstants
.EGO_REPOSITORY
+ ")");
92 Repository repository
= bc
.getService(srs
.iterator().next());
93 return repository
.login();
97 Node node
= session
.getNode(path
);
98 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(JCR_TITLE
).getString() : node
.getName();
99 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
.getProperty(JCR_DESCRIPTION
).getString() : null;
100 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
) ? node
.getProperty(JCR_LAST_MODIFIED
).getDate()
102 String url
= getCanonicalUrl(node
, request
);
103 String imgUrl
= null;
104 // TODO support images
105 // loop: for (NodeIterator it = node.getNodes(); it.hasNext();) {
106 // // Takes the first found cms:image
107 // Node child = it.nextNode();
108 // if (child.isNodeType(CMS_IMAGE)) {
109 // imgUrl = getDataUrl(child, request);
113 StringBuilder buf
= new StringBuilder();
114 buf
.append("<html>");
115 buf
.append("<head>");
116 writeMeta(buf
, "og:title", escapeHTML(title
));
117 writeMeta(buf
, "og:type", "website");
118 buf
.append("<meta name='twitter:card' content='summary' />");
119 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
120 writeMeta(buf
, "og:url", url
);
122 writeMeta(buf
, "og:description", escapeHTML(desc
));
124 writeMeta(buf
, "og:image", imgUrl
);
125 if (lastUpdate
!= null)
126 writeMeta(buf
, "og:updated_time", Long
.toString(lastUpdate
.getTime().getTime()));
127 buf
.append("</head>");
128 buf
.append("<body>");
129 buf
.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
130 .append(path
).append("'>").append(escapeHTML(title
)).append("</a> instead.</b></p>");
131 writeCanonical(buf
, node
);
132 buf
.append("</body>");
133 buf
.append("</html>");
134 writer
.print(buf
.toString());
136 response
.setHeader("Content-Type", "text/html");
138 } catch (Exception e
) {
139 throw new IllegalStateException("Cannot write canonical answer", e
);
141 JcrUtils
.logoutQuietly(session
);
146 * From http://stackoverflow.com/questions/1265282/recommended-method-for-
147 * escaping-html-in-java (+ escaping '). TODO Use
148 * org.apache.commons.lang.StringEscapeUtils
150 private String
escapeHTML(String s
) {
151 StringBuilder out
= new StringBuilder(Math
.max(16, s
.length()));
152 for (int i
= 0; i
< s
.length(); i
++) {
153 char c
= s
.charAt(i
);
154 if (c
> 127 || c
== '\'' || c
== '"' || c
== '<' || c
== '>' || c
== '&') {
162 return out
.toString();
165 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
166 buf
.append("<meta property='").append(tag
).append("' content='").append(value
).append("'/>");
169 private void writeCanonical(StringBuilder buf
, Node node
) throws RepositoryException
{
171 if (node
.hasProperty(JCR_TITLE
))
172 buf
.append("<p>").append(node
.getProperty(JCR_TITLE
).getString()).append("</p>");
173 if (node
.hasProperty(JCR_DESCRIPTION
))
174 buf
.append("<p>").append(node
.getProperty(JCR_DESCRIPTION
).getString()).append("</p>");
175 NodeIterator children
= node
.getNodes();
176 while (children
.hasNext()) {
177 writeCanonical(buf
, children
.nextNode());
179 buf
.append("</div>");
183 private StringBuilder
getServerBaseUrl(HttpServletRequest request
) {
185 URL url
= new URL(request
.getRequestURL().toString());
186 StringBuilder buf
= new StringBuilder();
187 buf
.append(url
.getProtocol()).append("://").append(url
.getHost());
188 if (url
.getPort() != -1)
189 buf
.append(':').append(url
.getPort());
191 } catch (MalformedURLException e
) {
192 throw new IllegalArgumentException("Cannot extract server base URL from " + request
.getRequestURL(), e
);
196 private String
getDataUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
198 StringBuilder buf
= getServerBaseUrl(request
);
199 buf
.append(CmsJcrUtils
.getDataPath(CmsConstants
.EGO_REPOSITORY
, node
));
200 return new URL(buf
.toString()).toString();
201 } catch (MalformedURLException e
) {
202 throw new IllegalArgumentException("Cannot build data URL for " + node
, e
);
206 // public static String getDataPath(Node node) throws
207 // RepositoryException {
208 // assert node != null;
209 // String userId = node.getSession().getUserID();
210 //// if (log.isTraceEnabled())
211 //// log.trace(userId + " : " + node.getPath());
212 // StringBuilder buf = new StringBuilder();
213 // boolean isAnonymous =
214 // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
216 // buf.append(WEBDAV_PUBLIC);
218 // buf.append(WEBDAV_PRIVATE);
219 // Session session = node.getSession();
220 // Repository repository = session.getRepository();
222 // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
223 // cn = repository.getDescriptor(NodeConstants.CN);
225 //// log.warn("No cn defined in repository, using " +
226 // NodeConstants.NODE);
227 // cn = NodeConstants.NODE;
230 // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
234 private String
getCanonicalUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
236 StringBuilder buf
= getServerBaseUrl(request
);
237 buf
.append('/').append('!').append(node
.getPath());
238 return new URL(buf
.toString()).toString();
239 } catch (MalformedURLException e
) {
240 throw new IllegalArgumentException("Cannot build data URL for " + node
, e
);
242 // return request.getRequestURL().append('!').append(node.getPath())
246 private Subject
anonymousLogin() {
247 Subject subject
= new Subject();
250 lc
= new LoginContext(CmsAuth
.LOGIN_CONTEXT_ANONYMOUS
, subject
);
253 } catch (LoginException e
) {
254 throw new IllegalStateException("Cannot login as anonymous", e
);