Spring Security – ACL Concepts
In this post I would like to discuss about ACL concepts in Spring Security .
ACL ( Access Control Lists) based security is means of providing access to particular Object using combination of
- User and
- Actions that can be performed by that User ( create, edit, update , delete)
Below is overview of topics I shall cover
- Motivation/Use cases
- Schema
- Interfaces
- Annotations and Permissions
- Example
- References
Motivation/Use cases
In one of my previous article, I have demonstrated use of Spring for role based security. This works fine if you want to restrict access to urls based on particular roles user has . For example
- ROLE_USER – provide access to all authenticated users
- ROLE_CUSTOMER – provide access to all users who have made some purchase before
- ROLE_ADMIN – provide access to users with admin capabilities
However if you want to provide fine grained access at object level , you can use ACL based security. For example
- An e-commerce website
- CUSTOMER - view
- CUSTOMER_SERVICE - view, edit
- MANAGER – view, edit, update,delete
- Banking software
- CUSTOMER – view
- CLERK – view
- MANAGER – view , edit, delete
- Blogging website
- ANONYMOUS_USER – view
- AUTHENTICATED_USER – view, edit
- ADMIN – view, edit, delete
Schema
Below are the core tables, pertaining to ACL, you need to have in your database.
Interfaces
Below represents key interfaces involved in implementing ACL Based security. These needs to be configured in security-context
Annotations and Permissions
You can use combination of expression and annotation to provide method level security
Example of expression is - hasPermission(#account, ‘WRITE’)
Annotations which spring provides are
@PreAuthorize , @PreFilter, @PostFilter, @PostAuthorize, @Secured and JSR 250
Below is snippet of code for couple of annotations
////Expression evaluated before method execution. In this case checks if principal can write
@PreAuthorize("hasPermission(#account, 'WRITE')")
public Boolean editAccount(Account account);
///// Filters the results. In this case Just returns new accounts
@PostFilter("filterObject.isNew()")
public List<Account> getNewAccounts();
Example
References
Below are some good references for further reading
- ACL implemented for blogging application can be found here
- Good conceptual overview can be found here
- DZone refcardz
- Great presentation about spring security at slideshare
Java Http Server to Stream Logs
Streaming Logs is technique you can use if you need to know more about your application performance/usage.
In this post I would like show how to develop a Java based Http Server which you can use to Stream Logs.
Code for application can be found here.
Overview
Concept can be depicted in diagram below
Below are the important components in the application
- LogServer – Server which streams logs
- LogWatcher – Thread to monitor logs
- MessageQueue – Stores Messages to be sent to client
- MessageListener – Interface to listen for messages
- ConnectionWatcher – Thread to monitor connection
- ConnectionHandler – Thread which streams response to client
Lets look at each of them in detail
LogServer
This is a Http Server which accepts connections from client. When server is started, it performs below functions
- Starts LogWatcher
- Starts ConnectionWatcher
- Waits for connection request
when request for connection comes , it
- Creates connectionHandler
- Adds connectionHandler to listeners in MessageQueues
Below is section from code
public void startServer() throws IOException{
System.out.println("Starting server...");
executor.execute(logWatcher);
executor.execute(connectionWatcher);
while(keepRunning){
SocketChannel socketChannel = serverSocketChannel.accept();
handleConnection(socketChannel);
}
}
private void handleConnection(final SocketChannel channel) {
try{
System.out.println("Handling request for client. Address ="+channel.socket().getInetAddress()
+", port="+channel.socket().getPort());
ConnectionHandler connectionHandler = new ConnectionHandler(channel);
messageQueue.addListener(connectionHandler);
executor.execute(connectionHandler);
connectionWatcher.addConnection(channel);
System.out.println("Handling request for client complete");
} catch(IOException ioex){
System.out.println("Exception handling request for client. Address
="+channel.socket().getInetAddress()+", port ="+channel.socket().getPort());
}
}
LogWatcher
This component monitors log file for changes.It performs below functions
- Monitors size of file determine changes.
- Changes are added to MessageQueue.
- Uses RandomAccessFile , so entire file is not scanned each time
Below is section from code
public void run() {
System.out.println("Running LogWatcher....");
System.out.println("LogWatcher Thread - "+Thread.currentThread().getName());
long origSize = 0;
while(keepRunning) {
try {
if(logFile.length() > origSize) {
addLinesToQueue();
}
origSize = logFile.length();
if((msgCount % 100) == 0){
Thread.sleep(20000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void addLinesToQueue() {
try{
RandomAccessFile raf = new RandomAccessFile(logFile, "r");
raf.skipBytes(bytesToSkip);
String line="";
while((line=raf.readLine())!=null) {
if ( line.length() >=4 && line.substring(0,4).matches(lineStartPattern) ) {
messageQueue.addToQueue(line);
msgCount++;
}
bytesToSkip = bytesToSkip + line.getBytes().length;
}
} catch (FileNotFoundException fnex){
System.out.println("cannot locate log file");
} catch (IOException e) {
e.printStackTrace();
}
}
MessageQueue
This object stores messages which are to be sent to all Listeners. Internally it uses
- Unbounded LinkedBlockingQueue to store messages
- Concurrent HashMap to store Listeners
It notifies listeners if number of messages in queue exceeds threshold (100 in this case)
Below is section from code
public void addToQueue(String msg) {
if (listeners.size() > 0) {
try {
msgQueue.put(msg);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(msgQueue.size() ==100){
System.out.println("notifying listeners start");
notifyListeners();
System.out.println("notifying listeners end");
}
}
MessageListener
This interface listens to changes in MessageQueue and handles those messages. In this particular example, ConnectionHandlers implement this interface and listen for messages.
This also illustrates use of Observer Pattern, wherein you have set of registered listeners who are listening for changes.
Below is section from code
public interface MessageListener {
public void onMessage(String message) /*throws Exception*/;
public String getName();
}
ConnectionHandler
This object handles responses for client. For every request, Server instantiates new ConnectionHandler.
ConnectionHandler implements 2 interfaces
- Runnable – To facilitate writing to connection output stream
- MessageListener – to listen for new messages from MessageQueue
To write to output, it uses PrintWriter . When creating instance of PrintWriter we set autoflush to true, so data is flushed to outputstream . Also notice PrintWriter is kept open and closed only when thread is stopped
Below is section from code
public void run() {
try {
System.out.println("RequestHandler Thread - "+Thread.currentThread().getName());
while(keepRunning){
String line = messagesQueue.take();
if(line.length() > 0) {
pw.println(line);
}
}
pw.close();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void onMessage(String message) {
try {
messagesQueue.put(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ConnectionWatcher
This thread facilitates monitoring of connection and notifies MessageQueue when connection is closed. ( MessageQueue removes Handler from list of listeners) .
Default methods which are available in sockets don’t provide information if connection with remote client is open/closed. One way to determine this is to read through the SocketChannel. If connection is closed, IOException is thrown. ( This is reason we are using SocketChannel)
Below is section of code
public void run() {
while(keepRunning){
System.out.println("Running ConnectionWatcher...");
for(SocketChannel socketChannel:connectionQueue){
if(!isConnectionOpen(socketChannel)){
System.out.println("Removing ="+socketChannel.socket().getInetAddress()+":
"+socketChannel.socket().getPort());
connectionQueue.remove(socketChannel);
messageQueue.removeListener(socketChannel.socket().getInetAddress()+":"+
socketChannel.socket().getPort());
} else {
System.out.println("Connection ="+socketChannel.socket().getInetAddress()+":
"+socketChannel.socket().getPort()+" is open.");
}
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private boolean isConnectionOpen (SocketChannel connection){
try {
ByteBuffer buffer = ByteBuffer.allocate(24);
int val = connection.read(buffer);
System.out.println("Is connection Open , Val ="+val);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
Running The application
1. Start LoggingApp – this is simple java program which generates logs. It uses log4j. (pass log4j.xml as java -D param)
2. Start LoggingServer ( pass log file as parameter)
3. Open Terminal (one or multiple) and type curl http://localhost:8080/ to see the logfiles in terminal
Usecases
Below are some use cases
1. Monitor application performance/usage in real time . Heroku recently released log2viz for their apps, which is similar concept.
2. Stream data to Hadoop or other ML systems to do facilitate predictive analysis
Conclusion
From this application/prototype, below are some concepts we can learn
1. Use of Concurrent collections ( you don’t have to worry about synchronized blocks or locks).
2. Use of Executor Framework , Producer-Consumer pattern.
3. Use of Observer Pattern .
4. Socket programming
Google Glasses – a fantastic use case for Indian market
Google recently unveiled video google glasses. There can be many use cases and people are still thinking how it can be put to use. Below are some great use cases pertaining to indian market, to improve lifestyle of common man
- Capturing videos of people who jump traffic light
Ever since I moved to India couple of years back, one of things which annoys me is way people abuse Traffic Light. People don’t have any regard whatsoever for rules or consideration for other people. I have always wanted to take a picture of these people and upload it in public domain. Being behind wheel, its not practical to take phone , capture picture, upload it etc. However ifIhadglasses I would just wear them while driving and capture these pictures/videos and upload them in public domain
- Capturing behavior of people who abuse their public office
This has been limited to purview of news editors or people who have fancy gadgets with hidden cameras. However ifIhadglasses I would capture these events upload it in public domain
Overall its fantastic technology, and can improve/change life of many for better
Simple Engineering Principles for Scalability
Scalability is important factor when engineering systems/applications and something that cannot happen overnight. In this post I would like to talk about 5 principles if followed, will help you to scale when right time comes
1. Layered Architectures and Interfaces
- Design components as layers and have them communicate via Interface
- Each Layer should be represented by appropriate Interface. Don’t design to implementations
- Interfaces provide immense flexibility and they can swapped out , when need arises
- Example having Datastore interface which can have storage/retrieval methods.
- Implementation for this can be for File, DataBase or any other external service. Application in itself doesn’t have to know
- Example having Parser interface which can have parse method
- Implementation can be XMLParser, CsvParser
- Example having Datastore interface which can have storage/retrieval methods.
Good description of above can be found in chapter 3 of Expert Spring MVC and Web Flows
2. Loosely coupled services
- Design each component as Service.
- Expose services via API’s / REST / SOAP interface.
- when communicating to these service configure appropriate Time-Out’s
- Example – When displaying page having multiple components, try to have an aggregator which collects information , assembles and returns the information. In case no response received (for per-configured Timeout) , have a place-holder . In this way in case of failure only one component will not be displayed instead of entire page going down.
3. Stateless web tier
- Design application based on REST principles. Every interaction should be isolated in it-self
- Externalize session information to some key-value data-store, so request can be distributed across the servers
4. Asynchronous processes
- Wherever possible use asynchronous processes
- Ensure your queues are configured for throughput and manage readers/writers appropriately
5. No-SQL / Sharding
- No-SQL – Select database according to context of problem which you are solving, instead of using RDBMS as default.
- Functional /Vertical Sharding – Group functionalities to-gather so they can exist by themselves( eg Independent war). As throughput increases you can increase capacity/instances on need basis.
- Horizontal Sharding – Distribute data horizontally based on some criteria so that a particular table doesn’t get overloaded
In addition to above you also need to Leverage cloud to your advantage. Most of problems related to Scalability has been solved by cloud service providers like Amazon and Google and you don’t have to re-invent wheel
Quick Facebook Integration using Spring social
In this post I shall discuss on how you can integrate your application with Facebook using Spring social.
Below are key concepts I shall cover
1. ConnectController
2. Configuration
3. FacebookController
Code for example is available at github
ConnectController
Connection to facebook happens via ConnectController. From application perspective all you need to do is create form with action “connect/facebook” & method “POST“, and controller will take care of rest. In case of disconnect you also need to pass hidden variable “_method” with value “delete”. Both of above can be seen in facebookConnect.jsp and facebookConnected.jsp
Configuration
Configuration related to spring-social is stored in spring-social.xml. Below is section from same
<bean id="connectionFactoryRegistry" class="org.springframework.social.connect.support.ConnectionFactoryRegistry">
<property name="connectionFactories">
<list>
<bean class="org.springframework.social.facebook.connect.FacebookConnectionFactory">
<constructor-arg value="${facebook.clientId}" />
<constructor-arg value="${facebook.clientSecret}" />
</bean>
</list>
</property>
</bean>
<bean id="jdbcConnectionRepository" class="org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository">
<constructor-arg ref="dataSource" />
<constructor-arg ref="connectionFactoryRegistry" />
<constructor-arg ref="textEncryptor" />
</bean>
<bean id="userConnectionRepository" factory-method="createConnectionRepository"
factory-bean="jdbcConnectionRepository" scope="request">
<constructor-arg value="#{request.getRequestURI().split('/').length > 3 ? request.getRequestURI().split('/')[3] : 'guest'}" />
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
Key points to note
- connectionFactoryRegistry – specifies the connection factory which you intend to use. In this case it is FacebookConnectionFactory. You also need to pass in clientId and clientSecret which can be obtained from facebook developer website.
- jdbcConnectionRepository – repository to store information about user connecting through your application. In this case it is implemented via JdbcConnectionRepository.
- userConnectionRepository - repository where information about specific user connecting via your application is stored. It is stored in userconnections table. note that it is request scoped bean. Before starting the app, be reminded to run the userconnections.sql script which creates the table
- SPeL – under the hood, Spring Social uses Spring Security. In this case we are using SPeL to evaluate the URI and extract providerUserID if its available. If its not available we assign user as guest. You can read more about this here and here
FacebookController
This is application specific controller. It consists of 2 methods, both stateless
- getProfile - returns profile of user whose providerId and accessToken is passed
- getFriends -returns friends of user whose providerId and accessToken is passed. This method also returns response in xml format
Lets look at getFriends in detail. Below is section of code
@RequestMapping(value="/friends/{providerUserId}/{facebookAccessToken}", method = RequestMethod.GET)
public String getFriends(@PathVariable("facebookAccessToken") String facebookAccessToken,
@PathVariable("providerUserId") String providerUserId,
Model model){
logger.info("providerUserId = "+providerUserId);
logger.info("facebookAccessToken = "+facebookAccessToken);
boolean userExistsInRepo = fbConnectionHelper.checkForUserInRepository(providerUserId,userConnectionRepository);
if(userExistsInRepo) {
fbConnectionHelper.updateExistingConnectionInRepository(providerUserId,facebookAccessToken,
userConnectionRepository);
} else {
fbConnectionHelper.addNewConnectionToRepository(providerUserId, facebookAccessToken, userConnectionRepository);
}
Facebook facebook =userConnectionRepository.getConnection(Facebook.class, providerUserId).getApi();
FriendsList friendsListDto = fbOperationsHelper.getFriendNames(facebook);
model.addAttribute("friendsListDto",friendsListDto);//// for xml
model.addAttribute("friendsList",friendsListDto.getFriendsList()); //// for jsp
return "facebook/friends";
}
Key points to note
- Request Parameters are
- providerUserId - we extract this via SPeL if its available
- facebookAccessToken
- Response Type
- if extension (xml/json) is typed in url response will be in that format
- if no extension is specified, facebook/friends.jsp will be returned
- Configuration for views is set up in spring-servlet.xml
- Business logic
- This method is particularly useful if you have facebookAccessToken associated with user ( via phone application ) and you don’t want user to log-in again
- when request comes, we check to see if information about user (providerUserId) is available in userconnections table.
- if its available then we update the table with new token.
- if user is not present in userconnections table we add new entry
Below are Uri’s associated with application
- http://localhost:port/connect/facebook - To connect & disconnect
- http://localhost:port/fb/profile/providerUserId/accessToken – To view profile
- http://localhost:port/fb/friends/providerUserId/accessToken – To view friends as jsp page
- http://localhost:port/fb/friends/providerUserId/accessToken.xml – To view friends as xml
That completes a quick overview of facebook integration.
You could find more elaborate treatment of similar topic ( including Twitter Integration and using Thyme Leaf templates) here. Thanks Mark for detailed information on topic
Degrees of Separation – using Spring Batch and Hadoop
In this post I shall discuss about computing Degrees of Separation using Spring Batch and Spring Hadoop.
In real world , Degrees of separation is used by Social Networking sites like Twitter, Facebook and Linked In to compute distance between users.
For this particular example I shall be using Breadth First Search(BFS) to compute Degrees of Separation for a actor-movie graph.
This link was Inspiration for post and Input was obtained from here
Example is based on Oracle Of Bacon game . Source code for example can be downloaded from github
Breadth First Search
In Graph processing, Breadth first search is used to compute shortest path between 2 Nodes in UnDirected Graph, where all the edges are of equal weight. Example in this post uses Distributed BFS to facilitate distributed computation using Map-Reduce.
Sequence of Steps
Below are sequence of steps for Spring Batch job which we will create. Each item below is represented as Step in Spring Batch
- PreProcessing the input – generating input file for Hadoop
- Copying Input to Hadoop
- Running BFS Iteratively using Hadoop
- Copying Generated Output
- PostProcessing the output – Generating Map of connections from output file
- Computing Degrees of Separation
Below is the section from application context where all of above steps has been mentioned
<batch:job id="job">
<batch:step id="pre-processor" next="local-to-hadoop">
<batch:tasklet ref="preProcessor"/>
</batch:step>
<batch:step id="local-to-hadoop" next="bfs">
<batch:tasklet ref="localToHadoop"/>
</batch:step>
<batch:step id="bfs" next="iterateDecision">
<batch:tasklet ref="bfsTask">
<batch:listeners>
<batch:listener ref="iterationDecider" />
</batch:listeners>
</batch:tasklet>
</batch:step>
<batch:decision id="iterateDecision" decider="iterationDecider">
<batch:next on="CONTINUE" to="bfs" />
<batch:next on="COMPLETED" to="hadoop-to-local"/>
</batch:decision>
<batch:step id="hadoop-to-local" next="post-processor">
<batch:tasklet ref="hadoopToLocal"/>
</batch:step>
<batch:step id="post-processor" next="degrees-of-separation">
<batch:tasklet ref="postProcessor"/>
</batch:step>
<batch:step id="degrees-of-separation" >
<batch:tasklet ref="degreesOfSeparation"/>
</batch:step>
</batch:job>
Lets look at each of above steps
Pre-Processing Input
Input is raw file with information about movies and actors. This needs to be converted in form of Nodes and Edges which can be fed as into hadoop for further processing.
SymbolGraph is representation of input file as Graph using AdjacencyList. Below are key Objects in SymbolGraph
- symbolTable – To maintain index of words in file
- invertedIndex – Inverted Index to map index to name
- adjList – Adjacency List representation of input
SymbolGraphBfs is sub-class of SymbolGraph, which adds additional attributes (distance, color and last_node_reached_from) to SymbolGraph, inorder to facilitate performing Distributed BFS. Below are key objects in SymbolGraphBfs
- nodeMapBeforeBfs ( adjList from symbolGraph is used to create this)
- Map of Id and Node.
- Node in turn consists of Id , List Of Edges, distance, color and lastNodeReachedFrom.
- Map of Id and Node.
- nodeConnectionsMap
- Map of Id and Node after BFS is complete. More on this later.
- After BFS is completed distance, color and lastNodeReachedFrom is updated
- distance – For source is 0, and for any other node distance is based on depth of node from source node
- Color
- Before start- source is gray, and everything else is white.
- After completion – All nodes reachable are black and all nodes un-reachable is white
- lastNodeReachedFrom – Last Node from which this particular node was reached
SymbolGraphBfs is created when applicationContext initializes and is passed as parameter to PreProcessor
PreProcessor writes nodeMapBeforeBfs to file which can be fed as input to Hadoop. Below are couple of lines from the file
9145 9138,10232,17203,39955,47706,48907,49425,51037,57750,59586,63036,65828:0:GRAY:9145 63036 63037,571,63038,9145,.........,25227,63061,63062,63063:Integer.MAX_VALUE:WHITE:-1
where
- Delimiter – ” : ” (Colon)
- ID – 9145 and 63036. ( Note 9145 is id associated with source – Bacon,Kevin)
- Edges
- 9145 - 9138,10232,17203,39955,47706,48907,49425,51037,57750,59586,63036,65828
- 63036 – 63037,571,63038,9145,………,25227,63061,63062,63063
- Distance from source
- 9145 - its 0, since its source
- 63036 – its set to Infinity
- Color
- 9145 - its GRAY, since its source
- 63036 – its WHITE
- lastNodeReachedFrom
- 9145 – its same as 9145
- 63036 – its set to -1 as part of initialization
Copying Input File to Hadoop
This task is fairly straightforward. It copies input file generated in PreProcessor step to Hadoop input directory. This is accomplished by LocalToHadoop.java
Running BFS Iteratively using Hadoop
In this step Mapper and Reducer is run iteratively to compute BFS. Below is section from configuration.
<job id="bfsHdpJob"
input-path="#{stepExecutionContext['mr.input']}"
output-path="#{stepExecutionContext['mr.output']}"
mapper="com.abstractlayers.degrees.tasks.BfsMapper"
reducer="com.abstractlayers.degrees.tasks.BfsReducer"
scope="step" />
- input-path – Path to Map-Reduce input-directory .
- output-path – Path to Map-Reduce output-directory
- mapper – Mapper class , represented by BFSMapper
- reducer – Reducer class, represented by BFSReducer
Data structures for Mapper , Reducer and Node are based off JohnandCallin blog article on Map-reduce with some minor modifications listed below
- In case of Nodes, there is an additional attribute lastNodeReachedFrom
- Changed delimiter in file.
- In case of Mapper, Context is used in place of Output and Reporter
To run the task iteratively I have used batch-decision from Spring Batch. Below is section from application context
<batch:decision id="iterateDecision" decider="iterationDecider">
<batch:next on="CONTINUE" to="bfs" />
<batch:next on="COMPLETED" to="hadoop-to-local"/>
</batch:decision>
<beans:bean id="iterationDecider" class="com.abstractlayers.degrees.tasks.IterationDecider">
<beans:property name="maxIterations" value="${iteration.count}" />
</beans:bean>
IterationDecider above facilitates two things
- Terminate step after 12 (variable iteration.count in dos.properties) iterations. As all inputs/outputs converge after this many iterations.
- Updates input-path/output-path for each successive iteration, so we can chain output from one iteration as input to another.
Below are couple of lines from output file
9145 9138,10232,17203,39955,47706,48907,49425,51037,57750,59586,63036,65828:0:BLACK:9145 63036 63037,571,63038,9145,.........,25227,63061,63062,63063:1:BLACK:9145
ID and Edges are same as input , but lets look at Distance,Color and lastNodeReachedFrom
- Distance
- 9145 - its 0, since its source
- 63036 – This is updated to 1. (it has direct connection to source and can be reached in 1 hop.)
- Color
- 9145 - updated to BLACK
- 63036 – updated to BLACK
- lastNodeReachedFrom
- 9145 – its same as 9145
- 63036 – This is updated to 9145, since 63036 can be reached from 9145
Copying Generated Output
In this step files are copied from Hadoop to Local environment. This is accomplished by HadoopToLocal.java
Post-Processing
Once the ouput is generated PostProcessor reads outputfile and updates nodesConnectionsMap
Degrees Of Separation Client
This is last step. It demonstrates how degrees of separation can be computed. In real world this will be some client which uses nodeConnectionsMap to compute degrees of separation. nodeConnectionsMap can be stored some key value databases ( eg. Redis) and used for distributed clients. In this specific example for source “Bacon, Kevin” it displays following information
- Total connections – 73210
- First Degree – 13
- Second Degree – 741
- Third Degree – 1579
- Distance between “Bacon,Kevin” and “Smith,Isaac” – 6
- Sample path between “Bacon,Kevin”and “Smith,Isaac” .
- Bacon, Kevin
- JFK (1991)
- Pesci, Joe
- Lethal Weapon 3 (1992)
- Hipp, Paul (I)
- Another Day in Paradise (1997)
- Smith, Isaac
I verified above results by running this program in single node and results are in alignment. Path shown is different however degree is no less than 6
Note the conventions above
- If an actor has worked directly in movie with source (Bacon,Kevin) his degree of separation will be 2.
- However you can tweak application per your needs if you want to change it to 1.
Running the application
Building package
mvn clean assembly:assembly
Configuration
Update HADOOP_CLASSPATH in hadoop-env.sh to include path to jar file
export HADOOP_CLASSPATH=${PATH_TO_JAR_FILE}/spring-degrees-of-separation-1.0.0.jar
Running it from Hadoop
/bin/hadoop jar ${PATH_TO_JAR_FILE}/spring-degrees-of-separation-1.0.0.jar com.abstractlayers.degrees.DegreesOfSeparation -mapper com.abstractlayers.degrees.tasks.BfsMapper





