    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.felix.bundleplugin;
    20
    21
    22  import java.io.BufferedReader;
    23  import java.io.ByteArrayInputStream;
    24  import java.io.ByteArrayOutputStream;
    25  import java.io.IOException;
    26  import java.io.InputStream;
    27  import java.io.InputStreamReader;
    28  import java.net.URL;
    29  import java.util.ArrayList;
    30  import java.util.Arrays;
    31  import java.util.HashSet;
    32  import java.util.Iterator;
    33  import java.util.LinkedHashMap;
    34  import java.util.List;
    35  import java.util.Map;
    36  import java.util.Set;
    37  import java.util.TreeSet;
    38  import java.util.regex.Pattern;
    39
    40  import javax.xml.transform.Transformer;
    41  import javax.xml.transform.TransformerFactory;
    42  import javax.xml.transform.stream.StreamResult;
    43  import javax.xml.transform.stream.StreamSource;
    44
    45  import aQute.bnd.header.Attrs;
    46  import aQute.bnd.service.AnalyzerPlugin;
    47  import aQute.bnd.osgi.Analyzer;
    48  import aQute.bnd.osgi.Descriptors.PackageRef;
    49  import aQute.bnd.osgi.Jar;
    50  import aQute.bnd.osgi.Processor;
    51  import aQute.bnd.osgi.Resource;
    52  import aQute.libg.generics.Create;
    53  import aQute.libg.qtokens.QuotedTokenizer;
    54  import aQute.service.reporter.Reporter;
    55  import org.apache.felix.utils.manifest.Attribute;
    56  import org.apache.felix.utils.manifest.Clause;
    57  import org.apache.felix.utils.manifest.Directive;
    58  import org.osgi.framework.Constants;
    59
    60  import static org.apache.felix.utils.manifest.Parser.parseHeader;
    61
    62
    63  public class BlueprintPlugin implements AnalyzerPlugin
    64  {
    65
    66      static Pattern QN = Pattern.compile( "[_A-Za-z$][_A-Za-z0-9$]*(\\.[_A-Za-z$][_A-Za-z0-9$]*)*" );
    67      static Pattern PATHS = Pattern.compile( ".*\\.xml" );
    68
    69      Transformer transformer;
    70
    71
    72      public BlueprintPlugin() throws Exception
    73      {
    74          transformer = getTransformer( getClass().getResource( "blueprint.xsl" ) );
    75      }
    76
    77
    78      public boolean analyzeJar( Analyzer analyzer ) throws Exception
    79      {
    80          String mode = analyzer.getProperty("service_mode");
    81          if (mode == null) {
    82              mode = "service";
    83          }
    84
    85          transformer.setParameter( "nsh_interface",
    86              analyzer.getProperty( "nsh_interface" ) != null ? analyzer.getProperty( "nsh_interface" ) : "" );
    87          transformer.setParameter( "nsh_namespace",
    88              analyzer.getProperty( "nsh_namespace" ) != null ? analyzer.getProperty( "nsh_namespace" ) : "" );
    89
    90          Set<String> headers = Create.set();
    91
    92          String bpHeader = analyzer.getProperty( "Bundle-Blueprint", "OSGI-INF/blueprint" );
    93          Map<String, ? extends Map<String, String>> map = Processor.parseHeader( bpHeader, null );
    94  		bpHeader = "";
    95          for ( String root : map.keySet() )
    96          {
    97              Jar jar = analyzer.getJar();
    98              Map<String, Resource> dir = jar.getDirectories().get( root );
    99              if ( dir == null || dir.isEmpty() )
    100             {
    101                 Resource resource = jar.getResource( root );
    102                 if ( resource != null )
    103 				{
    104                     process( analyzer, root, resource, headers );
    105 					if (bpHeader.length() > 0) {
    106 						bpHeader += ",";
    107 					}
    108 					bpHeader += root;
    109 				}
    110                 continue;
    111             }
    112             for ( Map.Entry<String, Resource> entry : dir.entrySet() )
    113             {
    114                 String path = entry.getKey();
    115                 Resource resource = entry.getValue();
    116                 if ( PATHS.matcher( path ).matches() )
    117 				{
    118                     process( analyzer, path, resource, headers );
    119 					if (bpHeader.length() > 0) {
    120 						bpHeader += ",";
    121 					}
    122 					bpHeader += path;
    123 				}
    124             }
    125         }
    126 		if( !map.isEmpty() )
    127 		{
    128 			analyzer.setProperty("Bundle-Blueprint", bpHeader);
    129 		}
    130
    131         // Group and analyze
    132         Set<String> caps = Create.set();
    133         Set<String> reqs = Create.set();
    134         Map<String, Set<Clause>> hdrs = Create.map();
    135         for ( String str : headers )
    136         {
    137             int idx = str.indexOf( ':' );
    138             if ( idx < 0 )
    139             {
    140                 analyzer.warning( ( new StringBuilder( "Error analyzing services in blueprint resource: " ) ).append(
    141                     str ).toString() );
    142                 continue;
    143             }
    144             String h = str.substring( 0, idx ).trim();
    145             String v = str.substring( idx + 1 ).trim();
    146             Clause[] hc = parseHeader(v);
    147             // Convert generic caps/reqs
    148             if ("Import-Service".equals(h))
    149             {
    150                 if (!"service".equals(mode))
    151                 {
    152                     Clause clause = hc[0];
    153                     String multiple = clause.getDirective("multiple");
    154                     String avail = clause.getDirective("availability");
    155                     String filter = clause.getAttribute("filter");
    156
    157                     StringBuilder sb = new StringBuilder();
    158                     sb.append("osgi.service;effective:=active;");
    159                     if ("optional".equals(avail)) {
    160                         sb.append("resolution:=optional;");
    161                     }
    162                     if ("true".equals(multiple)) {
    163                         sb.append("cardinality:=multiple;");
    164                     }
    165                     if (filter == null) {
    166                         filter = "(" + Constants.OBJECTCLASS + "=" + clause.getName() + ")";
    167                     } else if (!filter.startsWith("(") && !filter.endsWith(")")) {
    168                         filter = "(&(" + Constants.OBJECTCLASS + "=" + clause.getName() + ")(" + filter + "))";
    169                     } else {
    170                         filter = "(&(" + Constants.OBJECTCLASS + "=" + clause.getName() + ")" + filter + ")";
    171                     }
    172                     sb.append("filter:=\"").append(filter).append("\"");
    173                     reqs.add(sb.toString());
    174                 }
    175                 else if (!"generic".equals(mode))
    176                 {
    177                     Set<Clause> clauses = hdrs.get(h);
    178                     if (clauses == null) {
    179                         clauses = new HashSet<Clause>();
    180                         hdrs.put(h, clauses);
    181                     }
    182                     clauses.addAll(Arrays.asList(hc));
    183                 }
    184             }
    185             else if ("Export-Service".equals(h))
    186             {
    187                 if (!"service".equals(mode))
    188                 {
    189                     StringBuilder sb = new StringBuilder();
    190                     sb.append("osgi.service;effective:=active;objectClass");
    191                     if (hc.length > 1) {
    192                         sb.append(":List<String>=\"");
    193                     } else {
    194                         sb.append("=\"");
    195                     }
    196                     for (int i = 0; i < hc.length; i++)
    197                     {
    198                         if (i > 0)
    199                         {
    200                             sb.append(",");
    201                         }
    202                         sb.append(hc[i].getName());
    203                     }
    204                     sb.append("\"");
    205                     for (int i = 0; i < hc[0].getAttributes().length; i++)
    206                     {
    207                         sb.append(";");
    208                         sb.append(hc[0].getAttributes()[i].getName());
    209                         sb.append("=\"");
    210                         sb.append(hc[0].getAttributes()[i].getValue());
    211                         sb.append("\"");
    212                     }
    213                     caps.add(sb.toString());
    214                 }
    215                 else if (!"generic".equals(mode))
    216                 {
    217                     Set<Clause> clauses = hdrs.get(h);
    218                     if (clauses == null) {
    219                         clauses = new HashSet<Clause>();
    220                         hdrs.put(h, clauses);
    221                     }
    222                     clauses.addAll(Arrays.asList(hc));
    223                 }
    224             }
    225             else
    226             {
    227                 Set<Clause> clauses = hdrs.get(h);
    228                 if (clauses == null)
    229                 {
    230                     clauses = new HashSet<Clause>();
    231                     hdrs.put(h, clauses);
    232                 }
    233                 clauses.addAll(Arrays.asList( hc ) );
    234             }
    235         }
    236         if (!caps.isEmpty())
    237         {
    238             StringBuilder sb = new StringBuilder();
    239             String header = analyzer.getProperty("Provide-Capability");
    240             if (header != null)
    241             {
    242                 sb.append(header);
    243             }
    244             for (String cap : caps) {
    245                 if (sb.length() > 0) {
    246                     sb.append(",");
    247                 }
    248                 sb.append(cap);
    249             }
    250             analyzer.setProperty("Provide-Capability", sb.toString());
    251         }
    252         if (!reqs.isEmpty())
    253         {
    254             StringBuilder sb = new StringBuilder();
    255             String header = analyzer.getProperty("Require-Capability");
    256             if (header != null)
    257             {
    258                 sb.append(header);
    259             }
    260             for (String req : reqs) {
    261                 if (sb.length() > 0) {
    262                     sb.append(",");
    263                 }
    264                 sb.append(req);
    265             }
    266             analyzer.setProperty("Require-Capability", sb.toString());
    267         }
    268         // Merge
    269         for ( String header : hdrs.keySet() )
    270         {
    271             if ( "Import-Class".equals( header ) || "Import-Package".equals( header ) )
    272             {
    273                 Set<Clause> newAttr = hdrs.get(header);
    274                 for ( Clause a : newAttr )
    275                 {
    276                     String pkg = a.getName();
    277                     if ( "Import-Class".equals( header ) )
    278                     {
    279                         int n = a.getName().lastIndexOf( '.' );
    280                         if ( n > 0 )
    281                         {
    282                             pkg = pkg.subSequence( 0, n ).toString();
    283                         }
    284                         else
    285                         {
    286                             continue;
    287                         }
    288                     }
    289                     PackageRef pkgRef = analyzer.getPackageRef( pkg );
    290                     if ( !analyzer.getReferred().containsKey( pkgRef ) )
    291                     {
    292                         Attrs attrs = analyzer.getReferred().put(pkgRef);
    293                         for (Attribute attribute : a.getAttributes())
    294                         {
    295                             attrs.put(attribute.getName(), attribute.getValue());
    296                         }
    297                     }
    298                 }
    299             }
    300             else
    301             {
    302                 Set<String> merge = Create.set();
    303                 String org = analyzer.getProperty(header);
    304                 if (org != null && !org.isEmpty())
    305                 {
    306                     for (Clause clause : parseHeader(org))
    307                     {
    308                         merge.add(clause.toString());
    309                     }
    310                 }
    311                 for (Clause clause : hdrs.get(header))
    312                 {
    313                     merge.add(clause.toString());
    314                 }
    315                 StringBuilder sb = new StringBuilder();
    316                 for (String clause : merge)
    317                 {
    318                     if ( sb.length() > 0 )
    319                     {
    320                         sb.append( "," );
    321                     }
    322                     sb.append(clause);
    323
    324                 }
    325                 analyzer.setProperty( header, sb.toString() );
    326             }
    327         }
    328         return false;
    329     }
    330
    331
    332     private void process( Analyzer analyzer, String path, Resource resource, Set<String> headers )
    333     {
    334         InputStream in = null;
    335         try
    336         {
    337             in = resource.openInputStream();
    338
    339             // Retrieve headers
    340             Set<String> set = analyze( in );
    341             headers.addAll( set );
    342         }
    343         catch ( Exception e )
    344         {
    345             analyzer.error( ( new StringBuilder( "Unexpected exception in processing spring resources(" ) )
    346                 .append( path ).append( "): " ).append( e ).toString() );
    347         }
    348         finally
    349         {
    350             try
    351             {
    352                 if ( in != null )
    353                 {
    354                     in.close();
    355                 }
    356             }
    357             catch ( IOException e )
    358             {
    359             }
    360         }
    361     }
    362
    363
    364     public Set<String> analyze( InputStream in ) throws Exception
    365     {
    366         Set<String> refers = new HashSet<String>();
    367         ByteArrayOutputStream bout = new ByteArrayOutputStream();
    368         javax.xml.transform.Result r = new StreamResult( bout );
    369         javax.xml.transform.Source s = new StreamSource( in );
    370         transformer.transform( s, r );
    371         ByteArrayInputStream bin = new ByteArrayInputStream( bout.toByteArray() );
    372         bout.close();
    373         BufferedReader br = new BufferedReader( new InputStreamReader( bin ) );
    374         for ( String line = br.readLine(); line != null; line = br.readLine() )
    375         {
    376             line = line.trim();
    377             line = line.replace( ";availability:=mandatory", "" );
    378             if ( line.length() > 0 )
    379             {
    380                 refers.add( line );
    381             }
    382         }
    383
    384         br.close();
    385         return refers;
    386     }
    387
    388
    389     protected Transformer getTransformer( URL url ) throws Exception
    390     {
    391         TransformerFactory tf = TransformerFactory.newInstance();
    392         javax.xml.transform.Source source = new StreamSource( url.openStream() );
    393         return tf.newTransformer( source );
    394     }
    395
    396 }