[
http://jira.nuxeo.org/browse/NXP-1720?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#action_29650
]
Radu Darlea commented on NXP-1720:
----------------------------------
Excerpts from previous emails regarding ExternalEditWS Web Service:
"Here is the first draft spec for the new LiveEdit architecture.
Overview :
==========
The purpous is to have generic desktop integration architecture that allow
Nuxeo5 users to edit files directly with the native local editor :
- the user clicks on "edit on line" link
- the associated editor is started on the client side
- the editor must comunicate with Nuxeo server to :
- download the file to edit
- acquire lock if needed
- save the file back to nuxeo server
- create new version if needed
- release lock if needed
- ...
This somehow what we already have with current version of LiveEdit. The
purpouse is :
- to define a more generic architecture : be able to have editor plugins to
support other editors that MSOffice2003
- to clearly define the WS interface used by editors
Global Architecture :
=====================
[...]
Server side :
-------------
On the server side we will have a dedicated WebService. The WebService will
mainly handle 2 types of methods :
- download/upload file
- fetch available actions / do actions
Actions availables on the document may depend on the client project
specifications, and it's important that it is totally transparent for the
client plugin UI : we don't want to build a version of the clients plugins for
each project.
So the idea, as in existing LiveEdit, is that the WebService will provide the
client a simple list of actions, the client will simply display availables
actions, and eventually ask the server to execute them without knowing the
underlying logic. This "action logic" is somehow close to what we already do in
the web layer, an action is :
- an action id
- a label : generated on the server side
If several actions can be done (like checkout + lock), then it will be
presented as 3 actions : chekout, lock, checkout_and_lock.
This way we won't have to handle associations conditions on the client side :
the user can always select one and only one action : none / lock / checkout /
chekout_and_lock
[...]
Authentication management :
---------------------------
As the Bootstrap client needs to do a http call, it needs to be able to
authenticate.
There are 2 solutions :
- reuse JSESSIONID
- use basic authentication
=> means the bootstrap client has a configuration file or registry keys
to store login/password entered by the user.
[...]
The Editor :
============
The editor process will :
- receive and read the bootstrap file
- start the client editor (MSOffice via COM interop interface, OOffice via ???
...)
- call WS to get list of pre-edit actions
- display a dialog for letting user select action
- call WS to download the file
- call WS to get list of post-edit actions
- display a dialog for letting user select action
- save and upload the file to Nuxeo Server
- terminate
For MSOffice, this Editor process could be seperated in 2 parts :
- the bootstrap part : 2 first steps
- the pure editor part : all other steps
=> This could be implemented as an office plugin
The WebService :
================
This Web Service will be exposed by a dedicated EJB3. It will be pretty much
like the existing FileManagerWS : but cleaner
code with better wsdl. I propose to call it ExternalEditWS (feel free to
propose a sexier name :) ).
API :
-----
For now here is what I see in the API :
- Map<String,String> getPreEditActions(String sid, String repo, String docRef,
String fieldName) : send possible action for pre-edit
- Map<String,String> getPostEditActions(String sid, String repo, String
docRef, Stringe fieldName) : send possible action for post-edit
- Boolean doAction(String sid, String repo, String docRef, String
fieldName,String actionId) : execute action, returns true if client must
re-read the file from server
- getFile(String sid, String repo, String docRef, String fieldName)
- saveFile(String sid, String repo, String docRef, String fieldName, Blob blob)
- String getProperty(String sid, String repo, String docRef, String fieldName)
- String setProperty(String sid, String repo, String docRef, String fieldName,
String value)
Authentication :
----------------
The call to WS must be authenticated.
2 ways for doing that :
- reuse the JSESSIONID : get UserPrincipal and Login context from the
incomming request
=> call with sid=null
- explicitly create a session and forward it in each call
=> call with non null sid"
"Anyway, a complete WS should include some other methods: getting/setting
properties of the document, session management. And I think download / upload
are in fact actions. So, WS should:
- user session management
- supply the list of actions available for a particular stage
- do that actions
The user session management should allow:
- fresh new session creation through the user authentication (user name /
password, cert, etc.)
- propagate an already created session. If the user authenticated already in
the browser for instance, there should be no need for a new authentication, and
the browser could not know the credentials. So we need to provide both ways of
ensuring the user access.
About this, a little later.
Looking at the current implementation, I see 2 different approaches:
1. Based on Seam (UI authentication) - navigationContext == FileManageWS
2. Independent self content pure WS == NuxeoRemotingBean +
AbstractNuxeoWebService
I would certainly prefer the second approach because I am not sure that the
navigationContext is available outside the UI. My intention is to prepare a
self content WS API which should be available to any client. Making the
assumption the client would be a browser limits the usability of WS. This
assumption could be a valid prerequisite though, and if it is accepted it could
simplify the overall implementation. But in that case I don't see why use WS
when other options are available (for instance recently developed NuxeoClient).
The specific difference between the 2 approaches is the mechanism of obtaining
the user context. I mean: authentication and managing the session.
This problem is solved by the navigationContext/Seam, in the first case. The
second approach is using a pair of methods connect/disconnect and a sessionID
string required for all the exported methods.
These mechanism are legitimated mechanisms. The third would be WS Addressing.
Well, maybe it is time to describe a little what WS Addressing does...
WS-Addressing was originally authored by Microsoft, IBM, BEA, Sun, and SAP and
submitted to W3C for standardization. The W3C WS-Addressing Working Group has
refined and augmented the specification in the process of standardization.
WS-Addressing is currently specified in three parts:
* The Core specification of Endpoint References and Message Addressing
Properties.
* A binding of these properties to SOAP.
* The Metadata specification defines how the abstract properties defined in
Core are described using WSDL, how to include WSDL metadata in endpoint
references, and how WS-Policy can be used to indicate the support of
WS-Addressing by a Web service.
The last updated occurred in 2004 December and it is now a mature standard.
Specification abstract: "WS-Addressing provides transport-neutral mechanisms to
address Web services and messages. Specifically, this specification defines XML
elements to identify Web service endpoints and to secure end-to-end endpoint
identification in messages. This specification enables messaging systems to
support message transmission through networks that include processing nodes
such as endpoint managers, firewalls, and gateways in a transport-neutral
manner."
What does this mean?
WS-Addressing initiates a major transformation of the web services landscape
with the advent of the "end-point". WSDL let you defines the interface and the
classes that implement a particular interface whole an end-point maps to an
instance that conform a particular class. As such, end points can be and are
generally dynamically created. An end-point is uniquely identified by its
"address" and properties. Note that the end-point address may and is generally
different from the service port address. In addition to specifying an end-point
definition, WS-Addressing specifies also the way end-points are used and mapped
within SOAP 1.2.
An Endpoint Reference (EPR) is an XML structure encapsulating information
useful for addressing a message to a Web service. This includes the destination
address of the message, any additional parameters (called reference parameters)
necessary to route the message to the destination, and optional metadata (such
as WSDL or WS-Policy) about the service. Endpoint references allow applications
to augment a traditional URI-based address with application-specific
information that can be used to better dispatch the message.
Normally, the WS Server side only creates one instance of your service class,
and have it handle all incoming requests concurrently. This effectively takes
you back to pre-OO programming, as you will not be able to make use of instance
fields at all — it's as if all your methods are static. The problem with that
is that if the rest of your application is modeled in OO, this mismatch becomes
painful. It would be much nicer if you can make it OO all the way, like you do
with RMI, where you can export "objects". The stateful web service support
brings you that. On the wire, it uses WS-Addressing standard, so it's fully
interoperable.
On a simple phrase, WS Addressing takes out the localization of the required
resource within the container from the HTTP header and places it inside the
SOAP envelope. And that allows dynamic addressing. And that enables sessions.
For all which want to see these in the context from where I took them (of
course I didn't write these out of my head):
http://en.wikipedia.org/wiki/WS-Addressing
ftp://www6.software.ibm.com/software/developer/library/ws-rm-exec-summary.pdf
http://hyperthink.net/blog/PermaLink,guid,8519a1bf-e1b7-4b9b-b10c-fce8d359b83e.aspx
http://www.ebpml.org/ws-addressing.htm
and for usage. a very clear example
http://weblogs.java.net/blog/kohsuke/archive/2006/10/stateful_web_se.html
As Kohsuke Kawaguchi shows in his example, the session issue became a simple
standard implementation.
And not forget that WS Addressing is providing a solution transport-agnostic:
it can be http, SMTP, TCP anything.
[...]
Both have to be available, leading to the same result. And here is the problem.
Let's take 2 scenarios:
A. The client is using WS from a stand-alone (i.e. independent of browser)
application. She takes the entry point from WSDL, connect to Server which
creates a "session" in behalf. Further, the client is working on that Stateful
Bean (session).
B. The client is already authenticated in browser and wants to start using WS.
She already has a Web Session and associated a App Session, a Core Session.
Now, going to the entry point she has somehow supply and obtain the App session
transparently, without a new authentication. How we can do that? I am not sure
even if is possible."
"> In this moment it is implemented only the "Lock" action. What do you mean by
the others?
- Lock/Unlock
- Checkin/Checkout
- save, save as new minor version , save as new major version (Version Policy)
- validate / reject (WF actions)
- ..."
"are the "actions" are also available in WSDL?
or there will be a generic call executeAction(actionID)?
If the first case, I can see little advantage of that dynamic list, anyway the
actions are visible from the start in WSDL and they can or not be used by the
client. If a Client application doesn't care about locking, it simply doesn't
call lock(), nor makes it visible.
If the second case, there is a little overhead on the server (checking the
action ID and dispatching it) and it would be a little harder / riskier to link
some parameters. "
"No parameter for now. I understand your question about parameters, but this is
too complexe for what we need. Entering parameters in a generic client (ie :
LiveEdit) means we are able to send to the client a layout definition (like
list of parameters, with types, flag for compulsary parameters ...).
So for a start, no parameters.
[...]
If the user has a valid Nuxeo5 session, then all http calls will go throught
the NuxeoAuthenticationFilter. This filter will reestablish the JAAS
LoginContext and give you access to the user Proncipal via
request.getUserPrincipal.
This means :
- the EJB calls will be authenticated
- if needed you can get the Principal, either via the http request object "
"> As you can see, I am trying to model the Nuxeo WS implementation with
> Stateful Web Service as sessions. SWS isn't compatible with EJB SFSB. AFAIK:
> - SWS is managed by the WS stack
> - only one callback is available: timeout()
> I intend to model a client - web service dataflow as:
> C1. Client gets the port of the stateless web service NuxeoWSMainEntrancePoint
> C2. Client asks the EPR for the wanted service
> S1. Server authenticates the Client and constructs a BasicSession containing
> a CoreSession, a jaas LoginContext and a Repository
> S2. Server constructs the SWS and feeds the BasicSession to SWS
> S3. Server returns the EPR
> C3. Client constructs the web service port based on the EPR
> C4. Client uses a feature of the web service
> S4. Server accomplishes the task within the SWS
> .......................................
> Server destroys the SWS on timeout() or Client request.
>
> Is enough BasicSession in order to have a fully functional Nuxeo Session (I
> mean to be able to access the documents stored in repository)?
>
> Is it ok to obtain these objects and store them in SWS during the life
> of the SWS? The life of SWS starts with the access request from client
> and ends on timeout or at the client's disconnect.
>
Yes.
> I have JAAS authentication exception if I don't perform a dummy
> Framework.login() every time a web service feature is called. Why that? Is it
> ok to perform this dummy login in every method?
> For an example on how it is made, take a look at the
> LEWS.getRootDocument() and LEWS.getChildren() in the sandbox. BTW, this idea
> came to me looking at NuxeoRemotingBean: there it is exactly this
> implementation.
>
> I really don't like this dummy login... Is it really needed? Isn't damaging?
> Can be avoided?
> (Framework.login() ends up in LoginComponent.loginAs(null), not that it helps
> me much understand why)
>
It is not really a dummy login :) It is a system login. Framework.login() or
loginAs("asSomeone") should
be used from nuxeo code when you need to perform some background operation that
may require or not admin. privileges.
For example let say you are a regular user and some operation you are doing
using the client (under your real login) will trigger
an async. event that will later need to access to the repository as a "system
user" to do something. (for example to fetch some data to be indexed)
In that case the user identity doesn't matter - we only need to do a login to
avoid EJBAccessExceptions.
On your side it will be better to use a real login - this mean to use
login(username, password) or the NuxeoAuthenticationFilter that thierry is
talking about.
Look into
nuxeo-platform-ui-web/src/main/resources/OSGI-INF/deployment-fragment.xml for
examples on how to bind this filter on an URL pattern.
Anyway that filter is using JBoss specific code and won't work on glassfish or
other app servers. The filter implementation should be reviewed to try to use
generic code (for example use the jboss ClientLoginModule to do the
SecurityAssociation bindings - and avoid doin that explicitely)
or at least to better isolate JBoss specific code. But this is another
discussion.
Let's go back to our problem. So, why you have an exception at each request if
you don't re-login?
The answer is simple: because the Framework.login() is creating an
authentication context and binds it on the current thread.
When you make another client request the request will usually be served in
another thread so you loose your previously
login context. This also means you must logout before ending the request. If
you don' t do this and the thread is reused by another process
it will run inside your own login context. So you need to logout at request
end. And you need to login at request start.
This is because the jboss ClientLoginModule which is used by Framework.login()
is by default multi-threaded - it keeps the login context in a ThreadLocal
variable (so this login context is existing only on the thread you performed
the login).
In application like Apogee where you are always logged under the same identity
until you completely disconnect from the server - the jboss ClienLoginModule is
configured with multi threading off to be able to use the same login context on
all threads (the login context will be stored in a static variable). But this
is not possible to do on the server because here you may want to have plenty of
concurrent threads each one with its own login context.
So what is the solution? One solution is to use the filter used at web client
level another solution is to try to do the same stuff as the auth. filter do:
to create a login context the first time an user log in and then to restore
this login context at each subsequent request from the same user.
Restoring the login context means to initialize some JBoss internal state using
SecurityAssociation jboss class. I think this context restore can also be done
using a special login module (that we need to write) in conjunction with jboss
ClientLoginModule to avoid to use jboss specific code in our app without
re-authenticating each time. (this is also true for the web client auth.
filter).
So there are two methods to solve this: whether to use the filter whether to
reimplement the logic existing in that filter but in an app server independent
manner
Anyway, sooner or later (glassfish integration?) we will need to reimplement
the code in that filter and make it more generic so that you can use it outside
jboss and why not outside http requests. And also we need to make that code
generic - to avoid using JBoss internal API which I think is possible. Or at
least to isolate jboss specific code using extension points or something
similar.
It will also work if you do an explicit Framework.login(user,pass) at each
request and a logout at the end.
The downside is that the authentication will be performed each time. "
"his is listing the API for the ExternalEditWS. I think it is important not
only for the Server side, but rather it is important for the users. The final
approve that the API is complete and correct can be obtained only from the
Client side application developers. They can say if there is need for more
functions or not.
public interface ExternalEditWSable {
/**
* List the actions available before starting editing.
* @param repo
* @param docRef
* @param fieldName
* @return actions, all are exclusive, and the user will have to chose
* one of the options.
*/
public abstract HashMap<String, String> getPreEditActions(
String repo, String docRef, String fieldName);
/**
* List the actions available after finishing editing.
* @param repo
* @param docRef have to be not null
* @param fieldName
* @return actions, all are exclusive, and the user will have to chose
* one of the options.
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract HashMap<String, String> getPostEditActions(
String repo, String docRef, String fieldName)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Executes the action.
* @param repo
* @param docRef have to be not null
* @param fieldName
* @param actionId have to be not null
* @return if the document has to be reloaded
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract boolean doAction(
String repo, String docRef, String fieldName, String actionId)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Gets a blob property content. The property is named by the fieldName.
* @param repo
* @param docRef has to be not null
* @param fieldName has to be not null
* @return
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract byte[] getFile(String repo, String docRef, String fieldName)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Sets a blob property content. The property is named by the fieldName.
* @param repo
* @param docRef has to be not null
* @param fieldName has to be not null
* @return if the document has to be reloaded
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract boolean saveFile(
String repo, String docRef, String fieldName, byte[] content)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Gets a simple property content. The property is named by the fieldName.
* @param repo
* @param docRef has to be not null
* @param fieldName has to be not null
* @return
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract String getProperty(String repo, String docRef, String
fieldName)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Gets all simple properties content in one shot.
* @param repo
* @param docRef has to be not null
* @return
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract DocumentProperty[] getSimpleProperties(String repo, String
docRef)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Gets blob properties name in one shot.
* @param repo
* @param docRef has to be not null
* @return
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract String[] getBlobPropertyNames(String repo, String docRef)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Sets a simple property content. The property is named by the fieldName.
* @param repo
* @param docRef has to be not null
* @param fieldName has to be not null
* @return if the document has to be reloaded
* @throws ClientAuthenticationException
* @throws ItemNotFoundException
* @throws ServerProcessingException
*/
public abstract boolean setProperty(String repo, String docRef,
String fieldName, String value)
throws ClientAuthenticationException,
ItemNotFoundException, ServerProcessingException;
/**
* Disconnects gracefully the client. If the method is not call, a timeout
mechanism
* will perform it anyway.
*
*/
public abstract void disconnect();
}
For accessing the Stateful Web Service:
public interface NuxeoWSMainEntrancePointable {
/**
* Gives access to Live Edit Stateful Web Service.
* @param userName credential
* @param password credential
* @return the EPR to the stateful instance.
* @throws ClientAuthenticationException the credentials are bad
*/
public abstract W3CEndpointReference accessExternalEditWS(
String userName, String password) throws
ClientAuthenticationException;
} "
"> You are right. Moreover, I think always this will be a xpath. Otherwise I
don't see how can I uniquely identify the requested
> property, without knowing the schema name and the complete name.
the problem is that fieldName can define a field that is in fact a list of
other fields (Complex type).
=> we need to be able that in the files schmea we want to edit the doc that is
the 3th element of the list of file
> OTOH, I don't see an usage for the repo argument. I kept it because of the
> initial API design, do you think I should keep it for future > usage?
Nuxeo WebApp support to be connected to several repository.
=> This parameter must be present even if in defaut deployment there is only
one repository.
>> Since we may need to edit Blob that are part of a complex type, I think the
>> fieldName parameter should be renamed as xPath.
>> In simple cases, the xPath parameter will simply be the fieldName, but in
>> more complexe cases (like editing a file containted
>> inside the files schema) we will use a real XPath expression."
> Implement LiveEdit Web Service on top of JAXWS
> ----------------------------------------------
>
> Key: NXP-1720
> URL: http://jira.nuxeo.org/browse/NXP-1720
> Project: Nuxeo Enterprise Platform 5
> Issue Type: New Feature
> Affects Versions: 5.2
> Reporter: Radu Darlea
> Assignee: Radu Darlea
> Fix For: 5.2
>
>
> Implement the new LiveEdit Web Service.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
http://jira.nuxeo.org/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
_______________________________________________
ECM-tickets mailing list
[email protected]
http://lists.nuxeo.com/mailman/listinfo/ecm-tickets